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.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/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/BaseColorsPalette.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml new file mode 100644 index 0000000000..362d543646 --- /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 78% rename from src/Avalonia.Themes.Fluent/Accents/Base.xaml rename to src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml index c19a4f5c09..517a80fd7e 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/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 @@ - - + + 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/ColorPaletteResources.Properties.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs new file mode 100644 index 0000000000..366af8e227 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs @@ -0,0 +1,158 @@ +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); } + + /// + /// Gets or sets the RegionColor color value. + /// + public Color RegionColor { get => GetColor("SystemRegionColor"); set => SetColor("SystemRegionColor", 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/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 @@ - + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml index e83257fd9f..0528c40c21 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); 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..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,7 +24,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude; var mergeSourceNodes = new List(); - var hasAnyNonMergedResource = false; + var mergedResourceWasAdded = false; foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray()) { void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode) @@ -38,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 { @@ -47,15 +48,10 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer valueNode); } } - else - { - hasAnyNonMergedResource = true; - } - - if (hasAnyNonMergedResource && mergeSourceNodes.Any()) + else if (mergeSourceNodes.Any()) { throw new XamlDocumentParseException(context.CurrentDocument, - "Mix of MergeResourceInclude and other dictionaries inside of the ResourceDictionary.MergedDictionaries is not allowed", + "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() {