Browse Source

Initial support for deferred resources in XAML compiler.

pull/8422/head
Steven Kirk 4 years ago
parent
commit
5c6eeed4fa
  1. 18
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  2. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  3. 80
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs
  4. 9
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  5. 98
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

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

@ -192,6 +192,11 @@ namespace Avalonia.Controls
return false;
}
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator()
{
return _inner?.GetEnumerator() ?? Enumerable.Empty<KeyValuePair<object, object?>>().GetEnumerator();
}
void ICollection<KeyValuePair<object, object?>>.Add(KeyValuePair<object, object?> item)
{
Add(item.Key, item.Value);
@ -218,12 +223,17 @@ namespace Avalonia.Controls
return false;
}
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator()
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal bool ContainsDeferredKey(object key)
{
return _inner?.GetEnumerator() ?? Enumerable.Empty<KeyValuePair<object, object?>>().GetEnumerator();
}
if (_inner is not null && _inner.TryGetValue(key, out var result))
{
return result is DeferredItem;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
return false;
}
void IResourceProvider.AddOwner(IResourceHost owner)
{

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

@ -59,6 +59,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertAfter<TypeReferenceResolver>(
new XDataTypeTransformer());
InsertBefore<DeferredContentTransformer>(
new AvaloniaXamlIlDeferredResourceTransformer()
);
// After everything else
InsertBefore<NewObjectTransformer>(
new AddNameScopeRegistration(),

80
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs

@ -0,0 +1,80 @@
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
{
internal class AvaloniaXamlIlDeferredResourceTransformer : 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], types.XamlIlTypes.Object, context.Configuration);
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new XamlDirectCallPropertySetter(types.ResourceDictionaryDeferredAdd),
};
}
else if (pa.Property.Name == "Resources" && pa.Property.Getter.ReturnType.Equals(types.IResourceDictionary))
{
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new AdderSetter(pa.Property.Getter, types.ResourceDictionaryDeferredAdd),
};
}
return node;
}
class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter<IXamlILEmitter>
{
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<IXamlType> Parameters { get; }
public void Emit(IXamlILEmitter emitter)
{
var locals = new Stack<XamlLocalsPool.PooledLocal>();
// 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);
}
}
}
}

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

@ -96,6 +96,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType TextDecorations { get; }
public IXamlType TextTrimming { get; }
public IXamlType ISetter { get; }
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
@ -210,6 +213,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations");
TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter");
IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary");
ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
}
}

98
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

@ -66,6 +66,104 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
[Fact]
public void Item_Is_Added_To_ResourceDictionary_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Color x:Key='Red'>Red</Color>
</ResourceDictionary>";
var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml);
Assert.True(resources.ContainsDeferredKey("Red"));
}
}
[Fact]
public void Item_Is_Added_To_Window_Resources_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<Color x:Key='Red'>Red</Color>
</Window.Resources>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var resources = (ResourceDictionary)window.Resources;
Assert.True(resources.ContainsDeferredKey("Red"));
}
}
[Fact]
public void Item_Is_Added_To_Window_MergedDictionaries_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<Color x:Key='Red'>Red</Color>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var resources = (ResourceDictionary)window.Resources.MergedDictionaries[0];
Assert.True(resources.ContainsDeferredKey("Red"));
}
}
[Fact]
public void Item_Is_Added_To_Style_Resources_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<Color x:Key='Red'>Red</Color>
</Style.Resources>
</Style>";
var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml);
var resources = (ResourceDictionary)style.Resources;
Assert.True(resources.ContainsDeferredKey("Red"));
}
}
[Fact]
public void Item_Is_Added_To_Styles_Resources_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
<Color x:Key='Red'>Red</Color>
</Styles.Resources>
</Styles>";
var style = (Styles)AvaloniaRuntimeXamlLoader.Load(xaml);
var resources = (ResourceDictionary)style.Resources;
Assert.True(resources.ContainsDeferredKey("Red"));
}
}
private IDisposable StyledWindow(params (string, string)[] assets)
{
var services = TestServices.StyledWindow.With(

Loading…
Cancel
Save