Browse Source

Fluent2: derive WinUI tokens from legacy palette colors

ColorPaletteResources now answers for the nearest Fluent 2 token colors when
legacy palette colors are set (RegionColor -> SolidBackgroundFillColor*,
BaseHigh/MediumHigh/Medium -> TextFillColor* with per-variant token alphas,
BaseLow -> ControlFillColor*, ErrorText -> SystemFillColorCritical,
ChromeMedium -> SolidBackgroundFillColorQuarternary), so v1-era palette
customization keeps driving the new visuals. Heuristic by design; exact
control is overriding the token keys directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/21771/head
Mike James 3 months ago
parent
commit
b75afd6d91
  1. 49
      src/Avalonia.Themes.Fluent2/ColorPaletteResources.cs
  2. 35
      tests/Avalonia.Themes.Fluent2.UnitTests/PaletteTests.cs

49
src/Avalonia.Themes.Fluent2/ColorPaletteResources.cs

@ -18,6 +18,31 @@ public partial class ColorPaletteResources : ResourceProvider
{
private readonly Dictionary<string, Color> _colors = new(StringComparer.InvariantCulture);
private readonly record struct TokenDerivation(string SourceKey, byte? LightAlpha, byte? DarkAlpha);
// Legacy palette colors also drive the nearest Fluent 2 (WinUI 3) tokens so that
// v1-era palette customization keeps affecting the new visuals. The token's own
// per-variant alpha is applied over the user's RGB; a null alpha keeps the user's.
// This is a documented heuristic — for exact control override the token keys directly.
private static readonly Dictionary<string, TokenDerivation[]> s_tokenDerivations = new(StringComparer.InvariantCulture)
{
["TextFillColorPrimary"] = new[] { new TokenDerivation("SystemBaseHighColor", 0xE4, 0xFF) },
["TextFillColorSecondary"] = new[] { new TokenDerivation("SystemBaseMediumHighColor", 0x9E, 0xC5) },
["TextFillColorTertiary"] = new[] { new TokenDerivation("SystemBaseMediumColor", 0x72, 0x87) },
["ControlFillColorDefault"] = new[] { new TokenDerivation("SystemBaseLowColor", 0xB3, 0x0F) },
["ControlFillColorSecondary"] = new[] { new TokenDerivation("SystemBaseLowColor", 0x80, 0x15) },
["ControlFillColorTertiary"] = new[] { new TokenDerivation("SystemBaseLowColor", 0x4D, 0x08) },
["SolidBackgroundFillColorBase"] = new[] { new TokenDerivation("SystemRegionColor", null, null) },
["SolidBackgroundFillColorSecondary"] = new[] { new TokenDerivation("SystemRegionColor", null, null) },
["SolidBackgroundFillColorTertiary"] = new[] { new TokenDerivation("SystemRegionColor", null, null) },
["SolidBackgroundFillColorQuarternary"] = new[]
{
new TokenDerivation("SystemChromeMediumColor", null, null),
new TokenDerivation("SystemRegionColor", null, null),
},
["SystemFillColorCritical"] = new[] { new TokenDerivation("SystemErrorTextColor", null, null) },
};
public override bool HasResources => _hasAccentColor || _colors.Count > 0;
public override bool TryGetResource(object key, ThemeVariant? theme, out object? value)
@ -71,6 +96,30 @@ public partial class ColorPaletteResources : ResourceProvider
value = color;
return true;
}
if (TryGetDerivedTokenColor(strKey, theme, out value))
{
return true;
}
}
value = null;
return false;
}
private bool TryGetDerivedTokenColor(string key, ThemeVariant? theme, out object? value)
{
if (s_tokenDerivations.TryGetValue(key, out var derivations))
{
foreach (var derivation in derivations)
{
if (_colors.TryGetValue(derivation.SourceKey, out var source))
{
var alpha = theme == ThemeVariant.Dark ? derivation.DarkAlpha : derivation.LightAlpha;
value = alpha is { } a ? new Color(a, source.R, source.G, source.B) : source;
return true;
}
}
}
value = null;

35
tests/Avalonia.Themes.Fluent2.UnitTests/PaletteTests.cs

@ -65,6 +65,41 @@ public class PaletteTests
}
}
[AvaloniaFact]
public void Legacy_palette_colors_derive_fluent2_tokens()
{
var theme = new Fluent2Theme();
theme.Palettes[ThemeVariant.Light] = new ColorPaletteResources
{
RegionColor = Color.Parse("#FFF0E0D0"),
BaseHigh = Color.Parse("#FF102030"),
ErrorText = Colors.Orange,
};
var app = Application.Current!;
app.Styles.Add(theme);
try
{
Assert.True(app.TryGetResource("SolidBackgroundFillColorBase", ThemeVariant.Light, out var region));
Assert.Equal(Color.Parse("#FFF0E0D0"), Assert.IsType<Color>(region));
// BaseHigh drives TextFillColorPrimary with the token's light-variant alpha (0xE4).
Assert.True(app.TryGetResource("TextFillColorPrimary", ThemeVariant.Light, out var textPrimary));
Assert.Equal(Color.FromArgb(0xE4, 0x10, 0x20, 0x30), Assert.IsType<Color>(textPrimary));
Assert.True(app.TryGetResource("SystemFillColorCritical", ThemeVariant.Light, out var critical));
Assert.Equal(Colors.Orange, Assert.IsType<Color>(critical));
// Legacy keys themselves still answer, as in v1.
Assert.True(app.TryGetResource("SystemBaseHighColor", ThemeVariant.Light, out var baseHigh));
Assert.Equal(Color.Parse("#FF102030"), Assert.IsType<Color>(baseHigh));
}
finally
{
app.Styles.Remove(theme);
}
}
[AvaloniaFact]
public void Compact_density_switches_metric_resources()
{

Loading…
Cancel
Save