From 1135895b73902a30b499cf50512729ae85163375 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 14 Apr 2023 10:00:51 +0200 Subject: [PATCH 1/2] Make Classes properties readonly. Makes the `StyledElement.Classes` and `Flyout.FlyoutPresenterClasses` properties readonly. Adds a transformer to the XAML compiler to convert attribute syntax classes setters to collection syntax so that they will be added to the existing `Classes` object. Prevents an unneeded allocation of a `Classes` object when setting `Classes` from XAML. --- src/Avalonia.Base/StyledElement.cs | 18 +------- .../DateTimePickers/DateTimePickerPanel.cs | 2 +- src/Avalonia.Controls/Flyouts/Flyout.cs | 12 +---- .../AvaloniaXamlIlCompiler.cs | 3 +- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 9 ---- .../AvaloniaXAmlIlClassesTransformer.cs | 46 +++++++++++++++++++ .../Xaml/BasicTests.cs | 32 +++++++++---- 7 files changed, 74 insertions(+), 48 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 731cb97161..5881efce1e 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -78,7 +78,7 @@ namespace Avalonia private static readonly ControlTheme s_invalidTheme = new ControlTheme(); private int _initCount; private string? _name; - private readonly Classes _classes = new Classes(); + private Classes? _classes; private ILogicalRoot? _logicalRoot; private IAvaloniaList? _logicalChildren; private IResourceDictionary? _resources; @@ -183,21 +183,7 @@ namespace Avalonia /// collection. /// /// - public Classes Classes - { - get - { - return _classes; - } - - set - { - if (_classes != value) - { - _classes.Replace(value); - } - } - } + public Classes Classes => _classes ??= new(); /// /// Gets or sets the control's data context. diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs index 9774b603e8..69b0ffe9a6 100644 --- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs +++ b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs @@ -454,7 +454,7 @@ namespace Avalonia.Controls.Primitives children.Add(new ListBoxItem { Height = ItemHeight, - Classes = new Classes($"{PanelType}Item"), + Classes = { $"{PanelType}Item" }, VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center, Focusable = false }); diff --git a/src/Avalonia.Controls/Flyouts/Flyout.cs b/src/Avalonia.Controls/Flyouts/Flyout.cs index cebcbb6562..8ec5cfa50a 100644 --- a/src/Avalonia.Controls/Flyouts/Flyout.cs +++ b/src/Avalonia.Controls/Flyouts/Flyout.cs @@ -18,17 +18,7 @@ namespace Avalonia.Controls /// /// Gets the Classes collection to apply to the FlyoutPresenter this Flyout is hosting /// - public Classes FlyoutPresenterClasses - { - get => _classes ??= new Classes(); - set - { - if (_classes is null) - _classes = value; - else if (_classes != value) - _classes.Replace(value); - } - } + public Classes FlyoutPresenterClasses => _classes ??= new Classes(); /// /// Defines the property. diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 197815f9a0..9579e37826 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -43,7 +43,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers()); InsertAfter( new AvaloniaXamlIlAvaloniaPropertyResolver(), - new AvaloniaXamlIlReorderClassesPropertiesTransformer() + new AvaloniaXamlIlReorderClassesPropertiesTransformer(), + new AvaloniaXAmlIlClassesTransformer() ); InsertBefore( diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 4068caac21..d8524cfd88 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -221,15 +221,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return ConvertDefinitionList(node, text, types, types.RowDefinitions, types.RowDefinition, "row definitions", out result); } - if (type.Equals(types.Classes)) - { - var classes = text.Split(' '); - var classNodes = classes.Select(c => new XamlAstTextNode(node, c, type: types.XamlIlTypes.String)).ToArray(); - - result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, types.Classes, types.XamlIlTypes.String, classNodes); - return true; - } - if (types.IBrush.IsAssignableFrom(type)) { if (Color.TryParse(text, out Color color)) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs new file mode 100644 index 0000000000..8e5a894071 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs @@ -0,0 +1,46 @@ +using System.Linq; +using XamlX.Ast; +using XamlX.Transform; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + /// + /// Converts an attribute syntax property value assignment to a collection syntax property + /// assignment. + /// + /// + /// Converts the property assignment `Classes="foo bar"` to: + /// + /// + /// + /// foo + /// bar + /// + /// + /// + class AvaloniaXAmlIlClassesTransformer : IXamlAstTransformer + { + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + var types = context.GetAvaloniaTypes(); + + if (node is XamlAstXamlPropertyValueNode propertyValue && + propertyValue.IsAttributeSyntax && + propertyValue.Property is XamlAstClrProperty property && + property.Getter?.ReturnType.Equals(types.Classes) == true && + propertyValue.Values.Count == 1 && + propertyValue.Values[0] is XamlAstTextNode value) + { + var classes = value.Text.Split(' '); + var stringType = context.Configuration.WellKnownTypes.String; + return new XamlAstXamlPropertyValueNode( + node, + property, + classes.Select(x => new XamlAstTextNode(node, x, type: stringType)), + false); + } + + return node; + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 25c0516744..5e30198d00 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -1,24 +1,20 @@ -using System; +using System.Collections; +using System.ComponentModel; +using System.Linq; +using System.Xml; using Avalonia.Collections; using Avalonia.Controls; +using Avalonia.Controls.Documents; using Avalonia.Controls.Presenters; using Avalonia.Data; using Avalonia.Data.Converters; -using Avalonia.Markup.Data; -using Avalonia.Markup.Xaml.Styling; using Avalonia.Markup.Xaml.Templates; using Avalonia.Media; using Avalonia.Media.Immutable; +using Avalonia.Metadata; using Avalonia.Styling; using Avalonia.UnitTests; -using System.Collections; -using System.ComponentModel; -using System.Linq; -using System.Xml; using Xunit; -using Avalonia.Controls.Documents; -using Avalonia.Metadata; -using Avalonia.Themes.Simple; namespace Avalonia.Markup.Xaml.UnitTests.Xaml { @@ -920,6 +916,22 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(new[] { "foo", "bar" }, target.Classes); } + [Fact] + public void Can_Specify_Button_Classes_Longform() + { + var xaml = @" +"; + var target = (Button)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.Equal(new[] { "foo", "bar" }, target.Classes); + } + [Fact] public void Can_Specify_Flyout_FlyoutPresenterClasses() { From 071691a764ff13255b0fe7761a696f677ded5238 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 14 Apr 2023 10:58:24 +0100 Subject: [PATCH 2/2] fix typo --- .../CompilerExtensions/AvaloniaXamlIlCompiler.cs | 2 +- .../Transformers/AvaloniaXAmlIlClassesTransformer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 9579e37826..5ca2b09eba 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -44,7 +44,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions InsertAfter( new AvaloniaXamlIlAvaloniaPropertyResolver(), new AvaloniaXamlIlReorderClassesPropertiesTransformer(), - new AvaloniaXAmlIlClassesTransformer() + new AvaloniaXamlIlClassesTransformer() ); InsertBefore( diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs index 8e5a894071..ed42247d29 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs @@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers /// /// /// - class AvaloniaXAmlIlClassesTransformer : IXamlAstTransformer + class AvaloniaXamlIlClassesTransformer : IXamlAstTransformer { public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) {