diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index a3c51dc965..3b4692f2aa 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -67,11 +67,7 @@ namespace Avalonia.Collections /// The resource, or null if not found. public TValue this[TKey key] { - get - { - return _inner[key]; - } - + get => GetItemCore(key); set { TValue old; @@ -98,7 +94,11 @@ namespace Avalonia.Collections } } - object IDictionary.this[object key] { get => ((IDictionary)_inner)[key]; set => ((IDictionary)_inner)[key] = value; } + object IDictionary.this[object key] + { + get => GetItemCore((TKey)key); + set => this[(TKey)key] = (TValue)value; + } /// public void Add(TKey key, TValue value) @@ -166,7 +166,7 @@ namespace Avalonia.Collections } /// - public bool TryGetValue(TKey key, out TValue value) => _inner.TryGetValue(key, out value); + public bool TryGetValue(TKey key, out TValue value) => TryGetValueCore(key, out value); /// IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); @@ -204,6 +204,9 @@ namespace Avalonia.Collections /// void IDictionary.Remove(object key) => Remove((TKey)key); + protected virtual TValue GetItemCore(TKey key) => _inner[key]; + protected virtual bool TryGetValueCore(TKey key, out TValue value) => _inner.TryGetValue(key, out value); + private void NotifyAdd(TKey key, TValue value) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); diff --git a/src/Avalonia.Build.Tasks/Properties/launchSettings.json b/src/Avalonia.Build.Tasks/Properties/launchSettings.json new file mode 100644 index 0000000000..6f880e9b85 --- /dev/null +++ b/src/Avalonia.Build.Tasks/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "Avalonia.Build.Tasks": { + "commandName": "Executable", + "executablePath": "$(SolutionDir)\\src\\Avalonia.Build.Tasks\\bin\\Debug\\netcoreapp3.1\\Avalonia.Build.Tasks.exe", + "commandLineArgs": "D:\\projects\\AvaloniaUI\\Avalonia\\src\\Avalonia.Themes.Fluent\\obj\\Debug\\netstandard2.0\\Avalonia\\original.dll \"D:\\projects\\AvaloniaUI\\Avalonia\\src\\Avalonia.Themes.Fluent\\obj\\Debug\\netstandard2.0\\Avalonia\\references\" $(SolutionDir)\\out.dll" + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Templates/IControlTemplate.cs b/src/Avalonia.Controls/Templates/IControlTemplate.cs index af5f3097af..3f64632087 100644 --- a/src/Avalonia.Controls/Templates/IControlTemplate.cs +++ b/src/Avalonia.Controls/Templates/IControlTemplate.cs @@ -9,22 +9,4 @@ namespace Avalonia.Controls.Templates public interface IControlTemplate : ITemplate> { } - - public class TemplateResult - { - public T Result { get; } - public INameScope NameScope { get; } - - public TemplateResult(T result, INameScope nameScope) - { - Result = result; - NameScope = nameScope; - } - - public void Deconstruct(out T result, out INameScope scope) - { - result = Result; - scope = NameScope; - } - } } diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index c56dd74143..c19e975950 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using Avalonia.Collections; -using Avalonia.Metadata; +using Avalonia.Controls.Templates; #nullable enable @@ -101,6 +101,11 @@ namespace Avalonia.Controls public event EventHandler? OwnerChanged; + public void Add(object key, Func loader) + { + Add(key, new LazyItem(loader)); + } + public bool TryGetResource(object key, out object? value) { if (TryGetValue(key, out value)) @@ -176,9 +181,52 @@ namespace Avalonia.Controls } } + protected override object? GetItemCore(object key) + { + var item = base.GetItemCore(key); + + if (item is LazyItem lazy) + { + var value = lazy.Load(); + this[key] = value; + return value; + } + else + { + return item; + } + } + + protected override bool TryGetValueCore(object key, out object? value) + { + if (base.TryGetValueCore(key, out value)) + { + if (value is LazyItem lazy) + { + value = lazy.Load(); + this[key] = value; + } + + return true; + } + + return false; + } + private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); } + + private class LazyItem + { + private Func _load; + public LazyItem(Func load) => _load = load; + public object? Load() + { + var result = _load(null); + return result is TemplateResult t ? t.Result : result; + } + } } } diff --git a/src/Avalonia.Styling/Controls/Templates/TemplateResult.cs b/src/Avalonia.Styling/Controls/Templates/TemplateResult.cs new file mode 100644 index 0000000000..643be587a2 --- /dev/null +++ b/src/Avalonia.Styling/Controls/Templates/TemplateResult.cs @@ -0,0 +1,20 @@ +namespace Avalonia.Controls.Templates +{ + public class TemplateResult + { + public T Result { get; } + public INameScope NameScope { get; } + + public TemplateResult(T result, INameScope nameScope) + { + Result = result; + NameScope = nameScope; + } + + public void Deconstruct(out T result, out INameScope scope) + { + result = Result; + scope = NameScope; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index abff763bb1..c84c65fc84 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -57,13 +57,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer() ); + InsertBefore( + new AvaloniaXamlIlLazyResourceTransformer() + ); + // After everything else InsertBefore( new AddNameScopeRegistration(), new AvaloniaXamlIlDataContextTypeTransformer(), new AvaloniaXamlIlBindingPathTransformer(), new AvaloniaXamlIlCompiledBindingsMetadataRemover() - ); + ); Transformers.Add(new AvaloniaXamlIlMetadataRemover()); Transformers.Add(new AvaloniaXamlIlRootObjectScope()); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlLazyResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlLazyResourceTransformer.cs new file mode 100644 index 0000000000..fdf300559c --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlLazyResourceTransformer.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Linq; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + class AvaloniaXamlIlLazyResourceTransformer : IXamlAstTransformer + { + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) + return node; + + var types = context.GetAvaloniaTypes(); + + if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content") + { + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], context.Configuration); + pa.PossibleSetters = new List + { + new XamlDirectCallPropertySetter(types.ResourceDictionaryLazyAdd), + }; + } + else if ((pa.Property.DeclaringType == types.StyledElement || + pa.Property.DeclaringType == types.Style || + pa.Property.DeclaringType == types.Styles) && + pa.Property.Name == "Resources") + { + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], context.Configuration); + pa.PossibleSetters = new List + { + new AdderSetter(pa.Property.Getter, types.ResourceDictionaryLazyAdd), + }; + } + + return node; + } + + class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter + { + private readonly IXamlMethod _getter; + private readonly IXamlMethod _adder; + + public AdderSetter(IXamlMethod getter, IXamlMethod adder) + { + _getter = getter; + _adder = adder; + TargetType = getter.DeclaringType; + Parameters = adder.ParametersWithThis().Skip(1).ToList(); + } + + public IXamlType TargetType { get; } + + public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters + { + AllowMultiple = true + }; + + public IReadOnlyList Parameters { get; } + public void Emit(IXamlILEmitter emitter) + { + var locals = new Stack(); + // Save all "setter" parameters + for (var c = Parameters.Count - 1; c >= 0; c--) + { + var loc = emitter.LocalsPool.GetLocal(Parameters[c]); + locals.Push(loc); + emitter.Stloc(loc.Local); + } + + emitter.EmitCall(_getter); + while (locals.Count>0) + using (var loc = locals.Pop()) + emitter.Ldloc(loc.Local); + emitter.EmitCall(_adder, true); + } + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 34aae2c5ed..694a3319c8 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -78,6 +78,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType ColumnDefinition { get; } public IXamlType ColumnDefinitions { get; } public IXamlType Classes { get; } + public IXamlType ResourceDictionary { get; } + public IXamlMethod ResourceDictionaryLazyAdd { get; } + public IXamlType Style { get; } + public IXamlType Styles { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -168,6 +172,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers RowDefinition = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinition"); RowDefinitions = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinitions"); Classes = cfg.TypeSystem.GetType("Avalonia.Controls.Classes"); + ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); + ResourceDictionaryLazyAdd = ResourceDictionary.FindMethod( + "Add", + XamlIlTypes.Void, + true, + XamlIlTypes.Object, + cfg.TypeSystem.GetType("System.Func`2").MakeGenericType( + cfg.TypeSystem.GetType("System.IServiceProvider"), + XamlIlTypes.Object)); + Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style"); + Styles = cfg.TypeSystem.GetType("Avalonia.Styling.Styles"); } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 578fa888a3..52be76b755 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -29,6 +29,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void Foo() + { + using (StyledWindow()) + { + var xaml = @" + + + Red + + +"; + + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var resources = window.Resources; + var brush = (SolidColorBrush)resources["RedBrush"]; + + Assert.Equal(Colors.Red, brush.Color); + } + } + [Fact] public void DynamicResource_Finds_Resource_In_Parent_Dictionary() {