Browse Source

Support ControlTheme in XAML compiler.

pull/8262/head
Steven Kirk 4 years ago
parent
commit
dee353bb96
  1. 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  2. 39
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
  3. 75
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  4. 53
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs
  5. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs

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

@ -48,6 +48,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertBefore<ContentConvertTransformer>(
new AvaloniaXamlIlBindingPathParser(),
new AvaloniaXamlIlControlThemeTransformer(),
new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlPropertyPathTransformer(),

39
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs

@ -0,0 +1,39 @@
using System.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlControlThemeTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.ControlTheme"))
return node;
// Check if we've already transformed this node.
if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode)
return node;
var targetTypeNode = on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "TargetType") ??
throw new XamlParseException("ControlTheme must have a TargetType.", node);
IXamlType targetType;
if (targetTypeNode.Values[0] is XamlTypeExtensionNode extension)
targetType = extension.Value.GetClrType();
else if (targetTypeNode.Values[0] is XamlAstTextNode text)
targetType = TypeReferenceResolver.ResolveType(context, text.Text, false, text, true).GetClrType();
else
throw new XamlParseException("Could not determine TargetType for ControlTheme.", targetTypeNode);
return new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlAstClrTypeReference(targetTypeNode, targetType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
}
}

75
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@ -1,19 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Data.Core;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
using XamlParseException = XamlX.XamlParseException;
using XamlLoadException = XamlX.XamlLoadException;
class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@ -22,21 +17,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
return node;
var parent = context.ParentNodes().OfType<XamlAstObjectNode>()
.FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style");
if (parent == null)
throw new XamlParseException(
"Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node);
var selectorProperty = parent.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
if (selectorProperty == null)
throw new XamlParseException(
"Can not find parent Style Selector", node);
var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode;
if (selector?.TargetType == null)
throw new XamlParseException(
"Can not resolve parent Style Selector type", node);
var targetTypeNode = context.ParentNodes()
.OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
.FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) ??
throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType", node);
IXamlType propType = null;
var property = @on.Children.OfType<XamlAstXamlPropertyValueNode>()
@ -50,7 +34,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
new XamlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
new XamlAstClrTypeReference(targetTypeNode, targetTypeNode.TargetType.GetClrType(), false), property.Values[0]);
property.Values = new List<IXamlAstValueNode> {avaloniaPropertyNode};
propType = avaloniaPropertyNode.AvaloniaPropertyType;
}
@ -84,6 +68,55 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
private (IXamlLineInfo, IXamlType) GetTargetType(AstTransformationContext context, IXamlAstNode node)
{
foreach (var n in context.ParentNodes())
{
if (n is XamlAstObjectNode parent)
{
switch (parent.Type.GetClrType().FullName)
{
case "Avalonia.Styling.Style":
var selectorProperty = parent.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
if (selectorProperty == null)
throw new XamlParseException("Can not find parent Style Selector.", node);
var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode;
if (selector?.TargetType != null)
return (selector, selector.TargetType);
throw new XamlParseException(
"Can not resolve parent Style Selector type", node);
case "Avalonia.Styling.ControlTheme":
var targetTypeProperty = parent.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "TargetType");
if (targetTypeProperty == null)
throw new XamlParseException("ControlTemplate has no TargetType.", parent);
break;
}
}
}
throw new XamlParseException("'Setter' is only valid inside a 'Style' or 'ControlTheme'.", node);
//var parent = context.ParentNodes().OfType<XamlAstObjectNode>()
// .FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style" ||
// p.Type.GetClrType().FullName == "Avalonia.Styling.ControlTheme");
//if (parent == null)
// throw new XamlParseException(
// "Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node);
//var selectorProperty = parent.Children.OfType<XamlAstXamlPropertyValueNode>()
// .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector" ||
// p.Property.GetClrProperty().Name == "TargetType");
//if (selectorProperty == null)
// throw new XamlParseException(
// "Can not find parent Style Selector or ControlTemplate TargetType", node);
//var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode;
//if (selector?.TargetType == null)
// throw new XamlParseException(
// "Can not resolve parent Style Selector type", node);
}
class SetterValueProperty : XamlAstClrProperty
{
public SetterValueProperty(IXamlLineInfo line, IXamlType setterType, IXamlType targetType,

53
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs

@ -0,0 +1,53 @@
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class ControlThemeTests : XamlTestBase
{
[Fact]
public void ControlTheme_Can_Be_StaticResource()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = $@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:u='using:Avalonia.Markup.Xaml.UnitTests.Xaml'>
<Window.Resources>
{ControlThemeXaml}
</Window.Resources>
<u:TestTemplatedControl Theme='{{StaticResource MyTheme}}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = Assert.IsType<TestTemplatedControl>(window.Content);
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.NotNull(button.Template);
var child = Assert.Single(button.GetVisualChildren());
var border = Assert.IsType<Border>(child);
Assert.Equal(Brushes.Red, border.Background);
}
}
private const string ControlThemeXaml = @"
<ControlTheme x:Key='MyTheme' TargetType='u:TestTemplatedControl'>
<Setter Property='Template'>
<ControlTemplate>
<Border/>
</ControlTemplate>
</Setter>
<Style Selector='^ /template/ Border'>
<Setter Property='Background' Value='Red'/>
</Style>
</ControlTheme>";
}
}

8
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs

@ -0,0 +1,8 @@
using Avalonia.Controls.Primitives;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class TestTemplatedControl : TemplatedControl
{
}
}
Loading…
Cancel
Save