Browse Source

WIP

feature/2769-control-themes
Steven Kirk 6 years ago
parent
commit
bf6533952f
  1. 3
      samples/ControlCatalog/App.xaml
  2. 21
      samples/ControlCatalog/CustomControl.cs
  3. 12
      samples/ControlCatalog/CustomControl.xaml
  4. 2
      samples/ControlCatalog/MainWindow.xaml
  5. 9
      src/Avalonia.Build.Tasks/Properties/launchSettings.json
  6. 25
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  7. 5
      src/Avalonia.Controls/ControlTheme.cs
  8. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  9. 1
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  10. 36
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeMetadataTransformer.cs
  11. 58
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  12. 5
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

3
samples/ControlCatalog/App.xaml

@ -1,6 +1,7 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.App">
x:Class="ControlCatalog.App"
DataContext="1">
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>

21
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<IBrush> StrokeProperty =
AvaloniaProperty.Register<CustomControl, IBrush>(nameof(Stroke));
public IBrush Stroke
{
get => GetValue(StrokeProperty);
set => SetValue(StrokeProperty, value);
}
}
}

12
samples/ControlCatalog/CustomControl.xaml

@ -0,0 +1,12 @@
<ControlTheme xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ControlCatalog"
x:DefaultControlThemeFor="{x:Type local:CustomControl}">
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<Border Background="Yellow" BorderBrush="{TemplateBinding Stroke}" BorderThickness="1">
<TextBlock>Custom Control</TextBlock>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>

2
samples/ControlCatalog/MainWindow.xaml

@ -71,6 +71,6 @@
<MenuItem Header="About" Command="{Binding AboutCommand}" />
</MenuItem>
</Menu>
<local:MainView />
<local:CustomControl />
</DockPanel>
</Window>

9
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\\"
}
}
}

25
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<XamlIlAstXmlDirective>()
.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 ?

5
src/Avalonia.Controls/ControlTheme.cs

@ -18,6 +18,11 @@ namespace Avalonia.Controls
/// </summary>
public Styles Styles => _styles ??= new Styles(Owner);
/// <summary>
/// Gets or sets the control type that the theme applies to.
/// </summary>
public Type? TargetType { get; set; }
protected override IReadOnlyList<IStyle> GetChildrenCore()
{
return (IReadOnlyList<IStyle>?)_styles ?? Array.Empty<IStyle>();

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -39,6 +39,7 @@
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaBindingExtensionHackTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlAvaloniaPropertyResolver.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlConstructorServiceProviderTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlControlThemeMetadataTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlDesignPropertiesTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlMetadataRemover.cs" />

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

@ -45,6 +45,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertBefore<XamlIlContentConvertTransformer>(
new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlControlThemeMetadataTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer()

36
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<XamlIlAstXmlDirective>().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);
}
}
}
}

58
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<XamlIlAstObjectNode>()
.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<XamlIlAstXamlPropertyValueNode>()
.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<XamlIlAstXamlPropertyValueNode>()
.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<IXamlIlAstValueNode>
{
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<XamlIlAstObjectNode>()
.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<XamlIlAstXamlPropertyValueNode>()
.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<XamlIlAstXmlDirective>()
.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,

5
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);

Loading…
Cancel
Save