Browse Source

WIP

release/11.0.0-preview3
Max Katz 3 years ago
parent
commit
0dfc280e8f
  1. 24
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  2. 128
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/OnPlatformTransformer.cs
  3. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  4. 107
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs
  5. 102
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs

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

@ -21,20 +21,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings)
: base(configuration, emitMappings, true)
{
void InsertAfter<T>(params IXamlAstTransformer[] t)
void InsertAfter<T>(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t);
void InsertBefore<T>(params IXamlAstTransformer[] t)
void InsertBefore<T>(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T), t);
// Before everything else
Transformers.Insert(0, new XNameTransformer());
Transformers.Insert(1, new IgnoredDirectivesTransformer());
Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
// Targeted
InsertBefore<PropertyReferenceResolver>(
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
@ -48,7 +48,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertBefore<ContentConvertTransformer>(
new AvaloniaXamlIlControlThemeTransformer(),
new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlBindingPathParser(),
new AvaloniaXamlIlPropertyPathTransformer(),
new AvaloniaXamlIlSetterTransformer(),
@ -57,6 +57,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
);
InsertBefore<TypeReferenceResolver>(
new OnPlatformTransformer());
InsertAfter<TypeReferenceResolver>(
new XDataTypeTransformer());
@ -87,14 +89,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
_contextType = CreateContextType(contextTypeBuilder);
}
public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings,
IXamlType contextType) : this(configuration, emitMappings)
{
_contextType = contextType;
}
public const string PopulateName = "__AvaloniaXamlIlPopulate";
public const string BuildName = "__AvaloniaXamlIlBuild";
@ -116,7 +118,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
});
var rootObject = (XamlAstObjectNode)parsed.Root;
var classDirective = rootObject.Children
@ -131,8 +133,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
false) :
TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
(XamlAstXmlTypeReference)rootObject.Type, true);
if (overrideRootType != null)
{
if (!rootType.Type.IsAssignableFrom(overrideRootType))
@ -145,7 +147,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transform(parsed);
Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource);
}
public void OverrideRootType(XamlDocument doc, IXamlAstTypeReference newType)

128
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/OnPlatformTransformer.cs

@ -0,0 +1,128 @@
using System;
using System.Linq;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class OnPlatformTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlAstObjectNode xmlobj
&& xmlobj.Type is XamlAstXmlTypeReference xmlref
&& xmlref.Name.StartsWith("OnPlatform")
&& !xmlref.GenericArguments.Any())
{
IXamlType propertyType = null;
if (context.ParentNodes().FirstOrDefault() is XamlAstXamlPropertyValueNode parentPropertyValueNode)
{
var property = (XamlAstNamePropertyReference)parentPropertyValueNode.Property;
var declaringType = TypeReferenceResolver.ResolveType(context, (XamlAstXmlTypeReference)property.DeclaringType, context.StrictMode);
propertyType = declaringType.Type.GetAllProperties().First(p => p.Name == property.Name).PropertyType;
}
else if (context.ParentNodes().FirstOrDefault() is XamlAstObjectNode parentNode)
{
var parentType = parentNode.Type is XamlAstClrTypeReference clrType
? clrType.Type
: TypeReferenceResolver.ResolveType(context, (XamlAstXmlTypeReference)parentNode.Type, context.StrictMode).Type;
var contentProperty = context.Configuration.FindContentProperty(parentType);
propertyType = contentProperty.PropertyType;
}
if (propertyType is null)
{
throw new InvalidOperationException("Unable to find OnPlatform property type");
}
xmlobj.Type = TypeReferenceResolver.ResolveType(context, xmlref.XmlNamespace, xmlref.Name,
xmlref.IsMarkupExtension, new[] { new XamlAstClrTypeReference(xmlref, propertyType, false) },
xmlref, context.StrictMode);
}
if (node is XamlAstNamePropertyReference xmlprop
&& xmlprop.DeclaringType is XamlAstXmlTypeReference propxmlref
&& propxmlref.Name.StartsWith("OnPlatform")
&& !propxmlref.GenericArguments.Any())
{
var expectedType = context.ParentNodes().OfType<XamlAstObjectNode>()
.First(n => n.Type is XamlAstClrTypeReference clrRef && clrRef.Type.Name.StartsWith("OnPlatform")
|| n.Type is XamlAstXmlTypeReference xmlRef && xmlRef.Name.StartsWith("OnPlatform"))
.Type;
xmlprop.DeclaringType = expectedType;
xmlprop.TargetType = expectedType;
}
// if (node is XamlAstObjectNode onobj
// && onobj.Type is XamlAstXmlTypeReference onref
// && onref.Name == "On")
// {
// var platformStr = (onobj.Children.OfType<XamlAstXamlPropertyValueNode>()
// .FirstOrDefault(v => ((XamlAstNamePropertyReference)v.Property).Name == "Platform")?.Values.Single() as XamlAstTextNode)?
// .Text;
// if (string.IsNullOrWhiteSpace(platformStr))
// {
// throw new InvalidOperationException("On.Platform string must be set");
// }
// var content = onobj.Children.OfType<XamlAstObjectNode>().FirstOrDefault();
// if (content is null)
// {
// throw new InvalidOperationException("On content object must be set");
// }
//
// var parentOnPlatformObject = context.ParentNodes().OfType<XamlAstObjectNode>()
// .First(n => n.Type is XamlAstClrTypeReference clrRef && clrRef.Type.Name.StartsWith("OnPlatform")
// || n.Type is XamlAstXmlTypeReference xmlRef && xmlRef.Name.StartsWith("OnPlatform"));
// parentOnPlatformObject.Children.Remove(onobj);
// foreach (var platform in platformStr.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries))
// {
// var propertyNode = new XamlAstXamlPropertyValueNode(onobj,
// new XamlAstNamePropertyReference(onobj, parentOnPlatformObject.Type, platform.Trim(),
// parentOnPlatformObject.Type), content);
// parentOnPlatformObject.Children.Add(propertyNode);
// }
//
// return parentOnPlatformObject.Children.Last();
// }
// if (node is XamlAstXamlPropertyValueNode propNode)
// {
// var type = (propNode.Property as XamlAstNamePropertyReference).TargetType as XamlAstXmlTypeReference;
//
// propNode.VisitChildren(new OnPlatformGenericTypeVisitor(type));
//
// return node;
// }
return node;
}
private class OnPlatformGenericTypeVisitor : IXamlAstVisitor
{
private readonly XamlAstXmlTypeReference _type;
public OnPlatformGenericTypeVisitor(IXamlAstTypeReference type)
{
}
public IXamlAstNode Visit(IXamlAstNode node)
{
if (node is XamlAstXmlTypeReference { Name: "OnPlatform" } xmlref)
{
xmlref.GenericArguments.Add(_type);
}
return node;
}
public void Push(IXamlAstNode node)
{
}
public void Pop()
{
}
}
}

2
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@ -1 +1 @@
Subproject commit c1c0594ec2c35b08988183b1a5b3e34dfa19179d
Subproject commit 2c3b53a3db61018db620418fab509ba1d168ff24

107
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs

