diff --git a/src/Avalonia.Base/Platform/PlatformColorValues.cs b/src/Avalonia.Base/Platform/PlatformColorValues.cs index d14b58e939..b328e1304b 100644 --- a/src/Avalonia.Base/Platform/PlatformColorValues.cs +++ b/src/Avalonia.Base/Platform/PlatformColorValues.cs @@ -61,6 +61,37 @@ public record PlatformColorValues init => _accentColor3 = value; } + /// + /// Dark shade 1 of , when the platform provides one (Windows does). + /// Null when the platform only reports the base accent color; consumers are expected to compute a shade instead. + /// + public Color? AccentColorDark1 { get; init; } + + /// + /// Dark shade 2 of , when the platform provides one. See . + /// + public Color? AccentColorDark2 { get; init; } + + /// + /// Dark shade 3 of , when the platform provides one. See . + /// + public Color? AccentColorDark3 { get; init; } + + /// + /// Light shade 1 of , when the platform provides one. See . + /// + public Color? AccentColorLight1 { get; init; } + + /// + /// Light shade 2 of , when the platform provides one. See . + /// + public Color? AccentColorLight2 { get; init; } + + /// + /// Light shade 3 of , when the platform provides one. See . + /// + public Color? AccentColorLight3 { get; init; } + public PlatformColorValues() { AccentColor1 = DefaultAccent; diff --git a/src/Avalonia.Themes.Fluent2/Accents/SystemAccentColors.cs b/src/Avalonia.Themes.Fluent2/Accents/SystemAccentColors.cs index 5d6749f9cd..5cbee992c1 100644 --- a/src/Avalonia.Themes.Fluent2/Accents/SystemAccentColors.cs +++ b/src/Avalonia.Themes.Fluent2/Accents/SystemAccentColors.cs @@ -20,7 +20,8 @@ internal sealed class SystemAccentColors : ResourceProvider private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215); // Static shade values from WinUI (matching the Uno/WinUI SystemResources defaults). Used when // the platform provides no accent color, so the out-of-the-box palette matches WinUI exactly. - // OS-provided accent colors get computed shades instead. + // When the platform reports the OS-computed shades (Windows does, via UISettings) those are + // used verbatim; otherwise an OS-provided base accent gets HSL-computed shades. private static readonly Color s_defaultSystemAccentColorDark1 = Color.FromUInt32(0xFF005A9E); private static readonly Color s_defaultSystemAccentColorDark2 = Color.FromUInt32(0xFF004275); private static readonly Color s_defaultSystemAccentColorDark3 = Color.FromUInt32(0xFF002642); @@ -117,11 +118,22 @@ internal sealed class SystemAccentColors : ResourceProvider { _invalidateColors = false; - var platformSettings = GetFromOwner(Owner); + var colorValues = GetFromOwner(Owner)?.GetColorValues(); - _systemAccentColor = platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor; + _systemAccentColor = colorValues?.AccentColor1 ?? s_defaultSystemAccentColor; - if (_systemAccentColor == s_defaultSystemAccentColor) + if (colorValues is + { + AccentColorDark1: { } dark1, AccentColorDark2: { } dark2, AccentColorDark3: { } dark3, + AccentColorLight1: { } light1, AccentColorLight2: { } light2, AccentColorLight3: { } light3 + }) + { + // The platform reported the OS-computed shades (Windows, via UISettings); + // use them verbatim so the palette matches the OS exactly. + (_systemAccentColorDark1, _systemAccentColorDark2, _systemAccentColorDark3) = (dark1, dark2, dark3); + (_systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3) = (light1, light2, light3); + } + else if (_systemAccentColor == s_defaultSystemAccentColor) { // Platforms without a real accent color report the default; use WinUI's static // shade values so the out-of-the-box palette matches WinUI exactly. diff --git a/src/Avalonia.Themes.Fluent2/Controls/CheckBox.xaml b/src/Avalonia.Themes.Fluent2/Controls/CheckBox.xaml index db1a3df3bb..0bdb0776ad 100644 --- a/src/Avalonia.Themes.Fluent2/Controls/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent2/Controls/CheckBox.xaml @@ -14,11 +14,15 @@ M5.5 10.586 1.707 6.793A1 1 0 0 0 .293 8.207l4.5 4.5a1 1 0 0 0 1.414 0l11-11A1 1 0 0 0 15.793.293L5.5 10.586Z 32 - 120 + + 0 - + + diff --git a/src/Avalonia.Themes.Fluent2/Controls/RadioButton.xaml b/src/Avalonia.Themes.Fluent2/Controls/RadioButton.xaml index 3fbbea3f17..307f42447a 100644 --- a/src/Avalonia.Themes.Fluent2/Controls/RadioButton.xaml +++ b/src/Avalonia.Themes.Fluent2/Controls/RadioButton.xaml @@ -12,7 +12,9 @@ 32 - 120 + + 0 12 14 10 @@ -28,7 +30,9 @@ - + + diff --git a/src/Avalonia.Themes.Fluent2/README.md b/src/Avalonia.Themes.Fluent2/README.md index e687956a73..634900e3c7 100644 --- a/src/Avalonia.Themes.Fluent2/README.md +++ b/src/Avalonia.Themes.Fluent2/README.md @@ -48,6 +48,12 @@ Migrating from `Avalonia.Themes.Fluent`: change the package reference and swap | Scroll bars | 16 px rail | **12 px rail, 2 px collapsed thumb** | | Window background | AltHigh | **SolidBackgroundFillColorBase** (#F3F3F3/#202020) | | Default accent shades | HSL-computed | **WinUI static values** (when no OS accent) | +| OS accent shades (Windows) | HSL-computed | **read from the OS** (`UISettings` Light1–3/Dark1–3) | + +One deliberate deviation from WinUI (following FluentAvalonia's lead): CheckBox +and RadioButton do **not** get WinUI's forced 120 px `MinWidth` — they size to +content plus 8 px trailing padding, as in v1. Set the `CheckBoxMinWidth` / +`RadioButtonMinWidth` resources to restore the WinUI metric. The full WinUI 3 token family (`TextFillColor*`, `ControlFillColor*`, `SubtleFillColor*`, `ControlStrokeColor*`, `CardBackgroundFillColor*`, @@ -64,8 +70,10 @@ legacy `SystemControl*` aliases. - No HighContrast variant yet (same as v1). - AnimatedIcon glyph animations are approximated with static glyphs and simple transitions. -- OS-provided accent colors still use HSL-computed shades, which can deviate - slightly from Windows' palette algorithm. +- On Windows the six accent shades (`SystemAccentColorDark1`…`Light3`) come + straight from the OS; on platforms that report only a base accent color + (macOS, Linux) the shades are HSL-computed and can deviate slightly from + Windows' palette algorithm. ## Compatibility tests diff --git a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs index c4b016c1b9..b1395bd510 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs @@ -73,8 +73,14 @@ internal class Win32PlatformSettings : DefaultPlatformSettings PlatformThemeVariant.Dark : PlatformThemeVariant.Light, ContrastPreference = ColorContrastPreference.NoPreference, - AccentColor1 = accent - }; + AccentColor1 = accent, + AccentColorDark1 = uiSettings.GetColorValue(UIColorType.AccentDark1).ToAvalonia(), + AccentColorDark2 = uiSettings.GetColorValue(UIColorType.AccentDark2).ToAvalonia(), + AccentColorDark3 = uiSettings.GetColorValue(UIColorType.AccentDark3).ToAvalonia(), + AccentColorLight1 = uiSettings.GetColorValue(UIColorType.AccentLight1).ToAvalonia(), + AccentColorLight2 = uiSettings.GetColorValue(UIColorType.AccentLight2).ToAvalonia(), + AccentColorLight3 = uiSettings.GetColorValue(UIColorType.AccentLight3).ToAvalonia() + }; } } diff --git a/tests/Avalonia.Themes.Fluent2.UnitTests/ControlDefaultsTests.cs b/tests/Avalonia.Themes.Fluent2.UnitTests/ControlDefaultsTests.cs new file mode 100644 index 0000000000..53b8fb865b --- /dev/null +++ b/tests/Avalonia.Themes.Fluent2.UnitTests/ControlDefaultsTests.cs @@ -0,0 +1,50 @@ +using Avalonia.Controls; +using Avalonia.Threading; + +namespace Avalonia.Themes.Fluent2.UnitTests; + +/// +/// Guards the deliberate desktop-ergonomic deviations from the WinUI metrics. +/// +public class ControlDefaultsTests +{ + [AvaloniaFact] + public void CheckBox_and_RadioButton_do_not_force_winui_min_width() + { + var theme = new Fluent2Theme(); + var app = Application.Current!; + app.Styles.Add(theme); + try + { + var checkBox = new CheckBox { Content = "Ok" }; + var radioButton = new RadioButton { Content = "Ok" }; + var window = new Window + { + Width = 400, + Height = 300, + Content = new StackPanel { Children = { checkBox, radioButton } }, + }; + window.Show(); + try + { + Dispatcher.UIThread.RunJobs(); + + // WinUI forces MinWidth=120 on both; v1 never did, and neither does Fluent2. + Assert.Equal(0d, checkBox.MinWidth); + Assert.Equal(0d, radioButton.MinWidth); + Assert.True(checkBox.Bounds.Width < 120, + $"CheckBox width {checkBox.Bounds.Width} should hug its content."); + Assert.True(radioButton.Bounds.Width < 120, + $"RadioButton width {radioButton.Bounds.Width} should hug its content."); + } + finally + { + window.Close(); + } + } + finally + { + app.Styles.Remove(theme); + } + } +} diff --git a/tests/Avalonia.Themes.Fluent2.UnitTests/PaletteTests.cs b/tests/Avalonia.Themes.Fluent2.UnitTests/PaletteTests.cs index 1acfdc0a29..27d04d2cae 100644 --- a/tests/Avalonia.Themes.Fluent2.UnitTests/PaletteTests.cs +++ b/tests/Avalonia.Themes.Fluent2.UnitTests/PaletteTests.cs @@ -1,5 +1,6 @@ using Avalonia.Controls; using Avalonia.Media; +using Avalonia.Platform; using Avalonia.Styling; namespace Avalonia.Themes.Fluent2.UnitTests; @@ -65,6 +66,48 @@ public class PaletteTests } } + [AvaloniaFact] + public void Platform_provided_accent_shades_take_precedence_over_computed_ones() + { + var originalSettings = AvaloniaLocator.Current.GetService(); + Assert.NotNull(originalSettings); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new ShadeReportingPlatformSettings()); + + var theme = new Fluent2Theme(); + var app = Application.Current!; + app.Styles.Add(theme); + try + { + Assert.True(app.TryGetResource("SystemAccentColor", ThemeVariant.Light, out var accent)); + Assert.Equal(Colors.Crimson, Assert.IsType(accent)); + + // The OS-reported shades are used verbatim, not HSL-computed from the base color. + Assert.True(app.TryGetResource("SystemAccentColorDark1", ThemeVariant.Light, out var dark1)); + Assert.Equal(Colors.DarkRed, Assert.IsType(dark1)); + Assert.True(app.TryGetResource("SystemAccentColorLight3", ThemeVariant.Light, out var light3)); + Assert.Equal(Colors.MistyRose, Assert.IsType(light3)); + } + finally + { + app.Styles.Remove(theme); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(originalSettings); + } + } + + private sealed class ShadeReportingPlatformSettings : DefaultPlatformSettings + { + public override PlatformColorValues GetColorValues() => new() + { + AccentColor1 = Colors.Crimson, + AccentColorDark1 = Colors.DarkRed, + AccentColorDark2 = Colors.Maroon, + AccentColorDark3 = Colors.Brown, + AccentColorLight1 = Colors.Salmon, + AccentColorLight2 = Colors.LightSalmon, + AccentColorLight3 = Colors.MistyRose, + }; + } + [AvaloniaFact] public void Legacy_palette_colors_derive_fluent2_tokens() {