Browse Source

Fix theme-dependend markup extensions not knowing current theme context

pull/11111/head
Max Katz 3 years ago
parent
commit
a507e92b31
  1. 2
      src/Avalonia.Base/Controls/IResourceDictionary.cs
  2. 22
      src/Avalonia.Base/Controls/IThemeVariantProvider.cs
  3. 12
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  4. 46
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  5. 1
      src/Avalonia.Base/Styling/IThemeVariantHost.cs
  6. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  7. 31
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlThemeVariantProviderTransformer.cs
  8. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  9. 6
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  10. 22
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  11. 4
      src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
  12. 22
      tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs
  13. 4
      tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
  14. 136
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs

2
src/Avalonia.Base/Controls/IResourceDictionary.cs

@ -18,6 +18,6 @@ namespace Avalonia.Controls
/// <summary>
/// Gets a collection of merged resource dictionaries that are specifically keyed and composed to address theme scenarios.
/// </summary>
IDictionary<ThemeVariant, IResourceProvider> ThemeDictionaries { get; }
IDictionary<ThemeVariant, IThemeVariantProvider> ThemeDictionaries { get; }
}
}

22
src/Avalonia.Base/Controls/IThemeVariantProvider.cs

@ -0,0 +1,22 @@
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Controls;
/// <summary>
/// Resource provider with theme variant awareness.
/// Can be used with <see cref="IResourceDictionary.ThemeDictionaries"/>.
/// </summary>
/// <remarks>
/// This is a helper interface for the XAML compiler to make Key property accessibly by the markup extensions.
/// Which means, it can only be used with ResourceDictionaries and markup extensions in the XAML code.
/// This API might be removed in the future minor updates.
/// </remarks>
[Unstable]
public interface IThemeVariantProvider : IResourceProvider
{
/// <summary>
/// Key property set by the compiler.
/// </summary>
ThemeVariant? Key { get; set; }
}

12
src/Avalonia.Base/Controls/ResourceDictionary.cs

