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()
{