Skip to content

feat: x:Shared #16644

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/Avalonia.Base/Controls/ResourceDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ public void AddDeferred(object key, Func<IServiceProvider?, object?> factory)
public void AddDeferred(object key, IDeferredContent deferredContent)
=> Add(key, deferredContent);

public void AddNotSharedDeferred(object key, IDeferredContent deferredContent)
=> Add(key, new NotSharedDeferredItem(deferredContent));

public void Clear()
{
if (_inner?.Count > 0)
Expand Down Expand Up @@ -236,12 +239,16 @@ public bool TryGetValue(object key, out object? value)
try
{
_lastDeferredItemKey = key;
_inner[key] = value = deferred.Build(null) switch
value = deferred.Build(null) switch
{
ITemplateResult t => t.Result,
{ } v => v,
_ => null,
};
if (deferred is not NotSharedDeferredItem)
{
_inner[key] = value;
}
}
finally
{
Expand All @@ -250,7 +257,6 @@ public bool TryGetValue(object key, out object? value)
}
return true;
}

value = null;
return false;
}
Expand Down Expand Up @@ -376,5 +382,11 @@ private sealed class DeferredItem : IDeferredContent
public DeferredItem(Func<IServiceProvider?, object?> factory) => _factory = factory;
public object? Build(IServiceProvider? serviceProvider) => _factory(serviceProvider);
}

private sealed class NotSharedDeferredItem(IDeferredContent deferredContent) : IDeferredContent
{
private readonly IDeferredContent _deferredContent = deferredContent ;
public object? Build(IServiceProvider? serviceProvider) => _deferredContent.Build(serviceProvider);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
Expand All @@ -22,23 +23,61 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content"
&& ShouldBeDeferred(pa.Values[1]))
{
IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared
? types.ResourceDictionaryNotSharedDeferredAdd
: types.ResourceDictionaryDeferredAdd;

pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new XamlDirectCallPropertySetter(types.ResourceDictionaryDeferredAdd),
new XamlDirectCallPropertySetter(addMethod),
};
}
else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true
&& ShouldBeDeferred(pa.Values[1]))
{
IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared
? types.ResourceDictionaryNotSharedDeferredAdd
: types.ResourceDictionaryDeferredAdd;

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),
new AdderSetter(pa.Property.Getter, addMethod),
};
}

return node;

bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value)
{
value = default;
if (valueNode is XamlAstConstructableObjectNode co)
{
// Try find x:Share directive
if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective)
{
if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text)
{
if (bool.TryParse(text.Text, out var parseValue))
{
// If the parser succeeds, remove the x:Share directive
co.Children.Remove(sharedDirective);
return true;
}
else
{
context.ReportTransformError("Invalid argument type for x:Shared directive.", node);
}
}
else
{
context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node);
}
}
}
return false;
}
}

private static bool ShouldBeDeferred(IXamlAstValueNode node)
Expand Down Expand Up @@ -71,7 +110,7 @@ private static bool ShouldBeDeferred(IXamlAstValueNode node)

return true;
}

class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable<AdderSetter>
{
private readonly IXamlMethod _getter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ sealed class AvaloniaXamlIlWellKnownTypes
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
public IXamlMethod ResourceDictionaryNotSharedDeferredAdd { get; }
public IXamlMethod ResourceDictionaryEnsureCapacity { get; }
public IXamlMethod ResourceDictionaryGetCount { get; }
public IXamlType IThemeVariantProvider { get; }
Expand Down Expand Up @@ -312,6 +313,9 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
ResourceDictionaryDeferredAdd = ResourceDictionary.GetMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,
cfg.TypeSystem.GetType("Avalonia.Controls.IDeferredContent"));
ResourceDictionaryNotSharedDeferredAdd = ResourceDictionary.GetMethod("AddNotSharedDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,
cfg.TypeSystem.GetType("Avalonia.Controls.IDeferredContent"));

ResourceDictionaryEnsureCapacity = ResourceDictionary.GetMethod("EnsureCapacity", XamlIlTypes.Void, true, XamlIlTypes.Int32);
ResourceDictionaryGetCount = ResourceDictionary.GetMethod("get_Count", XamlIlTypes.Int32, true);
IThemeVariantProvider = cfg.TypeSystem.GetType("Avalonia.Controls.IThemeVariantProvider");
Expand Down
54 changes: 54 additions & 0 deletions tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XSharedDirectiveTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Avalonia.Controls;
using Avalonia.UnitTests;
using Xunit;

namespace Avalonia.Markup.Xaml.UnitTests.Xaml;

public class XSharedDirectiveTests : XamlTestBase
{
[Fact]
public void Should_Create_New_Instance_Where_x_Share_Is_False()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
const string xaml = $$"""
<Window xmlns="https://github.com/avaloniaui"
xmlns:sys="clr-namespace:System;assembly=netstandard"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<ColumnDefinitions x:Key="InplicitSharedResource">
<ColumnDefinition Width="150" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="Auto" />
</ColumnDefinitions>
<ColumnDefinitions x:Key="NotSharedResource"
x:Shared="false">
<ColumnDefinition Width="150" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="Auto" />
</ColumnDefinitions>
</Window.Resources>
</Window>
""";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
window.ApplyTemplate();

var implicitSharedInstance1 = window.FindResource("ImplicitSharedResource");
Assert.NotNull(implicitSharedInstance1);
var implicitSharedInstance2 = window.FindResource("ImplicitSharedResource");
Assert.NotNull(implicitSharedInstance2);

Assert.Same(implicitSharedInstance1, implicitSharedInstance2);

var notSharedResource1 = window.FindResource("NotSharedResource");
Assert.NotNull(notSharedResource1);

var notSharedResource2 = window.FindResource("NotSharedResource");
Assert.NotNull(notSharedResource2);

Assert.NotSame(notSharedResource1, notSharedResource2);

Assert.Equal(notSharedResource1.ToString(), notSharedResource2.ToString());
}
}
}
Loading