Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add design time converters #19301

Merged
merged 2 commits into from Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 31 additions & 18 deletions src/Controls/src/Core.Design/AttributeTableBuilder.cs
Expand Up @@ -43,8 +43,7 @@ public AttributeTableBuilder()
new TypeConverterAttribute(typeof(ItemsLayoutDesignTypeConverter)));

AddMemberAttributes("Microsoft.Maui.Controls.InputView", "Keyboard",
new TypeConverterAttribute(typeof(KeyboardDesignTypeConverter)),
new TypeConverterAttribute(typeof(StringConverter)));
new TypeConverterAttribute(typeof(KeyboardDesignTypeConverter)));

AddMemberAttributes("Microsoft.Maui.Controls.EntryCell", "Keyboard",
new TypeConverterAttribute(typeof(KeyboardDesignTypeConverter)));
Expand All @@ -67,37 +66,51 @@ public AttributeTableBuilder()
//new System.Windows.Markup.MarkupExtensionReturnTypeAttribute (),
);

// Type level attributes
etvorun marked this conversation as resolved.
Show resolved Hide resolved
AddTypeAttributes("Microsoft.Maui.Controls.Brush", new TypeConverterAttribute(typeof(ColorDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Graphics.Color", new TypeConverterAttribute(typeof(ColorDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.GridLength", new TypeConverterAttribute(typeof(GridLengthDesignTypeConverter)));

AddTypeAttributes("Microsoft.Maui.Controls.Button+ButtonContentLayout", new TypeConverterAttribute(typeof(ButtonContentDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Controls.Compatibility.Constraint", new TypeConverterAttribute(typeof(ConstraintDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Controls.ConstraintExpression", new MarkupExtensionReturnTypeAttribute());
AddTypeAttributes("Microsoft.Maui.Controls.ImageSource", new TypeConverterAttribute(typeof(ImageSourceDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Controls.LayoutOptions", new TypeConverterAttribute(typeof(LayoutOptionsDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Controls.LinearItemsLayout", new TypeConverterAttribute(typeof(LinearItemsLayoutDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Controls.ResourcesChangedEventArgs", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.CornerRadius", new TypeConverterAttribute(typeof(CornerRadiusDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Graphics.Color", new TypeConverterAttribute(typeof(ColorDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Graphics.Point", new TypeConverterAttribute(typeof(PointTypeDesignConverter)));
AddTypeAttributes("Microsoft.Maui.Graphics.Rect", new TypeConverterAttribute(typeof(RectTypeDesignConverter)));
AddTypeAttributes("Microsoft.Maui.GridLength", new TypeConverterAttribute(typeof(GridLengthDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignContent", new TypeConverterAttribute(typeof(FlexAlignContentDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignItems", new TypeConverterAttribute(typeof(FlexAlignItemsDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignSelf", new TypeConverterAttribute(typeof(FlexAlignSelfDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexBasis", new TypeConverterAttribute(typeof(FlexBasisDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexDirection", new TypeConverterAttribute(typeof(FlexDirectionDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexJustify", new TypeConverterAttribute(typeof(FlexJustifyDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexWrap", new TypeConverterAttribute(typeof(FlexWrapDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Thickness", new TypeConverterAttribute(typeof(ThicknessTypeDesignConverter)));

// Property level attributes
etvorun marked this conversation as resolved.
Show resolved Hide resolved
AddMemberAttributes("Microsoft.Maui.Controls.AbsoluteLayout", "LayoutBounds", new TypeConverterAttribute(typeof(BoundsDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Button", "ContentLayout", new TypeConverterAttribute(typeof(ButtonContentDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Button", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Compatibility.AbsoluteLayout", "LayoutBounds", new TypeConverterAttribute(typeof(BoundsDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.DatePicker", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Editor", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Entry", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Grid", "ColumnDefinitions", new TypeConverterAttribute(typeof(GridLengthCollectionDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Grid", "RowDefinitions", new TypeConverterAttribute(typeof(GridLengthCollectionDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Image", "Source", new TypeConverterAttribute(typeof(ImageSourceDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.ImageButton", "Source", new TypeConverterAttribute(typeof(ImageSourceDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.ImageCell", "ImageSource", new TypeConverterAttribute(typeof(ImageSourceDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Label", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Picker", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.SearchBar", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.TimePicker", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.RadioButton", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.SearchBar", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.SearchHandler", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Shell", "FlyoutBackgroundImage", new TypeConverterAttribute(typeof(ImageSourceDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Span", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));

AddMemberAttributes("Microsoft.Maui.Controls.Grid", "RowDefinitions", new TypeConverterAttribute(typeof(GridLengthCollectionDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Grid", "ColumnDefinitions", new TypeConverterAttribute(typeof(GridLengthCollectionDesignTypeConverter)));

AddTypeAttributes("Microsoft.Maui.Layouts.FlexJustify", new TypeConverterAttribute(typeof(FlexJustifyDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexDirection", new TypeConverterAttribute(typeof(FlexDirectionDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignContent", new TypeConverterAttribute(typeof(FlexAlignContentDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignItems", new TypeConverterAttribute(typeof(FlexAlignItemsDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignSelf", new TypeConverterAttribute(typeof(FlexAlignSelfDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexWrap", new TypeConverterAttribute(typeof(FlexWrapDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Layouts.FlexBasis", new TypeConverterAttribute(typeof(FlexBasisDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.TimePicker", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.VisualElement", "IsVisible", new TypeConverterAttribute(typeof(VisibilityDesignTypeConverter)));
}

private void AddTypeAttributes(string typeName, params Attribute[] attribs)
Expand Down
41 changes: 41 additions & 0 deletions src/Controls/src/Core.Design/BoundsDesignTypeConverter.cs
@@ -0,0 +1,41 @@
using System;
using System.ComponentModel;
using System.Globalization;

namespace Microsoft.Maui.Controls.Design
{
public class BoundsDesignTypeConverter : StringConverter
{
public override bool IsValid(ITypeDescriptorContext context, object value)
{
// MUST MATCH BoundsTypeConverter.ConvertFrom
string strValue = value?.ToString();
if (string.IsNullOrEmpty(strValue))
return false;

string[] xywh = strValue.Split(',');
bool hasX, hasY, hasW, hasH;

hasX = (xywh.Length == 2 || xywh.Length == 4) && double.TryParse(xywh[0], NumberStyles.Number, CultureInfo.InvariantCulture, out _);
hasY = (xywh.Length == 2 || xywh.Length == 4) && double.TryParse(xywh[1], NumberStyles.Number, CultureInfo.InvariantCulture, out _);
hasW = xywh.Length == 4 && double.TryParse(xywh[2], NumberStyles.Number, CultureInfo.InvariantCulture, out _);
hasH = xywh.Length == 4 && double.TryParse(xywh[3], NumberStyles.Number, CultureInfo.InvariantCulture, out _);
if (!hasW && xywh.Length == 4 && string.Compare("AutoSize", xywh[2].Trim(), StringComparison.OrdinalIgnoreCase) == 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking out loud: Would this work

Suggested change
if (!hasW && xywh.Length == 4 && string.Compare("AutoSize", xywh[2].Trim(), StringComparison.OrdinalIgnoreCase) == 0)
if (!hasW && xywh.Length == 4 && string.Compare("AutoSize", xywh[2].AsSpan().Trim(), StringComparison.OrdinalIgnoreCase) == 0)

?

I just rembember there was: dotnet/runtime#75452 and https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.trim?view=net-7.0.

{
hasW = true;
}

if (!hasH && xywh.Length == 4 && string.Compare("AutoSize", xywh[3].Trim(), StringComparison.OrdinalIgnoreCase) == 0)
{
hasH = true;
}

if (hasX && hasY && xywh.Length == 2)
return true;
if (hasX && hasY && hasW && hasH && xywh.Length == 4)
return true;

return false;
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.ComponentModel;

namespace Microsoft.Maui.Controls.Design
{
public class ButtonContentDesignTypeConverter : StringConverter
{
private enum ImagePosition { Left, Top, Right, Bottom }; // MUST MATCH values of ButtonContentConverter.ImagePosition
etvorun marked this conversation as resolved.
Show resolved Hide resolved
private static readonly char[] Separators = { ',' };

public override bool IsValid(ITypeDescriptorContext context, object value)
{
// MUST MATCH ButtonContentConverter.ConvertFrom
string stringValue = value?.ToString();
if (string.IsNullOrEmpty(stringValue))
return false;

string[] parts = stringValue.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 2)
return false;

if (char.IsDigit(parts[0][0]))
{
// Examples: "5" or "10, Top"
if (!double.TryParse(parts[0], out _))
return false; // bogus number, e.g. 5a
if (parts.Length == 2 && !Enum.TryParse(parts[1], true, out ImagePosition _))
return false; // bogus position, e.g. "Hello"
}
else
{
// Examples: "Right" or "Bottom, 5"
if (!Enum.TryParse(parts[0], true, out ImagePosition _))
return false; // bogus position, e.g. "Hello"
if (parts.Length == 2 && !double.TryParse(parts[1], out _))
return false; // bogus number, e.g. 5a
}

return true;
}
}
}
15 changes: 15 additions & 0 deletions src/Controls/src/Core.Design/ConstraintDesignTypeConverter.cs
@@ -0,0 +1,15 @@
using System.ComponentModel;
using System.Globalization;

namespace Microsoft.Maui.Controls.Design
{
public class ConstraintDesignTypeConverter : StringConverter
{
public override bool IsValid(ITypeDescriptorContext context, object value)
{
// MUST MATCH ConstraintTypeConverter.ConvertFrom
var strValue = value?.ToString();
return (strValue != null && double.TryParse(strValue, NumberStyles.Number, CultureInfo.InvariantCulture, out _));
}
}
}
50 changes: 31 additions & 19 deletions src/Controls/src/Core.Design/Controls.Core.Design.csproj
Expand Up @@ -8,25 +8,37 @@
<_MauiDesignDllBuild Condition=" '$(OS)' != 'Unix' ">True</_MauiDesignDllBuild>
</PropertyGroup>
<ItemGroup Condition=" '$(_MauiDesignDllBuild)' == 'True' ">
<Reference Include="System.Xaml" />
<Compile Include="AttributeTableBuilder.cs" />
<Compile Include="ColorDesignTypeConverter.cs" />
<Compile Include="EnumConverter.cs" />
<Compile Include="NonExclusiveEnumConverter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RegisterMetadata.cs" />
<Compile Include="VisualDesignTypeConverter.cs" />
<Compile Include="ItemsLayoutDesignTypeConverter.cs" />
<Compile Include="KeyboardDesignTypeConverter.cs" />
<Compile Include="EasingDesignTypeConverter.cs" />
<Compile Include="FlowDirectionDesignTypeConverter.cs" />
<Compile Include="GridLengthCollectionDesignTypeConverter.cs" />
<Compile Include="GridLengthDesignTypeConverter.cs" />
<Compile Include="KnownValuesDesignTypeConverter.cs" />
<Compile Include="LayoutOptionsDesignTypeConverter.cs" />
<Compile Include="LinearItemsLayoutDesignTypeConverter.cs" />
<Compile Include="FontSizeDesignTypeConverter.cs" />
<Compile Include="FlexEnumDesignTypeConverters.cs" />
<Compile Include="AttributeTableBuilder.cs" />
<Compile Include="BoundsDesignTypeConverter.cs" />
<Compile Include="ButtonContentDesignTypeConverter.cs" />
<Compile Include="ColorDesignTypeConverter.cs" />
<Compile Include="ConstraintDesignTypeConverter.cs" />
<Compile Include="CornerRadiusDesignTypeConverter.cs" />
<Compile Include="DesignTypeConverterHelper.cs" />
<Compile Include="EasingDesignTypeConverter.cs" />
<Compile Include="EnumConverter.cs" />
<Compile Include="FlexEnumDesignTypeConverters.cs" />
<Compile Include="FlowDirectionDesignTypeConverter.cs" />
<Compile Include="FontSizeDesignTypeConverter.cs" />
<Compile Include="GridLengthCollectionDesignTypeConverter.cs" />
<Compile Include="GridLengthDesignTypeConverter.cs" />
<Compile Include="ImageSourceDesignTypeConverter.cs" />
<Compile Include="ItemsLayoutDesignTypeConverter.cs" />
<Compile Include="KeyboardDesignTypeConverter.cs" />
<Compile Include="KnownValuesDesignTypeConverter.cs" />
<Compile Include="LayoutOptionsDesignTypeConverter.cs" />
<Compile Include="LinearItemsLayoutDesignTypeConverter.cs" />
<Compile Include="NonExclusiveEnumConverter.cs" />
<Compile Include="PointTypeDesignConverter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RectTypeDesignConverter.cs" />
<Compile Include="RegisterMetadata.cs" />
<Compile Include="ThicknessTypeDesignConverter.cs" />
<Compile Include="VisibilityDesignTypeConverter.cs" />
<Compile Include="VisualDesignTypeConverter.cs" />
<Reference Include="System.Xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.DesignTools.Extensibility" Version="17.5.33428.366" />
</ItemGroup>
<!-- The IDE will look for a top level assembly resource called 'Microsoft.Maui.toolbox.xml' to -->
Expand Down
42 changes: 42 additions & 0 deletions src/Controls/src/Core.Design/CornerRadiusDesignTypeConverter.cs
@@ -0,0 +1,42 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Controls.Core.Design;

namespace Microsoft.Maui.Controls.Design
{
public class CornerRadiusDesignTypeConverter : StringConverter
{
public override bool IsValid(ITypeDescriptorContext context, object value)
{
// MUST MATCH CornerRadiusTypeConverter.ConvertFrom
var strValue = value?.ToString();
if (strValue != null)
{
if (strValue.IndexOf(",", StringComparison.Ordinal) != -1)
{
var parts = strValue.Split(',');

// Example: "1,2,3,4"
if (parts.Length == 4)
return parts.All(p => double.TryParse(p, NumberStyles.Number, CultureInfo.InvariantCulture, out _));

// Example: "1,a,b". CornerRadiusTypeConverter has unusual behavior
// for 2 or 3 token string. We match its behavior here
if (parts.Length < 4)
return double.TryParse(parts[0], NumberStyles.Number, CultureInfo.InvariantCulture, out _);
}
else
{
// Example: "1 2 3 4". Any count of numbers between 1 and 4 is valid.
// Note that CornerRadiusTypeConverter is sensitive to spaces, e.g.
// "1 2" is valid but "1 2" is not. We match its behavior here.
return DesignTypeConverterHelper.TryParseNumbers(strValue.Trim(), ' ', maxCount: 4) is int;
}
}

return false;
}
}
}
27 changes: 27 additions & 0 deletions src/Controls/src/Core.Design/DesignTypeConverterHelper.cs
@@ -0,0 +1,27 @@
using System.Globalization;
using System.Linq;

namespace Controls.Core.Design
{
internal static class DesignTypeConverterHelper
{
/// <summary>
/// Returns count of numbers in the string. Returns null if some of the values are invalid or total count exceeds max count.
/// </summary>
public static int? TryParseNumbers(string numberCollection, char separator, int maxCount)
{
// Examples: "1,2" or "1 2 3 4".
if (string.IsNullOrEmpty(numberCollection))
return null;

string[] parts = numberCollection.Split(separator);
if (parts.Length > maxCount)
return null; // too many numbers

if (parts.All(p => double.TryParse(p, NumberStyles.Number, CultureInfo.InvariantCulture, out _)))
etvorun marked this conversation as resolved.
Show resolved Hide resolved
return parts.Length; // all numbers are valid

return null;
}
}
}
17 changes: 17 additions & 0 deletions src/Controls/src/Core.Design/ImageSourceDesignTypeConverter.cs
@@ -0,0 +1,17 @@
using System;
using System.ComponentModel;

namespace Microsoft.Maui.Controls.Design
{
public class ImageSourceDesignTypeConverter : StringConverter
{
public override bool IsValid(ITypeDescriptorContext context, object value)
{
// MUST MATCH ImageSourceConverter.ConvertFrom
if (value?.ToString() is string strValue)
return Uri.TryCreate(strValue, UriKind.Absolute, out Uri _);

return false;
}
}
}
15 changes: 15 additions & 0 deletions src/Controls/src/Core.Design/PointTypeDesignConverter.cs
@@ -0,0 +1,15 @@
using System.ComponentModel;
using Controls.Core.Design;

namespace Microsoft.Maui.Controls.Design
{
public class PointTypeDesignConverter : StringConverter
{
public override bool IsValid(ITypeDescriptorContext context, object value)
{
// MUST MATCH Point.TryParse
int? count = DesignTypeConverterHelper.TryParseNumbers(value?.ToString(), ',', 2);
return count == 2;
}
}
}
15 changes: 15 additions & 0 deletions src/Controls/src/Core.Design/RectTypeDesignConverter.cs
@@ -0,0 +1,15 @@
using System.ComponentModel;
using Controls.Core.Design;

namespace Microsoft.Maui.Controls.Design
{
public class RectTypeDesignConverter : StringConverter
{
public override bool IsValid(ITypeDescriptorContext context, object value)
{
// MUST MATCH Rect.TryParse
int? count = DesignTypeConverterHelper.TryParseNumbers(value?.ToString(), ',', 4);
return count == 4;
}
}
}
30 changes: 30 additions & 0 deletions src/Controls/src/Core.Design/ThicknessTypeDesignConverter.cs
@@ -0,0 +1,30 @@
using System;
using System.ComponentModel;
using Controls.Core.Design;

namespace Microsoft.Maui.Controls.Design
{
public class ThicknessTypeDesignConverter : StringConverter
{
public override bool IsValid(ITypeDescriptorContext context, object value)
{
// MUST MATCH ThicknessTypeConverter.ConvertFrom
string strValue = value?.ToString()?.Trim();
if (string.IsNullOrEmpty(strValue))
return false;

if (strValue.IndexOf(",", StringComparison.Ordinal) != -1)
{
int? count = DesignTypeConverterHelper.TryParseNumbers(value?.ToString(), ',', maxCount: 4);
return count == 2 || count == 4;
}
else
{
// Example: "1 2 3 4". Any count of numbers between 1 and 4 is valid.
// Note that ThicknessTypeConverter is sensitive to spaces, e.g.
// "1 2" is valid but "1 2" is not. We match its behavior here.
return DesignTypeConverterHelper.TryParseNumbers(strValue.Trim(), ' ', maxCount: 4) is int;
}
}
}
}