@ -1,97 +1,74 @@
#nullable enable
using System;
using System.Globalization;
using System.Reflection;
using Avalonia.Data.Converters;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.MarkupExtensions;
public class OnPlatformExtension
public class On
{
private static readonly object s_unset = new object();
public string Platform { get; set; } = "Unknown";
[Content]
public object? Content { get; set; }
}
public class OnPlatformExtension<TReturn> : IAddChild<On>
{
private readonly Dictionary<string, TReturn?> _values = new();
public OnPlatformExtension()
{
}
public OnPlatformExtension(object defaultValue)
public OnPlatformExtension(TReturn defaultValue)
{
Default = defaultValue;
}
[Content]
public object? Default { get; set; } = s_unset;
public object? Windows { get; set; } = s_unset;
public object? macOS { get; set; } = s_unset;
public object? Linux { get; set; } = s_unset;
public object? Android { get; set; } = s_unset;
public object? iOS { get; set; } = s_unset;
public object? Browser { get; set; } = s_unset;
public IValueConverter? Converter { get; set; }
public object? ConverterParameter { get; set; }
public TReturn? Default { get => _values.TryGetValue(nameof(Default), out var value) ? value : default; set { _values[nameof(Default)] = value; } }
public TReturn? Windows { get => _values.TryGetValue(nameof(Windows), out var value) ? value : default; set { _values[nameof(Windows)] = value; } }
public TReturn? macOS { get => _values.TryGetValue(nameof(macOS), out var value) ? value : default; set { _values[nameof(macOS)] = value; } }
public TReturn? Linux { get => _values.TryGetValue(nameof(Linux), out var value) ? value : default; set { _values[nameof(Linux)] = value; } }
public TReturn? Android { get => _values.TryGetValue(nameof(Android), out var value) ? value : default; set { _values[nameof(Android)] = value; } }
public TReturn? iOS { get => _values.TryGetValue(nameof(iOS), out var value) ? value : default; set { _values[nameof(iOS)] = value; } }
public TReturn? Browser { get => _values.TryGetValue(nameof(Browser), out var value) ? value : default; set { _values[nameof(Browser)] = value; } }
public object? ProvideValue(IServiceProvider serviceProvider)
public object? ProvideValue()
{
if (Default == s_unset
&& Windows == s_unset
&& macOS == s_unset
&& Linux == s_unset
&& Android == s_unset
&& iOS == s_unset
&& Browser == s_unset)
if (!_values.Any())
{
throw new InvalidOperationException("OnPlatformExtension requires a value to be specified for at least one platform or Default.");
}
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
var targetType = provideTarget.TargetProperty switch
{
AvaloniaProperty ap => ap.PropertyType,
PropertyInfo pi => pi.PropertyType,
_ => null,
};
if (provideTarget.TargetObject is Setter setter)
{
targetType = setter.Property?.PropertyType ?? targetType;
}
if (!TryGetValueForPlatform(out var value))
{
return AvaloniaProperty.UnsetValue;
}
if (targetType is null)
{
return value;
}
var converter = Converter ?? DefaultValueConverter.Instance;
return converter.Convert(value, targetType, ConverterParameter, CultureInfo.CurrentUICulture);
var (value, hasValue) = TryGetValueForPlatform();
return !hasValue ? AvaloniaProperty.UnsetValue : value;
}
private bool TryGetValueForPlatform(out object? value)
private (TReturn? value, bool hasValue) TryGetValueForPlatform()
{
var runtimeInfo = AvaloniaLocator.Current.GetRequiredService<IRuntimePlatform>().GetRuntimeInfo();
value = runtimeInfo.OperatingSystem switch
return runtimeInfo.OperatingSystem switch
{
OperatingSystemType.WinNT when Windows != s_unset => Windows,
OperatingSystemType.Linux when Linux != s_unset => Linux,
OperatingSystemType.OSX when macOS != s_unset => macOS,
OperatingSystemType.Android when Android != s_unset => Android,
OperatingSystemType.iOS when iOS != s_unset => iOS,
OperatingSystemType.Browser when Browser != s_unset => Browser,
_ => Default
OperatingSystemType.WinNT => _values.TryGetValue(nameof(Windows), out var val) ? (val, true) : default,
OperatingSystemType.OSX => _values.TryGetValue(nameof(macOS), out var val) ? (val, true) : default,
OperatingSystemType.Linux => _values.TryGetValue(nameof(Linux), out var val) ? (val, true) : default,
OperatingSystemType.Android => _values.TryGetValue(nameof(Android), out var val) ? (val, true) : default,
OperatingSystemType.iOS => _values.TryGetValue(nameof(iOS), out var val) ? (val, true) : default,
OperatingSystemType.Browser => _values.TryGetValue(nameof(Browser), out var val) ? (val, true) : default,
_ => _values.TryGetValue(nameof(Default), out var val) ? (val, true) : default
};
}
return value != s_unset;
public void AddChild(On child)
{
foreach (var platform in child.Platform.Split(new [] { "," }, StringSplitOptions.RemoveEmptyEntries))
{
_values[platform.Trim()] = (TReturn?)child.Content;
}
}
}

102
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs

@ -4,7 +4,6 @@ using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.PlatformSupport;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;
@ -18,7 +17,7 @@ public class OnPlatformExtensionTests : XamlTestBase
{
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>()
.ToConstant(new TestRuntimePlatform(OperatingSystemType.Unknown));
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
@ -31,7 +30,7 @@ public class OnPlatformExtensionTests : XamlTestBase
Assert.Equal("Hello World", textBlock.Text);
}
}
[Fact]
public void Should_Resolve_Default_Value_From_Ctor()
{
@ -39,7 +38,7 @@ public class OnPlatformExtensionTests : XamlTestBase
{
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>()
.ToConstant(new TestRuntimePlatform(OperatingSystemType.Unknown));
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
@ -52,7 +51,7 @@ public class OnPlatformExtensionTests : XamlTestBase
Assert.Equal("Hello World", textBlock.Text);
}
}
[Theory]
[InlineData(OperatingSystemType.WinNT, "Im Windows")]
[InlineData(OperatingSystemType.OSX, "Im macOS")]
@ -67,7 +66,7 @@ public class OnPlatformExtensionTests : XamlTestBase
{
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>()
.ToConstant(new TestRuntimePlatform(currentPlatform));
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
@ -91,7 +90,7 @@ public class OnPlatformExtensionTests : XamlTestBase
{
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>()
.ToConstant(new TestRuntimePlatform(OperatingSystemType.WinNT));
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
@ -104,7 +103,7 @@ public class OnPlatformExtensionTests : XamlTestBase
Assert.Equal(50.1, border.Height);
}
}
[Fact]
public void Should_Convert_Avalonia_Type()
{
@ -112,7 +111,7 @@ public class OnPlatformExtensionTests : XamlTestBase
{
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>()
.ToConstant(new TestRuntimePlatform(OperatingSystemType.WinNT));
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
@ -126,6 +125,27 @@ public class OnPlatformExtensionTests : XamlTestBase
}
}
[Fact]
public void Should_Respect_Custom_TypeArgument()
{
using (AvaloniaLocator.EnterScope())
{
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>()
.ToConstant(new TestRuntimePlatform(OperatingSystemType.WinNT));
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<TextBlock DataContext='{OnPlatform Windows=""10, 10, 10, 10"", x:TypeArguments=Thickness}'/>
</UserControl>";
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = (TextBlock)userControl.Content!;
Assert.Equal(new Thickness(10, 10, 10, 10), textBlock.DataContext);
}
}
[Fact]
public void Should_Allow_Nester_Markup_Extensions()
{
@ -149,9 +169,9 @@ public class OnPlatformExtensionTests : XamlTestBase
Assert.Equal(Color.Parse("#ff506070"), ((ISolidColorBrush)border.Background!).Color);
}
}
[Fact]
public void Should_Use_Converter_If_Provided()
public void Should_Support_Xml_Syntax()
{
using (AvaloniaLocator.EnterScope())
{
@ -160,23 +180,27 @@ public class OnPlatformExtensionTests : XamlTestBase
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
<UserControl.Resources>
<local:TestOnPlatformConverter x:Key='TestConverter' />
</UserControl.Resources>
<Border Padding='{OnPlatform Windows=""My Value"", ConverterParameter=""My Parameter"", Converter={StaticResource TestConverter}}'/>
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border>
<Border.Background>
<OnPlatform>
<OnPlatform.Windows>
<SolidColorBrush Color='#ff506070' />
</OnPlatform.Windows>
</OnPlatform>
</Border.Background>
</Border>
</UserControl>";
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = (Border)userControl.Content!;
Assert.Equal(new Thickness(4), border.Padding);
Assert.Equal(Color.Parse("#ff506070"), ((ISolidColorBrush)border.Background!).Color);
}
}
[Fact]
public void Should_Support_Xml_Syntax()
public void Should_Support_Xml_Syntax_With_Custom_TypeArguments()
{
using (AvaloniaLocator.EnterScope())
{
@ -187,11 +211,39 @@ public class OnPlatformExtensionTests : XamlTestBase
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border>
<Border.Tag>
<OnPlatform x:TypeArguments='Thickness' Windows='10, 10, 10, 10' />
</Border.Tag>
</Border>
</UserControl>";
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = (Border)userControl.Content!;
Assert.Equal(new Thickness(10, 10, 10, 10), border.Tag);
}
}
[Fact]
public void Should_Support_Special_On_Syntax()
{
using (AvaloniaLocator.EnterScope())
{
AvaloniaLocator.CurrentMutable.Bind<IRuntimePlatform>()
.ToConstant(new TestRuntimePlatform(OperatingSystemType.OSX));
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border>
<Border.Background>
<OnPlatform>
<OnPlatform.Windows>
<On Platform='Windows, macOS'>
<SolidColorBrush Color='#ff506070' />
</OnPlatform.Windows>
</On>
<On Platform='Linux'>
<SolidColorBrush Color='#000' />
</On>
</OnPlatform>
</Border.Background>
</Border>
@ -203,7 +255,7 @@ public class OnPlatformExtensionTests : XamlTestBase
Assert.Equal(Color.Parse("#ff506070"), ((ISolidColorBrush)border.Background!).Color);
}
}
[Fact]
public void Should_Support_Control_Inside_Xml_Syntax()
{
@ -232,12 +284,12 @@ public class OnPlatformExtensionTests : XamlTestBase
private class TestRuntimePlatform : StandardRuntimePlatform
{
private readonly OperatingSystemType _operatingSystemType;
public TestRuntimePlatform(OperatingSystemType operatingSystemType)
{
_operatingSystemType = operatingSystemType;
}
public override RuntimePlatformInfo GetRuntimeInfo()
{
return new RuntimePlatformInfo() { OperatingSystem = _operatingSystemType };

Loading…
Cancel
Save