diff --git a/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs b/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs new file mode 100644 index 0000000000..f06e6f1ca9 --- /dev/null +++ b/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Avalonia.Metadata; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public sealed class AvaloniaListAttribute : Attribute +{ + public string[]? Separators { get; init; } + + // StringSplitOptions.TrimEntries = 2, but only on net6 target. + public StringSplitOptions SplitOptions { get; init; } = StringSplitOptions.RemoveEmptyEntries | (StringSplitOptions)2; +} diff --git a/src/Avalonia.Controls/Shapes/Polygon.cs b/src/Avalonia.Controls/Shapes/Polygon.cs index 70a45f3516..3ac5af2d33 100644 --- a/src/Avalonia.Controls/Shapes/Polygon.cs +++ b/src/Avalonia.Controls/Shapes/Polygon.cs @@ -1,19 +1,18 @@ -using System.Collections.Generic; using Avalonia.Media; namespace Avalonia.Controls.Shapes { public class Polygon : Shape { - public static readonly StyledProperty> PointsProperty = - AvaloniaProperty.Register>("Points"); + public static readonly StyledProperty PointsProperty = + AvaloniaProperty.Register("Points"); static Polygon() { AffectsGeometry(PointsProperty); } - public IList Points + public Points Points { get { return GetValue(PointsProperty); } set { SetValue(PointsProperty, value); } diff --git a/src/Avalonia.Controls/Shapes/Polyline.cs b/src/Avalonia.Controls/Shapes/Polyline.cs index 4b4bb3ffd0..e6edd7a599 100644 --- a/src/Avalonia.Controls/Shapes/Polyline.cs +++ b/src/Avalonia.Controls/Shapes/Polyline.cs @@ -5,8 +5,8 @@ namespace Avalonia.Controls.Shapes { public class Polyline: Shape { - public static readonly StyledProperty> PointsProperty = - AvaloniaProperty.Register>("Points"); + public static readonly StyledProperty PointsProperty = + AvaloniaProperty.Register("Points"); static Polyline() { @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Shapes AffectsGeometry(PointsProperty); } - public IList Points + public Points Points { get { return GetValue(PointsProperty); } set { SetValue(PointsProperty, value); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index d8524cfd88..65fa6f3e8b 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -198,6 +198,29 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node); } } + + if (type.Equals(types.ColumnDefinition) || type.Equals(types.RowDefinition)) + { + try + { + var gridLength = GridLength.Parse(text); + + result = new AvaloniaXamlIlGridLengthAstNode(node, types, gridLength); + + var definitionConstructorGridLength = type.GetConstructor(new List {types.GridLength}); + var lengthNode = new AvaloniaXamlIlGridLengthAstNode(node, types, gridLength); + var definitionTypeRef = new XamlAstClrTypeReference(node, type, false); + + result = new XamlAstNewClrObjectNode(node, definitionTypeRef, + definitionConstructorGridLength, new List {lengthNode}); + + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node); + } + } if (type.Equals(types.Cursor)) { @@ -211,16 +234,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } - if (type.Equals(types.ColumnDefinitions)) - { - return ConvertDefinitionList(node, text, types, types.ColumnDefinitions, types.ColumnDefinition, "column definitions", out result); - } - - if (type.Equals(types.RowDefinitions)) - { - return ConvertDefinitionList(node, text, types, types.RowDefinitions, types.RowDefinition, "row definitions", out result); - } - if (types.IBrush.IsAssignableFrom(type)) { if (Color.TryParse(text, out Color color)) @@ -295,46 +308,69 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } - result = null; - return false; - } - - private static bool ConvertDefinitionList( - IXamlAstValueNode node, - string text, - AvaloniaXamlIlWellKnownTypes types, - IXamlType listType, - IXamlType elementType, - string errorDisplayName, - out IXamlAstValueNode result) - { - try + // Keep it in the end, so more specific parsers can be applied. + var itemType = GetElementType(type, context.Configuration.WellKnownTypes); + if (itemType is not null + && types.AvaloniaList.MakeGenericType(itemType).IsAssignableFrom(type)) { - var lengths = GridLength.ParseLengths(text); - - var definitionTypeRef = new XamlAstClrTypeReference(node, elementType, false); - - var definitionConstructorGridLength = elementType.GetConstructor(new List {types.GridLength}); + const StringSplitOptions trimOption = (StringSplitOptions)2; // StringSplitOptions.TrimEntries + var separators = new[] { "," }; + var splitOptions = StringSplitOptions.RemoveEmptyEntries | trimOption; - IXamlAstValueNode CreateDefinitionNode(GridLength length) + var attribute = type.CustomAttributes.FirstOrDefault(a => a.Type == types.AvaloniaListAttribute); + if (attribute is not null) { - var lengthNode = new AvaloniaXamlIlGridLengthAstNode(node, types, length); - - return new XamlAstNewClrObjectNode(node, definitionTypeRef, - definitionConstructorGridLength, new List {lengthNode}); + if (attribute.Properties.TryGetValue("Separators", out var separatorsArray)) + { + separators = ((Array)separatorsArray)?.OfType().ToArray(); + } + if (attribute.Properties.TryGetValue("SplitOptions", out var splitOptionsObj)) + { + splitOptions = (StringSplitOptions)splitOptionsObj; + } + } + + var items = text.Split(separators, splitOptions ^ trimOption); + // Compiler targets netstandard, so we need to emulate StringSplitOptions.TrimEntries, if it was requested. + if (splitOptions.HasFlag(trimOption)) + { + items = items.Select(i => i.Trim()).ToArray(); } - var definitionNodes = - new List(lengths.Select(CreateDefinitionNode)); + if (itemType is null) + { + throw new XamlX.XamlLoadException($"Type '{type.Name}' is not a collection type.", node); + } - result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, listType, elementType, definitionNodes); + var nodes = new IXamlAstValueNode[items.Length]; + for (var index = 0; index < items.Length; index++) + { + var success = XamlTransformHelpers.TryGetCorrectlyTypedValue( + context, + new XamlAstTextNode(node, items[index], true, context.Configuration.WellKnownTypes.String), + itemType, out var itemNode); + if (!success) + { + result = null; + return false; + } + nodes[index] = itemNode; + } + + result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, type, itemType, nodes); return true; } - catch - { - throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a {errorDisplayName}", node); - } + + result = null; + return false; + } + + private static IXamlType GetElementType(IXamlType type, XamlTypeWellKnownTypes types) + { + return type.GetAllInterfaces().FirstOrDefault(i => + i.FullName.StartsWith(types.IEnumerableT.FullName))? + .GenericArguments[0]; } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 60a7d953ab..62ba2eb5a2 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -33,6 +33,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType InheritDataTypeFromItemsAttribute { get; } public IXamlType MarkupExtensionOptionAttribute { get; } public IXamlType MarkupExtensionDefaultOptionAttribute { get; } + public IXamlType AvaloniaListAttribute { get; } + public IXamlType AvaloniaList { get; } public IXamlType OnExtensionType { get; } public IXamlType UnsetValueType { get; } public IXamlType StyledElement { get; } @@ -141,6 +143,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers InheritDataTypeFromItemsAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.InheritDataTypeFromItemsAttribute"); MarkupExtensionOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionOptionAttribute"); MarkupExtensionDefaultOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionDefaultOptionAttribute"); + AvaloniaListAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.AvaloniaListAttribute"); + AvaloniaList = cfg.TypeSystem.GetType("Avalonia.Collections.AvaloniaList`1"); OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On"); AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, AvaloniaObject, AvaloniaProperty,