@ -13,13 +13,13 @@ namespace Avalonia.Controls
/// <summary>
/// An indexed dictionary of resources.
/// </summary>
public class ResourceDictionary : IResourceDictionary
public class ResourceDictionary : IResourceDictionary, IThemeVariantProvider
{
private object? lastDeferredItemKey;
private Dictionary<object, object?>? _inner;
private IResourceHost? _owner;
private AvaloniaList<IResourceProvider>? _mergedDictionaries;
private AvaloniaDictionary<ThemeVariant, IResourceProvider>? _themeDictionary;
private AvaloniaDictionary<ThemeVariant, IThemeVariantProvider>? _themeDictionary;
/// <summary>
/// Initializes a new instance of the <see cref="ResourceDictionary"/> class.
@ -93,13 +93,13 @@ namespace Avalonia.Controls
}
}
public IDictionary<ThemeVariant, IResourceProvider> ThemeDictionaries
public IDictionary<ThemeVariant, IThemeVariantProvider> ThemeDictionaries
{
get
{
if (_themeDictionary == null)
{
_themeDictionary = new AvaloniaDictionary<ThemeVariant, IResourceProvider>(2);
_themeDictionary = new AvaloniaDictionary<ThemeVariant, IThemeVariantProvider>(2);
_themeDictionary.ForEachItem(
(_, x) =>
{
@ -120,6 +120,8 @@ namespace Avalonia.Controls
return _themeDictionary;
}
}
ThemeVariant? IThemeVariantProvider.Key { get; set; }
bool IResourceNode.HasResources
{
@ -192,7 +194,7 @@ namespace Avalonia.Controls
if (_themeDictionary is not null)
{
IResourceProvider? themeResourceProvider;
IThemeVariantProvider? themeResourceProvider;
if (theme is not null && theme != ThemeVariant.Default)
{
if (_themeDictionary.TryGetValue(theme, out themeResourceProvider)

46
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@ -119,7 +119,19 @@ namespace Avalonia.Controls
resourceProvider = resourceProvider ?? throw new ArgumentNullException(nameof(resourceProvider));
key = key ?? throw new ArgumentNullException(nameof(key));
return new FloatingResourceObservable(resourceProvider, key, converter);
return new FloatingResourceObservable(resourceProvider, key, null, converter);
}
public static IObservable<object?> GetResourceObservable(
this IResourceProvider resourceProvider,
object key,
ThemeVariant? defaultThemeVariant,
Func<object?, object?>? converter = null)
{
resourceProvider = resourceProvider ?? throw new ArgumentNullException(nameof(resourceProvider));
key = key ?? throw new ArgumentNullException(nameof(key));
return new FloatingResourceObservable(resourceProvider, key, defaultThemeVariant, converter);
}
private class ResourceObservable : LightweightObservableBase<object?>
@ -128,7 +140,10 @@ namespace Avalonia.Controls
private readonly object _key;
private readonly Func<object?, object?>? _converter;
public ResourceObservable(IResourceHost target, object key, Func<object?, object?>? converter)
public ResourceObservable(
IResourceHost target,
object key,
Func<object?, object?>? converter)
{
_target = target;
_key = key;
@ -170,11 +185,8 @@ namespace Avalonia.Controls
private object? GetValue()
{
if (_target is not IThemeVariantHost themeVariantHost
|| !_target.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value))
{
value = _target.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
}
var theme = (_target as IThemeVariantHost)?.ActualThemeVariant;
var value = _target.FindResource(theme, _key) ?? AvaloniaProperty.UnsetValue;
return _converter?.Invoke(value) ?? value;
}
@ -183,14 +195,20 @@ namespace Avalonia.Controls
private class FloatingResourceObservable : LightweightObservableBase<object?>
{
private readonly IResourceProvider _target;
private readonly ThemeVariant? _overrideThemeVariant;
private readonly object _key;
private readonly Func<object?, object?>? _converter;
private IResourceHost? _owner;
public FloatingResourceObservable(IResourceProvider target, object key, Func<object?, object?>? converter)
public FloatingResourceObservable(
IResourceProvider target,
object key,
ThemeVariant? overrideThemeVariant,
Func<object?, object?>? converter)
{
_target = target;
_key = key;
_overrideThemeVariant = overrideThemeVariant;
_converter = converter;
}
@ -233,7 +251,7 @@ namespace Avalonia.Controls
{
_owner.ResourcesChanged -= ResourcesChanged;
}
if (_owner is IThemeVariantHost themeVariantHost)
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
{
themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged;
}
@ -244,12 +262,11 @@ namespace Avalonia.Controls
{
_owner.ResourcesChanged += ResourcesChanged;
}
if (_owner is IThemeVariantHost themeVariantHost2)
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost2)
{
themeVariantHost2.ActualThemeVariantChanged -= ActualThemeVariantChanged;
}
PublishNext();
}
@ -265,11 +282,8 @@ namespace Avalonia.Controls
private object? GetValue()
{
if (!(_target.Owner is IThemeVariantHost themeVariantHost)
|| !_target.Owner.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value))
{
value = _target.Owner?.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
}
var theme = _overrideThemeVariant ?? (_target.Owner as IThemeVariantHost)?.ActualThemeVariant;
var value = _target.Owner?.FindResource(theme, _key) ?? AvaloniaProperty.UnsetValue;
return _converter?.Invoke(value) ?? value;
}

1
src/Avalonia.Base/Styling/IThemeVariantHost.cs

@ -7,7 +7,6 @@ namespace Avalonia.Styling;
/// <summary>
/// Interface for the host element with a theme variant.
/// </summary>
[Unstable]
public interface IThemeVariantHost : IResourceHost
{
/// <summary>

3
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -58,7 +58,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
new AvaloniaXamlIlThemeVariantProviderTransformer()
);
InsertBefore<ConvertPropertyValuesToAssignmentsTransformer>(
new AvaloniaXamlIlOptionMarkupExtensionTransformer());

31
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlThemeVariantProviderTransformer.cs

@ -0,0 +1,31 @@
using System.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class AvaloniaXamlIlThemeVariantProviderTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
var type = context.GetAvaloniaTypes().IThemeVariantProvider;
if (!(node is XamlAstObjectNode on
&& type.IsAssignableFrom(on.Type.GetClrType())))
return node;
var keyDirective = on.Children.FirstOrDefault(n => n is XamlAstXmlDirective d
&& d.Namespace == XamlNamespaces.Xaml2006 &&
d.Name == "Key") as XamlAstXmlDirective;
if (keyDirective is null)
return node;
var keyProp = type.Properties.First(p => p.Name == "Key");
on.Children.Add(new XamlAstXamlPropertyValueNode(keyDirective,
new XamlAstClrProperty(keyDirective, keyProp, context.Configuration),
keyDirective.Values, true));
return node;
}
}

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -110,6 +110,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
public IXamlType IThemeVariantProvider { get; }
public IXamlType UriKind { get; }
public IXamlConstructor UriConstructor { get; }
public IXamlType Style { get; }
@ -250,6 +251,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
IThemeVariantProvider = cfg.TypeSystem.GetType("Avalonia.Controls.IThemeVariantProvider");
UriKind = cfg.TypeSystem.GetType("System.UriKind");
UriConstructor = Uri.GetConstructor(new List<IXamlType>() { cfg.WellKnownTypes.String, UriKind });
Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style");

6
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Media;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
@ -10,6 +11,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
private object? _anchor;
private BindingPriority _priority;
private ThemeVariant? _currentThemeVariant;
public DynamicResourceExtension()
{
@ -36,6 +38,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
(object?)serviceProvider.GetFirstParent<IResourceHost>();
}
_currentThemeVariant = StaticResourceExtension.GetDictionaryVariant(serviceProvider);
return this;
}
@ -59,7 +63,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
}
else if (_anchor is IResourceProvider resourceProvider)
{
var source = resourceProvider.GetResourceObservable(ResourceKey, GetConverter(targetProperty));
var source = resourceProvider.GetResourceObservable(ResourceKey, _currentThemeVariant, GetConverter(targetProperty));
return InstancedBinding.OneWay(source, _priority);
}

