Skip to content
1 change: 1 addition & 0 deletions src/Controls/src/SourceGen/Controls.SourceGen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

<ItemGroup>
<ProjectReference Include="..\BindingSourceGen\Controls.BindingSourceGen.csproj" />
<ProjectReference Include="..\..\..\Graphics\src\Graphics\Graphics.csproj" />
Copy link
Contributor

Choose a reason for hiding this comment

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

nope

we don't want to have dependencies. can we replace that with a Compile-Include ?

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried to do this real quick and it turns out to be challenging because Color.cs as is pulls in a bunch of other classes and one of them requires MathF which is not netstandard2.0. I will look into this tomorrow and I'll see if I can easily move the parsing logic to a separate file which could be shared between the two projects.

</ItemGroup>

<ItemGroup>
Expand Down
77 changes: 7 additions & 70 deletions src/Controls/src/SourceGen/TypeConverters/ColorConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,88 +5,25 @@
using System.Xml;
using Microsoft.CodeAnalysis;
using Microsoft.Maui.Controls.Xaml;
using Microsoft.Maui.Graphics;

using static Microsoft.Maui.Controls.SourceGen.GeneratorHelpers;

namespace Microsoft.Maui.Controls.SourceGen.TypeConverters;

internal class ColorConverter : ISGTypeConverter
{
private static readonly HashSet<string> KnownNamedColors = new(StringComparer.OrdinalIgnoreCase)
{
"AliceBlue", "AntiqueWhite", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black",
"BlanchedAlmond", "Blue", "BlueViolet", "Brown", "BurlyWood", "CadetBlue", "Chartreuse",
"Chocolate", "Coral", "CornflowerBlue", "Cornsilk", "Crimson", "Cyan", "DarkBlue",
"DarkCyan", "DarkGoldenrod", "DarkGray", "DarkGreen", "DarkGrey", "DarkKhaki",
"DarkMagenta", "DarkOliveGreen", "DarkOrange", "DarkOrchid", "DarkRed", "DarkSalmon",
"DarkSeaGreen", "DarkSlateBlue", "DarkSlateGray", "DarkSlateGrey", "DarkTurquoise",
"DarkViolet", "DeepPink", "DeepSkyBlue", "DimGray", "DimGrey", "DodgerBlue", "Firebrick",
"FloralWhite", "ForestGreen", "Fuchsia", "Gainsboro", "GhostWhite", "Gold", "Goldenrod",
"Gray", "Green", "GreenYellow", "Grey", "Honeydew", "HotPink", "IndianRed", "Indigo",
"Ivory", "Khaki", "Lavender", "LavenderBlush", "LawnGreen", "LemonChiffon", "LightBlue",
"LightCoral", "LightCyan", "LightGoldenrodYellow", "LightGray", "LightGreen", "LightGrey",
"LightPink", "LightSalmon", "LightSeaGreen", "LightSkyBlue", "LightSlateGray", "LightSlateGrey",
"LightSteelBlue", "LightYellow", "Lime", "LimeGreen", "Linen", "Magenta", "Maroon",
"MediumAquamarine", "MediumBlue", "MediumOrchid", "MediumPurple", "MediumSeaGreen",
"MediumSlateBlue", "MediumSpringGreen", "MediumTurquoise", "MediumVioletRed", "MidnightBlue",
"MintCream", "MistyRose", "Moccasin", "NavajoWhite", "Navy", "OldLace", "Olive", "OliveDrab",
"Orange", "OrangeRed", "Orchid", "PaleGoldenrod", "PaleGreen", "PaleTurquoise", "PaleVioletRed",
"PapayaWhip", "PeachPuff", "Peru", "Pink", "Plum", "PowderBlue", "Purple", "Red", "RosyBrown",
"RoyalBlue", "SaddleBrown", "Salmon", "SandyBrown", "SeaGreen", "SeaShell", "Sienna", "Silver",
"SkyBlue", "SlateBlue", "SlateGray", "SlateGrey", "Snow", "SpringGreen", "SteelBlue", "Tan",
"Teal", "Thistle", "Tomato", "Transparent", "Turquoise", "Violet", "Wheat", "White",
"WhiteSmoke", "Yellow", "YellowGreen"
};

// #rgb, #rrggbb, #aarrggbb are all valid
private const string RxColorHexPattern = @"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}([0-9a-fA-F]{2})?)$";
private static readonly Lazy<Regex> RxColorHex = new(() => new Regex(RxColorHexPattern, RegexOptions.Compiled | RegexOptions.Singleline));

// RGB, RGBA, HSL, HSLA, HSV, HSVA function patterns
private const string RxFuncPattern = "^(?<func>rgba|argb|rgb|hsla|hsl|hsva|hsv)\\(((?<v>\\d%?),){2}((?<v>\\d%?)|(?<v>\\d%?),(?<v>\\d%?))\\);?$";
private static readonly Lazy<Regex> RxFuncExpr = new(() => new Regex(RxFuncPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline));

public IEnumerable<string> SupportedTypes => new[] { "Color", "Microsoft.Maui.Graphics.Color" };

public string Convert(string value, BaseNode node, ITypeSymbol toType, SourceGenContext context, LocalVariable? parentVar = null)
{
var xmlLineInfo = (IXmlLineInfo)node;
if (!string.IsNullOrEmpty(value))
if (Color.TryParse(value, out var color))
{
// Any named colors are ok. Surrounding white spaces are ok. Case insensitive.
var actualColorName = KnownNamedColors.FirstOrDefault(c => string.Equals(c, value.Trim(), StringComparison.OrdinalIgnoreCase));
if (actualColorName is not null)
{
var colorsType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Colors")!;
return $"{colorsType.ToFQDisplayString()}.{actualColorName}";
}

// Check for HEX Color string
if (RxColorHex.Value.IsMatch(value))
{
var colorType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Color")!;
return $"{colorType.ToFQDisplayString()}.FromArgb(\"{value}\")";
}

var match = RxFuncExpr.Value.Match(value);

var funcName = match?.Groups?["func"]?.Value;
var funcValues = match?.Groups?["v"]?.Captures;

if (!string.IsNullOrEmpty(funcName) && funcValues is not null)
{
// ie: argb() needs 4 parameters:
if (funcValues.Count == funcName?.Length)
{
var colorType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Color")!;
return $"{colorType.ToFQDisplayString()}.Parse(\"{value}\")";
}
}

// As a last resort, try Color.Parse() for any other valid color formats
var colorType2 = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Color")!;
return $"{colorType2.ToFQDisplayString()}.Parse(\"{value}\")";
var colorType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Color")!;
return $"new {colorType.ToFQDisplayString()}({FormatInvariant(color.Red)}f, {FormatInvariant(color.Green)}f, {FormatInvariant(color.Blue)}f, {FormatInvariant(color.Alpha)}f) /* {value} */";
}

context.ReportConversionFailed(xmlLineInfo, value, toType, Descriptors.ConversionFailed);
context.ReportConversionFailed((IXmlLineInfo)node, value, toType, Descriptors.ConversionFailed);
return "default";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private partial void InitializeComponent()
#line 8 "{{testXamlFilePath}}"
setter.Value = "Pink";
#line default
var setter2 = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.Label.TextColorProperty, Value = global::Microsoft.Maui.Graphics.Colors.Pink};
var setter2 = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.Label.TextColorProperty, Value = new global::Microsoft.Maui.Graphics.Color(1f, 0.7529412f, 0.79607844f, 1f) /* Pink */};
if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(setter2!) == null)
global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter2!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 8, 14);
#line 8 "{{testXamlFilePath}}"
Expand Down
Loading