From bf6533952fdf933f2545c53cf992495df0bd87d5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 25 May 2020 08:18:33 +0200 Subject: [PATCH] WIP --- samples/ControlCatalog/App.xaml | 3 +- samples/ControlCatalog/CustomControl.cs | 21 +++++++ samples/ControlCatalog/CustomControl.xaml | 12 ++++ samples/ControlCatalog/MainWindow.xaml | 2 +- .../Properties/launchSettings.json | 9 +++ .../XamlCompilerTaskExecutor.cs | 25 +++++++- src/Avalonia.Controls/ControlTheme.cs | 5 ++ .../Avalonia.Markup.Xaml.csproj | 1 + .../AvaloniaXamlIlCompiler.cs | 1 + ...iaXamlIlControlThemeMetadataTransformer.cs | 36 ++++++++++++ .../AvaloniaXamlIlSetterTransformer.cs | 58 +++++++++++++------ .../AvaloniaXamlIlWellKnownTypes.cs | 5 ++ 12 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 samples/ControlCatalog/CustomControl.cs create mode 100644 samples/ControlCatalog/CustomControl.xaml create mode 100644 src/Avalonia.Build.Tasks/Properties/launchSettings.json create mode 100644 src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeMetadataTransformer.cs diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index e40509dfda..56eef3a2ba 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -1,6 +1,7 @@ + x:Class="ControlCatalog.App" + DataContext="1"> diff --git a/samples/ControlCatalog/CustomControl.cs b/samples/ControlCatalog/CustomControl.cs new file mode 100644 index 0000000000..fcd5fc8aa3 --- /dev/null +++ b/samples/ControlCatalog/CustomControl.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia; +using Avalonia.Controls.Primitives; +using Avalonia.Media; + +namespace ControlCatalog +{ + public class CustomControl : TemplatedControl + { + public static readonly StyledProperty StrokeProperty = + AvaloniaProperty.Register(nameof(Stroke)); + + public IBrush Stroke + { + get => GetValue(StrokeProperty); + set => SetValue(StrokeProperty, value); + } + } +} diff --git a/samples/ControlCatalog/CustomControl.xaml b/samples/ControlCatalog/CustomControl.xaml new file mode 100644 index 0000000000..d5bff61098 --- /dev/null +++ b/samples/ControlCatalog/CustomControl.xaml @@ -0,0 +1,12 @@ + + + + + Custom Control + + + + diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 935db20757..f36f51578d 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -71,6 +71,6 @@ - + diff --git a/src/Avalonia.Build.Tasks/Properties/launchSettings.json b/src/Avalonia.Build.Tasks/Properties/launchSettings.json new file mode 100644 index 0000000000..88836a7f48 --- /dev/null +++ b/src/Avalonia.Build.Tasks/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "Avalonia.Build.Tasks": { + "commandName": "Executable", + "executablePath": "dotnet", + "commandLineArgs": "D:\\projects\\AvaloniaUI\\Avalonia\\src\\Avalonia.Build.Tasks\\bin\\Debug\\netcoreapp2.0\\Avalonia.Build.Tasks.dll D:\\projects\\AvaloniaUI\\Avalonia\\samples\\ControlCatalog\\obj\\Debug\\netstandard2.0\\Avalonia\\" + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 3b69109e68..bd3af72ee4 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -156,8 +156,10 @@ namespace Avalonia.Build.Tasks new XamlIlAstClrTypeReference(classDirective, classType, false)); initialRoot.Children.Remove(classDirective); } - - + + var controlThemeForDirective = initialRoot.Children.OfType() + .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "DefaultControlThemeFor"); + compiler.Transform(parsed); var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate"; var buildName = classType == null ? "Build:" + res.Name : null; @@ -165,7 +167,6 @@ namespace Avalonia.Build.Tasks var classTypeDefinition = classType == null ? null : typeSystem.GetTypeReference(classType).Resolve(); - var populateBuilder = classTypeDefinition == null ? builder : typeSystem.CreateTypeBuilder(classTypeDefinition); @@ -286,6 +287,24 @@ namespace Avalonia.Build.Tasks } + if (controlThemeForDirective != null) + { + var controlType = ((XamlIlTypeExtensionNode)controlThemeForDirective.Values[0]).Value.GetClrType(); + + if (controlType.Methods.Any(x => x.Name == "GetDefaultControlTheme" && x.Parameters.Count == 0)) + { + throw new XamlIlParseException($"Default control theme provided for '{controlType.FullName}' " + + "but the class already overrides GetDefaultControlTheme.", classDirective); + } + + var controlTypeDefinition = typeSystem.GetTypeReference(controlType).Resolve(); + var themeBuilder = typeSystem.CreateTypeBuilder(controlTypeDefinition); + var method = themeBuilder.DefineMethod(typeSystem.FindType("Avalonia.Styling.IStyle"), + new IXamlIlType[0], "GetDefaultControlTheme", false, false, false, + typeSystem.FindType("Avalonia.Controls.Primitives.TemplatedControl") + .FindMethod(x => x.Name == "GetDefaultControlTheme" && x.Parameters.Count == 0)); + } + if (buildName != null || classTypeDefinition != null) { var compiledBuildMethod = buildName == null ? diff --git a/src/Avalonia.Controls/ControlTheme.cs b/src/Avalonia.Controls/ControlTheme.cs index 9f003e309a..5569a684e2 100644 --- a/src/Avalonia.Controls/ControlTheme.cs +++ b/src/Avalonia.Controls/ControlTheme.cs @@ -18,6 +18,11 @@ namespace Avalonia.Controls /// public Styles Styles => _styles ??= new Styles(Owner); + /// + /// Gets or sets the control type that the theme applies to. + /// + public Type? TargetType { get; set; } + protected override IReadOnlyList GetChildrenCore() { return (IReadOnlyList?)_styles ?? Array.Empty(); diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 06c5375520..96c1b40e88 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -39,6 +39,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs index b84f50fa8d..9a5a718a0b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -45,6 +45,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions InsertBefore( new AvaloniaXamlIlSelectorTransformer(), new AvaloniaXamlIlSetterTransformer(), + new AvaloniaXamlIlControlThemeMetadataTransformer(), new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), new AvaloniaXamlIlConstructorServiceProviderTransformer(), new AvaloniaXamlIlTransitionsTypeMetadataTransformer() diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeMetadataTransformer.cs new file mode 100644 index 0000000000..b4ab735347 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeMetadataTransformer.cs @@ -0,0 +1,36 @@ +using System.Linq; +using XamlIl; +using XamlIl.Ast; +using XamlIl.Transform; +using XamlIl.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + class AvaloniaXamlIlControlThemeMetadataTransformer : IXamlIlAstTransformer + { + public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) + { + if (!(node is XamlIlAstObjectNode on + && on.Type.GetClrType().FullName == "Avalonia.Controls.ControlTheme")) + return node; + + if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode) + // Deja vu. I've just been in this place before + return node; + + var themeFor = on.Children.OfType().FirstOrDefault(ch => + ch.Name == "DefaultControlThemeFor"); + + if (themeFor?.Values.FirstOrDefault() is XamlIlTypeExtensionNode tn) + { + on.Children.Remove(themeFor); + return new AvaloniaXamlIlTargetTypeMetadataNode(on, tn.Value, + AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style); + } + else + { + throw new XamlIlParseException("ControlTheme does not have a x:DefaultControlThemeFor attribute", node); + } + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index 629e2562d3..1e6eb2aef8 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -17,23 +17,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers && on.Type.GetClrType().FullName == "Avalonia.Styling.Setter")) return node; - var parent = context.ParentNodes().OfType() - .FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style"); - - if (parent == null) - throw new XamlIlParseException( - "Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node); - var selectorProperty = parent.Children.OfType() - .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector"); - if (selectorProperty == null) - throw new XamlIlParseException( - "Can not find parent Style Selector", node); - var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode; - if (selector?.TargetType == null) - throw new XamlIlParseException( - "Can not resolve parent Style Selector type", node); - - + var targetType = GetTargetType(context, on); var property = @on.Children.OfType() .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property"); if (property == null) @@ -43,9 +27,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (propertyName == null) throw new XamlIlParseException("Setter.Property must be a string", node); - var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, - new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]); + new XamlIlAstClrTypeReference(on, targetType, false), property.Values[0]); property.Values = new List { avaloniaPropertyNode @@ -69,6 +52,43 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return node; } + IXamlIlType GetTargetType(XamlIlAstTransformationContext context, XamlIlAstObjectNode node) + { + var parent = context.ParentNodes().OfType() + .FirstOrDefault(p => + p.Type.GetClrType().FullName == "Avalonia.Styling.Style" || + p.Type.GetClrType().FullName == "Avalonia.Controls.ControlTheme"); + + if (parent == null) + throw new XamlIlParseException( + "Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style or Avalonia.Controls.ControlTheme", node); + + if (parent.Type.GetClrType().FullName == "Avalonia.Styling.Style") + { + var selectorProperty = parent.Children.OfType() + .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector"); + if (selectorProperty == null) + throw new XamlIlParseException( + "Can not find parent Style Selector", node); + var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode; + if (selector?.TargetType == null) + throw new XamlIlParseException( + "Can not resolve parent Style Selector type", node); + return selector.TargetType; + } + else + { + var themeFor = parent.Children.OfType() + .FirstOrDefault(ch => ch.Name == "DefaultControlThemeFor"); + + if (themeFor?.Values.FirstOrDefault() is XamlIlTypeExtensionNode tn) + return tn.Value.GetClrType(); + else + throw new XamlIlParseException( + "ControlTheme must have a x:DefaultControlThemeFor directive.", node); + } + } + class SetterValueProperty : XamlIlAstClrProperty { public SetterValueProperty(IXamlIlLineInfo line, IXamlIlType setterType, IXamlIlType targetType, diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 1efae902c6..4617475eaa 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -1,3 +1,4 @@ +using System.Linq; using XamlIl.Transform; using XamlIl.TypeSystem; @@ -26,6 +27,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlIlType INameScope { get; } public IXamlIlMethod INameScopeRegister { get; } public IXamlIlMethod INameScopeComplete { get; } + public IXamlIlType ControlTheme { get; } + public IXamlIlProperty ControlThemeTargetType { get; } public AvaloniaXamlIlWellKnownTypes(XamlIlAstTransformationContext ctx) { @@ -61,6 +64,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers NameScope = ctx.Configuration.TypeSystem.GetType("Avalonia.Controls.NameScope"); NameScopeSetNameScope = NameScope.GetMethod(new FindMethodMethodSignature("SetNameScope", XamlIlTypes.Void, StyledElement, INameScope) {IsStatic = true}); + ControlTheme = ctx.Configuration.TypeSystem.GetType("Avalonia.Controls.ControlTheme"); + ControlThemeTargetType = ControlTheme.GetAllProperties().Single(x => x.Name == "TargetType"); AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", XamlIlTypes.Void, false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority);