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 all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 31 additions & 18 deletions src/Controls/src/Core.Design/AttributeTableBuilder.cs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Contributor

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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.ComponentModel;

namespace Microsoft.Maui.Controls.Design
{
public class ButtonContentDesignTypeConverter : StringConverter
{
private static readonly char[] Separators = { ',' };

// MUST MATCH values of ButtonContentConverter.ImagePosition. NOTE that we use enum rather than strings
// to better match ButtonContentConverter. First, Enum.Parse will accept int values like 1 or 20. Second,
// values passed to converter contain numbers and enum values, i.e. there is a possibility that we might
// need to handle ints in place of enums. ButtonContentConverter does not properly handle corner cases.
// For example, it does not trim incoming strings, so " 15" will be converted as
// ButtonContentLayout((ImagePorition)15, DefaultSpacing/*10*/)
private enum ImagePosition { Left, Top, Right, Bottom };

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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
49 changes: 49 additions & 0 deletions src/Controls/src/Core.Design/CornerRadiusDesignTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.ComponentModel;
using System.Globalization;
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)
{
foreach (string part in parts)
{
if (!double.TryParse(part, NumberStyles.Number, CultureInfo.InvariantCulture, out _))
return false;
}

return true;
}

// 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;
}
}
}
28 changes: 28 additions & 0 deletions src/Controls/src/Core.Design/DesignTypeConverterHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Globalization;

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

foreach (string part in parts)
{
if (!double.TryParse(part, NumberStyles.Number, CultureInfo.InvariantCulture, out _))
return null; // invalid number found
}
return parts.Length; // all numbers are valid
}
}
}
17 changes: 17 additions & 0 deletions src/Controls/src/Core.Design/ImageSourceDesignTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Loading
Loading