22
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -33,7 +33,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
var targetObject = provideTarget?.TargetObject;
var targetProperty = provideTarget?.TargetProperty;
var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant;
var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant
?? GetDictionaryVariant(serviceProvider);
var targetType = targetProperty switch
{
@ -78,6 +79,25 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
return ColorToBrushConverter.Convert(control.FindResource(ResourceKey!), targetType);
}
internal static ThemeVariant? GetDictionaryVariant(IServiceProvider serviceProvider)
{
var parents = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents;
if (parents is null)
{
return null;
}
foreach (var parent in parents)
{
if (parent is IThemeVariantProvider { Key: { } setKey })
{
return setKey;
}
}
return null;
}
}
}

4
src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs

@ -13,7 +13,7 @@ namespace Avalonia.Markup.Xaml.Styling
/// When used in runtime, this type might be unsafe with trimming and AOT.
/// </remarks>
[RequiresUnreferencedCode(TrimmingMessages.StyleResourceIncludeRequiresUnreferenceCodeMessage)]
public class ResourceInclude : IResourceProvider
public class ResourceInclude : IResourceProvider, IThemeVariantProvider
{
private readonly IServiceProvider? _serviceProvider;
private readonly Uri? _baseUri;
@ -65,6 +65,8 @@ namespace Avalonia.Markup.Xaml.Styling
/// </summary>
public Uri? Source { get; set; }
ThemeVariant? IThemeVariantProvider.Key { get; set; }
bool IResourceNode.HasResources => Loaded.HasResources;
public event EventHandler? OwnerChanged

22
tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs

@ -1,7 +1,9 @@
using System;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.UnitTests;
using BenchmarkDotNet.Attributes;
using Moq;
@ -30,27 +32,23 @@ namespace Avalonia.Benchmarks.Themes
_app.Dispose();
}
[Benchmark]
public void RepeatButton()
[Benchmark()]
[MethodImpl(MethodImplOptions.NoInlining)]
public void CreateButton()
{
var button = new RepeatButton();
var button = new Button();
_root.Child = button;
_root.LayoutManager.ExecuteLayoutPass();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
}
private static IDisposable CreateApp()
{
var services = new TestServices(
assetLoader: new AssetLoader(),
globalClock: new MockGlobalClock(),
platform: new AppBuilder().RuntimePlatform,
renderInterface: new MockPlatformRenderInterface(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
theme: () => LoadFluentTheme(),
renderInterface: new NullRenderingPlatform(),
dispatcherImpl: new NullThreadingPlatform(),
fontManagerImpl: new MockFontManagerImpl(),
textShaperImpl: new MockTextShaperImpl(),
windowingPlatform: new MockWindowingPlatform());
standardCursorFactory: new NullCursorFactory(),
theme: () => LoadFluentTheme());
return UnitTestApplication.Start(services);
}

4
tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs

@ -1,5 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Platform;
@ -29,6 +29,7 @@ namespace Avalonia.Benchmarks.Themes
}
[Benchmark]
[MethodImpl(MethodImplOptions.NoInlining)]
public bool InitFluentTheme()
{
UnitTestApplication.Current.Styles[0] = new FluentTheme();
@ -36,6 +37,7 @@ namespace Avalonia.Benchmarks.Themes
}
[Benchmark]
[MethodImpl(MethodImplOptions.NoInlining)]
public bool InitSimpleTheme()
{
UnitTestApplication.Current.Styles[0] = new SimpleTheme();

136
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs

@ -1,9 +1,12 @@
using System.Linq;
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.UnitTests;
@ -140,7 +143,7 @@ public class ThemeDictionariesTests : XamlTestBase
Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color);
}
[Fact(Skip = "Not implemented")]
[Fact]
public void StaticResource_Inside_Of_ThemeDictionaries_Should_Use_Same_Theme_Key()
{
var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@"
@ -183,6 +186,135 @@ public class ThemeDictionariesTests : XamlTestBase
Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color);
}
[Fact]
public void StaticResource_Inside_Of_ThemeDictionaries_Should_Use_Same_Theme_Key_From_Inner_File()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Inner.xaml"), @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<StaticResource x:Key='InnerKey' ResourceKey='OuterKey' />
</ResourceDictionary>"),
new RuntimeXamlLoaderDocument(@"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key='Default'>
<Color x:Key='OuterKey'>Green</Color>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='avares://Tests/Inner.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<ResourceDictionary x:Key='Dark'>
<Color x:Key='OuterKey'>White</Color>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='avares://Tests/Inner.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>")
};
var parsed = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var dictionary = (ResourceDictionary)parsed[1]!;
dictionary.TryGetResource("InnerKey", ThemeVariant.Dark, out var resource);
var colorResource = Assert.IsType<Color>(resource);
Assert.Equal(Colors.White, colorResource);
dictionary.TryGetResource("InnerKey", ThemeVariant.Light, out resource);
colorResource = Assert.IsType<Color>(resource);
Assert.Equal(Colors.Green, colorResource);
}
[Fact]
public void DynamicResource_Inside_Of_ThemeDictionaries_Should_Use_Same_Theme_Key_From_Inner_File()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Inner.xaml"), @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<SolidColorBrush x:Key='InnerKey' Color='{DynamicResource OuterKey}' />
</ResourceDictionary>"),
new RuntimeXamlLoaderDocument(@"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key='Default'>
<Color x:Key='OuterKey'>Green</Color>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='avares://Tests/Inner.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<ResourceDictionary x:Key='Dark'>
<Color x:Key='OuterKey'>White</Color>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='avares://Tests/Inner.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>")
};
var parsed = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var dictionary1 = (ResourceDictionary)parsed[0]!;
var dictionary2 = (ResourceDictionary)parsed[1]!;
var ownerApp = new Application(); // DynamicResource needs an owner to work
ownerApp.RequestedThemeVariant = new ThemeVariant("FakeOne", null);
ownerApp.Resources.MergedDictionaries.Add(dictionary1);
ownerApp.Resources.MergedDictionaries.Add(dictionary2);
dictionary2.TryGetResource("InnerKey", ThemeVariant.Dark, out var resource);
var colorResource = Assert.IsAssignableFrom<ISolidColorBrush>(resource);
Assert.Equal(Colors.White, colorResource.Color);
dictionary2.TryGetResource("InnerKey", ThemeVariant.Light, out resource);
colorResource = Assert.IsAssignableFrom<ISolidColorBrush>(resource);
Assert.Equal(Colors.Green, colorResource.Color);
}
[Fact]
public void DynamicResource_Inside_Control_Inside_Of_ThemeDictionaries_Should_Use_Control_Theme_Variant()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(@"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key='Light'>
<Color x:Key='ResourceKey'>Green</Color>
<Template x:Key='Template'>
<ThemeVariantScope RequestedThemeVariant='Dark' TextElement.Foreground='{DynamicResource ResourceKey}' />
</Template>
</ResourceDictionary>
<ResourceDictionary x:Key='Dark'>
<Color x:Key='ResourceKey'>White</Color>
<Template x:Key='Template'>
<ThemeVariantScope RequestedThemeVariant='Light' TextElement.Foreground='{DynamicResource ResourceKey}' />
</Template>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>")
};
var parsed = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var dictionary = (ResourceDictionary)parsed[0]!;
dictionary.TryGetResource("Template", ThemeVariant.Dark, out var resource);
var control = Assert.IsType<ThemeVariantScope>((resource as Template)?.Build());
control.Resources.MergedDictionaries.Add(dictionary);
Assert.Equal(Colors.Green, ((ISolidColorBrush)control[TextElement.ForegroundProperty]!).Color);
control.Resources.MergedDictionaries.Remove(dictionary);
dictionary.TryGetResource("Template", ThemeVariant.Light, out resource);
control = Assert.IsType<ThemeVariantScope>((resource as Template)?.Build());
control.Resources.MergedDictionaries.Add(dictionary);
Assert.Equal(Colors.White, ((ISolidColorBrush)control[TextElement.ForegroundProperty]!).Color);
}
[Fact]
public void StaticResource_Outside_Of_Dictionaries_Should_Use_Control_ThemeVariant()

Loading…
Cancel
Save