From a09c182e89245160b64677f369216d56711faeb9 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 21 Apr 2023 22:37:16 -0400 Subject: [PATCH 1/6] Implement SystemAccentColors backed by the actual system values --- samples/ControlCatalog/App.xaml | 2 - samples/ControlCatalog/MainView.xaml.cs | 48 ------ .../Accents/AccentColors.xaml | 12 -- .../Accents/SystemAccentColors.cs | 163 ++++++++++++++++++ src/Avalonia.Themes.Fluent/FluentTheme.xaml | 2 +- .../XamlMergeResourceGroupTransformer.cs | 12 -- 6 files changed, 164 insertions(+), 75 deletions(-) delete mode 100644 src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml create mode 100644 src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 3b847adcbb..64bf3e53b3 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -26,8 +26,6 @@ #FFFFFFFF - #FF0078D7 - #FF005A9E diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 9c439c874f..9c511f9eb0 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -19,13 +19,9 @@ namespace ControlCatalog { public class MainView : UserControl { - private readonly IPlatformSettings _platformSettings; - public MainView() { AvaloniaXamlLoader.Load(this); - _platformSettings = AvaloniaLocator.Current.GetRequiredService(); - PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues()); var sideBar = this.Get("Sidebar"); @@ -141,50 +137,6 @@ namespace ControlCatalog ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true; }; } - - _platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged; - PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues()); - } - - protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) - { - base.OnDetachedFromLogicalTree(e); - - _platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged; - } - - private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e) - { - Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1; - Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3); - Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5); - Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7); - Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, 0.3); - Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, 0.5); - Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, 0.7); - - static Color ChangeColorLuminosity(Color color, double luminosityFactor) - { - var red = (double)color.R; - var green = (double)color.G; - var blue = (double)color.B; - - if (luminosityFactor < 0) - { - luminosityFactor = 1 + luminosityFactor; - red *= luminosityFactor; - green *= luminosityFactor; - blue *= luminosityFactor; - } - else if (luminosityFactor >= 0) - { - red = (255 - red) * luminosityFactor + red; - green = (255 - green) * luminosityFactor + green; - blue = (255 - blue) * luminosityFactor + blue; - } - - return new Color(color.A, (byte)red, (byte)green, (byte)blue); - } } } } diff --git a/src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml b/src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml deleted file mode 100644 index 0fb3ab73c2..0000000000 --- a/src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - - #FF0078D7 - #FF005A9E - #FF004275 - #FF002642 - #FF429CE3 - #FF76B9ED - #FFA6D8FF - diff --git a/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs b/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs new file mode 100644 index 0000000000..a4ef15f950 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs @@ -0,0 +1,163 @@ +using System; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Styling; + +namespace Avalonia.Themes.Fluent.Accents; + +internal class SystemAccentColors : IResourceProvider +{ + public const string AccentKey = "SystemAccentColor"; + public const string AccentDark1Key = "SystemAccentColorDark1"; + public const string AccentDark2Key = "SystemAccentColorDark2"; + public const string AccentDark3Key = "SystemAccentColorDark3"; + public const string AccentLight1Key = "SystemAccentColorLight1"; + public const string AccentLight2Key = "SystemAccentColorLight2"; + public const string AccentLight3Key = "SystemAccentColorLight3"; + + private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215); + private readonly IPlatformSettings? _platformSettings; + private bool _invalidateColors = true; + private Color _systemAccentColor; + private Color _systemAccentColorDark1, _systemAccentColorDark2, _systemAccentColorDark3; + private Color _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3; + + public SystemAccentColors() + { + _platformSettings = AvaloniaLocator.Current.GetService(); + } + + public bool HasResources => true; + public bool TryGetResource(object key, ThemeVariant? theme, out object? value) + { + if (key is string strKey) + { + if (strKey.Equals(AccentKey, StringComparison.InvariantCulture)) + { + EnsureColors(); + value = _systemAccentColor; + return true; + } + + if (strKey.Equals(AccentDark1Key, StringComparison.InvariantCulture)) + { + EnsureColors(); + value = _systemAccentColorDark1; + return true; + } + + if (strKey.Equals(AccentDark2Key, StringComparison.InvariantCulture)) + { + EnsureColors(); + value = _systemAccentColorDark2; + return true; + } + + if (strKey.Equals(AccentDark3Key, StringComparison.InvariantCulture)) + { + EnsureColors(); + value = _systemAccentColorDark3; + return true; + } + + if (strKey.Equals(AccentLight1Key, StringComparison.InvariantCulture)) + { + EnsureColors(); + value = _systemAccentColorLight1; + return true; + } + + if (strKey.Equals(AccentLight2Key, StringComparison.InvariantCulture)) + { + EnsureColors(); + value = _systemAccentColorLight2; + return true; + } + + if (strKey.Equals(AccentLight3Key, StringComparison.InvariantCulture)) + { + EnsureColors(); + value = _systemAccentColorLight3; + return true; + } + } + + value = null; + return false; + } + + public IResourceHost? Owner { get; private set; } + public event EventHandler? OwnerChanged; + public void AddOwner(IResourceHost owner) + { + if (Owner != owner) + { + Owner = owner; + OwnerChanged?.Invoke(this, EventArgs.Empty); + + if (_platformSettings is not null) + { + _platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged; + } + } + } + + public void RemoveOwner(IResourceHost owner) + { + if (Owner == owner) + { + Owner = null; + OwnerChanged?.Invoke(this, EventArgs.Empty); + + if (_platformSettings is not null) + { + _platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged; + } + } + } + + private void EnsureColors() + { + if (_invalidateColors) + { + _invalidateColors = false; + + _systemAccentColor = _platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor; + (_systemAccentColorDark1,_systemAccentColorDark2, _systemAccentColorDark3, + _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3) = CalculateAccentShades(_systemAccentColor); + } + } + + public static (Color d1, Color d2, Color d3, Color l1, Color l2, Color l3) CalculateAccentShades(Color accentColor) + { + // dark1step = (hslAccent.L - SystemAccentColorDark1.L) * 255 + const double dark1step = 28.5 / 255d; + const double dark2step = 49 / 255d; + const double dark3step = 74.5 / 255d; + // light1step = (SystemAccentColorLight1.L - hslAccent.L) * 255 + const double light1step = 39 / 255d; + const double light2step = 70 / 255d; + const double light3step = 103 / 255d; + + var hslAccent = accentColor.ToHsl(); + + return ( + // Darker shades + new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark1step).ToRgb(), + new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark2step).ToRgb(), + new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark3step).ToRgb(), + + // Lighter shades + new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light1step).ToRgb(), + new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light2step).ToRgb(), + new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light3step).ToRgb() + ); + } + + private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e) + { + _invalidateColors = true; + Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); + } +} diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml index e83257fd9f..9154505c28 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml @@ -4,9 +4,9 @@ - + diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs index db8d604154..7d68979c26 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs @@ -24,7 +24,6 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude; var mergeSourceNodes = new List(); - var hasAnyNonMergedResource = false; foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray()) { void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode) @@ -47,17 +46,6 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer valueNode); } } - else - { - hasAnyNonMergedResource = true; - } - - if (hasAnyNonMergedResource && mergeSourceNodes.Any()) - { - throw new XamlDocumentParseException(context.CurrentDocument, - "Mix of MergeResourceInclude and other dictionaries inside of the ResourceDictionary.MergedDictionaries is not allowed", - valueNode); - } } } From c9b1ed8f51f41dea62021f7b4b689ec21f149e37 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 21 Apr 2023 22:37:27 -0400 Subject: [PATCH 2/6] Implement ColorPaletteResources as a public API --- .../AvaloniaDictionaryExtensions.cs | 2 +- .../Accents/BaseColorsPalette.xaml | 69 ++++++++ .../Accents/{Base.xaml => BaseResources.xaml} | 154 +++--------------- .../ColorPaletteResources.Properties.cs | 153 +++++++++++++++++ .../ColorPaletteResources.cs | 118 ++++++++++++++ .../ColorPaletteResourcesCollection.cs | 65 ++++++++ src/Avalonia.Themes.Fluent/FluentTheme.xaml | 12 +- .../FluentTheme.xaml.cs | 6 + 8 files changed, 447 insertions(+), 132 deletions(-) create mode 100644 src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml rename src/Avalonia.Themes.Fluent/Accents/{Base.xaml => BaseResources.xaml} (79%) create mode 100644 src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs create mode 100644 src/Avalonia.Themes.Fluent/ColorPaletteResources.cs create mode 100644 src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs index e350a019d4..8c731c188f 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs @@ -35,7 +35,7 @@ namespace Avalonia.Collections /// Indicates if a weak subscription should be used to track changes to the collection. /// /// A disposable used to terminate the subscription. - internal static IDisposable ForEachItem( + public static IDisposable ForEachItem( this IAvaloniaReadOnlyDictionary collection, Action added, Action removed, diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml new file mode 100644 index 0000000000..b8b5fcf1f4 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml @@ -0,0 +1,69 @@ + + + + + #FFFFFFFF + #33FFFFFF + #99FFFFFF + #CCFFFFFF + #66FFFFFF + #FF000000 + #33000000 + #99000000 + #CC000000 + #66000000 + #FF171717 + #FF000000 + #33000000 + #66000000 + #CC000000 + #FFCCCCCC + #FF7A7A7A + #FFCCCCCC + #FFF2F2F2 + #FFE6E6E6 + #FFF2F2F2 + #FFFFFFFF + #FF767676 + #19000000 + #33000000 + #C50500 + #FFFFFFFF + #17000000 + #2E000000 + + + #FF000000 + #33000000 + #99000000 + #CC000000 + #66000000 + #FFFFFFFF + #33FFFFFF + #99FFFFFF + #CCFFFFFF + #66FFFFFF + #FFF2F2F2 + #FF000000 + #33000000 + #66000000 + #CC000000 + #FF333333 + #FF858585 + #FF767676 + #FF171717 + #FF1F1F1F + #FF2B2B2B + #FFFFFFFF + #FF767676 + #19FFFFFF + #33FFFFFF + #FFF000 + #FF000000 + #18FFFFFF + #30FFFFFF + + + + diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml similarity index 79% rename from src/Avalonia.Themes.Fluent/Accents/Base.xaml rename to src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml index c19a4f5c09..c1f79e45d5 100644 --- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml @@ -2,7 +2,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="using:System" xmlns:converters="using:Avalonia.Controls.Converters"> - fonts:Inter#Inter, $Default 14 @@ -28,39 +27,33 @@ - - - - #FFFFFFFF - #33FFFFFF - #99FFFFFF - #CCFFFFFF - #66FFFFFF - #FF000000 - #33000000 - #99000000 - #CC000000 - #66000000 - #FF171717 - #FF000000 - #33000000 - #66000000 - #CC000000 - #FFCCCCCC - #FF7A7A7A - #FFCCCCCC - #FFF2F2F2 - #FFE6E6E6 - #FFF2F2F2 - #FFFFFFFF - #FF767676 - #19000000 - #33000000 - #C50500 + 374 + 0,2,0,2 + 1 + -1,0,-1,0 + 32 + 64 + 456 + 0 + 1 + 0 + + 12,11,12,12 + 96 + 40 + 758 - #17000000 - #2E000000 + + 0 + + 0,4,0,4 + + + 12,0,12,0 + + + - - - - - - - - #FFFFFFFF - - - 374 - 0,2,0,2 - 1 - -1,0,-1,0 - 32 - 64 - 456 - 0 - 1 - 0 - - 12,11,12,12 - 96 - 40 - 758 - - - 0 - - - 0,4,0,4 - - - 12,0,12,0 - - #FF000000 - #33000000 - #99000000 - #CC000000 - #66000000 - #FFFFFFFF - #33FFFFFF - #99FFFFFF - #CCFFFFFF - #66FFFFFF - #FFF2F2F2 - #FF000000 - #33000000 - #66000000 - #CC000000 - #FF333333 - #FF858585 - #FF767676 - #FF171717 - #FF1F1F1F - #FF2B2B2B - #FFFFFFFF - #FF767676 - #19FFFFFF - #33FFFFFF - #FFF000 - - #18FFFFFF - #30FFFFFF - - - - - - - - #FF000000 - - 374 - 0,2,0,2 - 1 - -1,0,-1,0 - 32 - 64 - 456 - 0 - 1 - 0 - - 12,11,12,12 - 96 - 40 - 758 - - - 0 - - - 0,4,0,4 - - - 12,0,12,0 diff --git a/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs new file mode 100644 index 0000000000..6d284150e4 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs @@ -0,0 +1,153 @@ +using Avalonia.Media; + +namespace Avalonia.Themes.Fluent; + +public partial class ColorPaletteResources +{ + private bool _hasAccentColor; + private Color _accentColor; + private Color _accentColorDark1, _accentColorDark2, _accentColorDark3; + private Color _accentColorLight1, _accentColorLight2, _accentColorLight3; + + public static readonly DirectProperty AccentProperty + = AvaloniaProperty.RegisterDirect(nameof(Accent), r => r.Accent, (r, v) => r.Accent = v); + + /// + /// Gets or sets the Accent color value. + /// + public Color Accent + { + get => _accentColor; + set => SetAndRaise(AccentProperty, ref _accentColor, value); + } + + /// + /// Gets or sets the AltHigh color value. + /// + public Color AltHigh { get => GetColor("SystemAltHighColor"); set => SetColor("SystemAltHighColor", value); } + + /// + /// Gets or sets the AltLow color value. + /// + public Color AltLow { get => GetColor("SystemAltLowColor"); set => SetColor("SystemAltLowColor", value); } + + /// + /// Gets or sets the AltMedium color value. + /// + public Color AltMedium { get => GetColor("SystemAltMediumColor"); set => SetColor("SystemAltMediumColor", value); } + + /// + /// Gets or sets the AltMediumHigh color value. + /// + public Color AltMediumHigh { get => GetColor("SystemAltMediumHighColor"); set => SetColor("SystemAltMediumHighColor", value); } + + /// + /// Gets or sets the AltMediumLow color value. + /// + public Color AltMediumLow { get => GetColor("SystemAltMediumLowColor"); set => SetColor("SystemAltMediumLowColor", value); } + + /// + /// Gets or sets the BaseHigh color value. + /// + public Color BaseHigh { get => GetColor("SystemBaseHighColor"); set => SetColor("SystemBaseHighColor", value); } + + /// + /// Gets or sets the BaseLow color value. + /// + public Color BaseLow { get => GetColor("SystemBaseLowColor"); set => SetColor("SystemBaseLowColor", value); } + + /// + /// Gets or sets the BaseMedium color value. + /// + public Color BaseMedium { get => GetColor("SystemBaseMediumColor"); set => SetColor("SystemBaseMediumColor", value); } + + /// + /// Gets or sets the BaseMediumHigh color value. + /// + public Color BaseMediumHigh { get => GetColor("SystemBaseMediumHighColor"); set => SetColor("SystemBaseMediumHighColor", value); } + + /// + /// Gets or sets the BaseMediumLow color value. + /// + public Color BaseMediumLow { get => GetColor("SystemBaseMediumLowColor"); set => SetColor("SystemBaseMediumLowColor", value); } + + /// + /// Gets or sets the ChromeAltLow color value. + /// + public Color ChromeAltLow { get => GetColor("SystemChromeAltLowColor"); set => SetColor("SystemChromeAltLowColor", value); } + + /// + /// Gets or sets the ChromeBlackHigh color value. + /// + public Color ChromeBlackHigh { get => GetColor("SystemChromeBlackHighColor"); set => SetColor("SystemChromeBlackHighColor", value); } + + /// + /// Gets or sets the ChromeBlackLow color value. + /// + public Color ChromeBlackLow { get => GetColor("SystemChromeBlackLowColor"); set => SetColor("SystemChromeBlackLowColor", value); } + + /// + /// Gets or sets the ChromeBlackMedium color value. + /// + public Color ChromeBlackMedium { get => GetColor("SystemChromeBlackMediumColor"); set => SetColor("SystemChromeBlackMediumColor", value); } + + /// + /// Gets or sets the ChromeBlackMediumLow color value. + /// + public Color ChromeBlackMediumLow { get => GetColor("SystemChromeBlackMediumLowColor"); set => SetColor("SystemChromeBlackMediumLowColor", value); } + + /// + /// Gets or sets the ChromeDisabledHigh color value. + /// + public Color ChromeDisabledHigh { get => GetColor("SystemChromeDisabledHighColor"); set => SetColor("SystemChromeDisabledHighColor", value); } + + /// + /// Gets or sets the ChromeDisabledLow color value. + /// + public Color ChromeDisabledLow { get => GetColor("SystemChromeDisabledLowColor"); set => SetColor("SystemChromeDisabledLowColor", value); } + + /// + /// Gets or sets the ChromeGray color value. + /// + public Color ChromeGray { get => GetColor("SystemChromeGrayColor"); set => SetColor("SystemChromeGrayColor", value); } + + /// + /// Gets or sets the ChromeHigh color value. + /// + public Color ChromeHigh { get => GetColor("SystemChromeHighColor"); set => SetColor("SystemChromeHighColor", value); } + + /// + /// Gets or sets the ChromeLow color value. + /// + public Color ChromeLow { get => GetColor("SystemChromeLowColor"); set => SetColor("SystemChromeLowColor", value); } + + /// + /// Gets or sets the ChromeMedium color value. + /// + public Color ChromeMedium { get => GetColor("SystemChromeMediumColor"); set => SetColor("SystemChromeMediumColor", value); } + + /// + /// Gets or sets the ChromeMediumLow color value. + /// + public Color ChromeMediumLow { get => GetColor("SystemChromeMediumLowColor"); set => SetColor("SystemChromeMediumLowColor", value); } + + /// + /// Gets or sets the ChromeWhite color value. + /// + public Color ChromeWhite { get => GetColor("SystemChromeWhiteColor"); set => SetColor("SystemChromeWhiteColor", value); } + + /// + /// Gets or sets the ErrorText color value. + /// + public Color ErrorText { get => GetColor("SystemErrorTextColor"); set => SetColor("SystemErrorTextColor", value); } + + /// + /// Gets or sets the ListLow color value. + /// + public Color ListLow { get => GetColor("SystemListLowColor"); set => SetColor("SystemListLowColor", value); } + + /// + /// Gets or sets the ListMedium color value. + /// + public Color ListMedium { get => GetColor("SystemListMediumColor"); set => SetColor("SystemListMediumColor", value); } +} diff --git a/src/Avalonia.Themes.Fluent/ColorPaletteResources.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResources.cs new file mode 100644 index 0000000000..ce52f51752 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/ColorPaletteResources.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Styling; +using Avalonia.Themes.Fluent.Accents; + +namespace Avalonia.Themes.Fluent; + +/// +/// Represents a specialized resource dictionary that contains color resources used by FluentTheme elements. +/// +/// +/// This class can only be used in . +/// +public partial class ColorPaletteResources : AvaloniaObject, IResourceNode +{ + private readonly Dictionary _colors = new(StringComparer.InvariantCulture); + + public bool HasResources => _hasAccentColor || _colors.Count > 0; + + public bool TryGetResource(object key, ThemeVariant? theme, out object? value) + { + if (key is string strKey) + { + if (strKey.Equals(SystemAccentColors.AccentKey, StringComparison.InvariantCulture)) + { + value = _accentColor; + return _hasAccentColor; + } + + if (strKey.Equals(SystemAccentColors.AccentDark1Key, StringComparison.InvariantCulture)) + { + value = _accentColorDark1; + return _hasAccentColor; + } + + if (strKey.Equals(SystemAccentColors.AccentDark2Key, StringComparison.InvariantCulture)) + { + value = _accentColorDark2; + return _hasAccentColor; + } + + if (strKey.Equals(SystemAccentColors.AccentDark3Key, StringComparison.InvariantCulture)) + { + value = _accentColorDark3; + return _hasAccentColor; + } + + if (strKey.Equals(SystemAccentColors.AccentLight1Key, StringComparison.InvariantCulture)) + { + value = _accentColorLight1; + return _hasAccentColor; + } + + if (strKey.Equals(SystemAccentColors.AccentLight2Key, StringComparison.InvariantCulture)) + { + value = _accentColorLight2; + return _hasAccentColor; + } + + if (strKey.Equals(SystemAccentColors.AccentLight3Key, StringComparison.InvariantCulture)) + { + value = _accentColorLight3; + return _hasAccentColor; + } + + if (_colors.TryGetValue(strKey, out var color)) + { + value = color; + return true; + } + } + + value = null; + return false; + } + + private Color GetColor(string key) + { + if (_colors.TryGetValue(key, out var color)) + { + return color; + } + + return default; + } + + private void SetColor(string key, Color value) + { + if (value == default) + { + _colors.Remove(key); + } + else + { + _colors[key] = value; + } + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == AccentProperty) + { + _hasAccentColor = _accentColor != default; + + if (_hasAccentColor) + { + (_accentColorDark1, _accentColorDark2, _accentColorDark3, + _accentColorLight1, _accentColorLight2, _accentColorLight3) = + SystemAccentColors.CalculateAccentShades(_accentColor); + } + } + } +} diff --git a/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs new file mode 100644 index 0000000000..261de5497d --- /dev/null +++ b/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs @@ -0,0 +1,65 @@ +using System; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Styling; + +namespace Avalonia.Themes.Fluent; + +internal class ColorPaletteResourcesCollection : AvaloniaDictionary, IResourceProvider +{ + public ColorPaletteResourcesCollection() : base(2) + { + this.ForEachItem( + (_, x) => + { + if (Owner is not null) + { + x.PropertyChanged += Palette_PropertyChanged; + } + }, + (_, x) => + { + if (Owner is not null) + { + x.PropertyChanged -= Palette_PropertyChanged; + } + }, + () => throw new NotSupportedException("Dictionary reset not supported")); + } + + public bool HasResources => Count > 0; + public bool TryGetResource(object key, ThemeVariant? theme, out object? value) + { + theme ??= ThemeVariant.Default; + if (base.TryGetValue(theme, out var paletteResources) + && paletteResources.TryGetResource(key, theme, out value)) + { + return true; + } + + value = null; + return false; + } + + public IResourceHost? Owner { get; private set; } + public event EventHandler? OwnerChanged; + public void AddOwner(IResourceHost owner) + { + Owner = owner; + OwnerChanged?.Invoke(this, EventArgs.Empty); + } + + public void RemoveOwner(IResourceHost owner) + { + Owner = null; + OwnerChanged?.Invoke(this, EventArgs.Empty); + } + + private void Palette_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == ColorPaletteResources.AccentProperty) + { + Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); + } + } +} diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml index 9154505c28..8f3c96d96a 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml @@ -1,11 +1,19 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:fluent="using:Avalonia.Themes.Fluent" + xmlns:accents="clr-namespace:Avalonia.Themes.Fluent.Accents"> - + + + + + + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs index 95539bc08a..5af22dbd1d 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Styling; @@ -31,6 +32,9 @@ namespace Avalonia.Themes.Fluent EnsureCompactStyles(); + Palettes = Resources.MergedDictionaries.OfType().FirstOrDefault() + ?? throw new InvalidOperationException("FluentTheme was initialized with missing ColorPaletteResourcesCollection."); + object GetAndRemove(string key) { var val = Resources[key] @@ -52,6 +56,8 @@ namespace Avalonia.Themes.Fluent set => SetValue(DensityStyleProperty, value); } + public IDictionary Palettes { get; } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); From 1aba81a27f1f568d41c948676ac3580c58cb6a5b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 22 Apr 2023 22:41:45 -0400 Subject: [PATCH 3/6] Fix non dynamic accent brushes --- .../Accents/FluentControlResources.xaml | 114 +++++++++++------- 1 file changed, 69 insertions(+), 45 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml index a9bc622221..61a74f26a4 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml @@ -4,8 +4,8 @@ - - + + @@ -52,7 +52,8 @@ - + @@ -291,15 +292,17 @@ ResourceKey="SystemControlHighlightBaseHighBrush" /> - + - - + + @@ -309,13 +312,17 @@ ResourceKey="SystemControlBackgroundBaseMediumLowBrush" /> - - + + - - + + - - + + - - + + @@ -470,8 +481,8 @@ - - + + @@ -502,8 +513,8 @@ - - + + @@ -701,8 +712,9 @@ - - + + @@ -775,8 +787,8 @@ - - + + @@ -823,7 +835,8 @@ - + @@ -1065,14 +1078,17 @@ ResourceKey="SystemControlHighlightBaseHighBrush" /> - - + + - - + + @@ -1082,13 +1098,17 @@ ResourceKey="SystemControlBackgroundBaseMediumLowBrush" /> - - + + - - + + - - + + - - + + @@ -1243,8 +1267,8 @@ - - + + @@ -1275,12 +1299,12 @@ - - + + - - + + @@ -1476,8 +1500,8 @@ - - + + From bbbc1280ff4f671073ebd033133cea9f62891579 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 23 Apr 2023 00:44:48 -0400 Subject: [PATCH 4/6] Rename RegionBrush to SystemRegionColor and use it in default templates --- src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml | 4 ++-- src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml | 4 ++-- .../ColorPaletteResources.Properties.cs | 5 +++++ .../Controls/EmbeddableControlRoot.xaml | 2 +- src/Avalonia.Themes.Fluent/Controls/Window.xaml | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml index b8b5fcf1f4..362d543646 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml @@ -29,7 +29,7 @@ #19000000 #33000000 #C50500 - #FFFFFFFF + #FFFFFFFF #17000000 #2E000000 @@ -60,7 +60,7 @@ #19FFFFFF #33FFFFFF #FFF000 - #FF000000 + #FF000000 #18FFFFFF #30FFFFFF diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml index c1f79e45d5..517a80fd7e 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml @@ -212,7 +212,7 @@ - + @@ -372,7 +372,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs index 6d284150e4..366af8e227 100644 --- a/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs +++ b/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs @@ -150,4 +150,9 @@ public partial class ColorPaletteResources /// Gets or sets the ListMedium color value. /// public Color ListMedium { get => GetColor("SystemListMediumColor"); set => SetColor("SystemListMediumColor", value); } + + /// + /// Gets or sets the RegionColor color value. + /// + public Color RegionColor { get => GetColor("SystemRegionColor"); set => SetColor("SystemRegionColor", value); } } diff --git a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml index f60424a2dc..ee51ef8085 100644 --- a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - + diff --git a/src/Avalonia.Themes.Fluent/Controls/Window.xaml b/src/Avalonia.Themes.Fluent/Controls/Window.xaml index ff27cce800..8db01fa4c8 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Window.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Window.xaml @@ -1,7 +1,7 @@ - + From 753b821035769f054c5e03717a57a7c69abc856f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 23 Apr 2023 00:45:03 -0400 Subject: [PATCH 5/6] Fix merged dictionaries ordering --- src/Avalonia.Themes.Fluent/FluentTheme.xaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml index 8f3c96d96a..0528c40c21 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml @@ -6,15 +6,15 @@ - - - - - - + + + + + + From b8ecad2cbcfef7141d46dc6be6219ba960ca5e5b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 24 Apr 2023 02:42:46 -0400 Subject: [PATCH 6/6] Restore resource include validation --- .../XamlMergeResourceGroupTransformer.cs | 10 ++++++- .../Xaml/MergeResourceIncludeTests.cs | 28 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs index 7d68979c26..8e04a7d467 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs @@ -24,6 +24,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude; var mergeSourceNodes = new List(); + var mergedResourceWasAdded = false; foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray()) { void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode) @@ -37,7 +38,8 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer && objectInitialization.Manipulation is XamlPropertyAssignmentNode sourceAssignmentNode) { parent.Children.Remove(assignmentNode); - mergeSourceNodes.Add(sourceAssignmentNode); + mergeSourceNodes.Add(sourceAssignmentNode); + mergedResourceWasAdded = true; } else { @@ -46,6 +48,12 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer valueNode); } } + else if (mergeSourceNodes.Any()) + { + throw new XamlDocumentParseException(context.CurrentDocument, + "MergeResourceInclude should always be included last when mixing with other dictionaries inside of the ResourceDictionary.MergedDictionaries.", + valueNode); + } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs index aa76756069..d6f554cdfe 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs @@ -83,6 +83,34 @@ public class MergeResourceIncludeTests Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.LoadGroup(documents)); } + [Fact] + public void MergeResourceInclude_Is_Allowed_After_ResourceInclude() + { + var documents = new[] + { + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @" + + Red +"), + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @" + + Blue +"), + new RuntimeXamlLoaderDocument(@" + + + + + +") + }; + + AvaloniaRuntimeXamlLoader.LoadGroup(documents); + } + [Fact] public void MergeResourceInclude_Works_With_Multiple_Resources() {