Skip to content
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
5 changes: 5 additions & 0 deletions src/Controls/src/Core/ColumnDefinitionCollection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable disable
using System.Collections.Generic;
namespace Microsoft.Maui.Controls
{
/// <include file="../../docs/Microsoft.Maui.Controls/ColumnDefinitionCollection.xml" path="Type[@FullName='Microsoft.Maui.Controls.ColumnDefinitionCollection']/Docs/*" />
Expand All @@ -11,5 +12,9 @@ public ColumnDefinitionCollection() : base()
public ColumnDefinitionCollection(params ColumnDefinition[] definitions) : base(definitions)
{
}

internal ColumnDefinitionCollection(List<ColumnDefinition> definitions, bool copy) : base(definitions, copy)
{
}
}
}
61 changes: 48 additions & 13 deletions src/Controls/src/Core/ColumnDefinitionCollectionTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#nullable disable
using System;
using System.Buffers;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.Maui.Controls.Xaml;

namespace Microsoft.Maui.Controls
Expand All @@ -19,28 +21,61 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var strValue = value?.ToString();
var strValue = value as string ?? value?.ToString()
?? throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(ColumnDefinitionCollection)));

if (strValue != null)
// fast path for no value or empty string
if (strValue.Length == 0)
return new ColumnDefinitionCollection();

#if NET6_0_OR_GREATER
var unsplit = (ReadOnlySpan<char>)strValue;
var count = unsplit.Count(',') + 1;
var definitions = new List<ColumnDefinition>(count);
foreach (var range in unsplit.Split(','))
{
var length = GridLengthTypeConverter.ParseStringToGridLength(unsplit[range]);
definitions.Add(new ColumnDefinition(length));
}
#else
var lengths = strValue.Split(',');
var count = lengths.Length;
var definitions = new List<ColumnDefinition>(count);
foreach (var lengthStr in lengths)
{
var lengths = strValue.Split(',');
var converter = new GridLengthTypeConverter();
var definitions = new ColumnDefinition[lengths.Length];
for (var i = 0; i < lengths.Length; i++)
definitions[i] = new ColumnDefinition { Width = (GridLength)converter.ConvertFromInvariantString(lengths[i]) };
return new ColumnDefinitionCollection(definitions);
var length = GridLengthTypeConverter.ParseStringToGridLength(lengthStr);
definitions.Add(new ColumnDefinition(length));
}
#endif

throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", strValue, typeof(ColumnDefinitionCollection)));
return new ColumnDefinitionCollection(definitions, copy: false);
}


public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is not ColumnDefinitionCollection cdc)
if (value is not ColumnDefinitionCollection definitions)
throw new NotSupportedException();
var converter = new GridLengthTypeConverter();
return string.Join(", ", cdc.Select(cd => converter.ConvertToInvariantString(cd.Width)));

var count = definitions.Count;

// fast path for empty or single definitions
if (count == 0)
return string.Empty;
if (count == 1)
return GridLengthTypeConverter.ConvertToString(definitions[0].Width);

// for multiple items
var pool = ArrayPool<string>.Shared;
var rentedArray = pool.Rent(definitions.Count);
for (var i = 0; i < definitions.Count; i++)
{
var definition = definitions[i];
rentedArray[i] = GridLengthTypeConverter.ConvertToString(definition.Width);
}
var result = string.Join(", ", rentedArray, 0, definitions.Count);
pool.Return(rentedArray);
return result;
}
}
}
2 changes: 2 additions & 0 deletions src/Controls/src/Core/DefinitionCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class DefinitionCollection<T> : IList<T>, ICollection<T> where T : IDefin

internal DefinitionCollection(params T[] items) => _internalList = new List<T>(items);

internal DefinitionCollection(List<T> items, bool copy) => _internalList = copy ? new List<T>(items) : items;

public void Add(T item)
{
_internalList.Add(item);
Expand Down
64 changes: 50 additions & 14 deletions src/Controls/src/Core/GridLengthTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,69 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var strValue = value?.ToString();
var strValue = value as string ?? value?.ToString();

if (strValue == null)
if (strValue is null)
return null;

strValue = strValue.Trim();
if (string.Compare(strValue, "auto", StringComparison.OrdinalIgnoreCase) == 0)
return GridLength.Auto;
if (string.Compare(strValue, "*", StringComparison.OrdinalIgnoreCase) == 0)
return new GridLength(1, GridUnitType.Star);
if (strValue.EndsWith("*", StringComparison.Ordinal) && double.TryParse(strValue.Substring(0, strValue.Length - 1), NumberStyles.Number, CultureInfo.InvariantCulture, out var length))
return new GridLength(length, GridUnitType.Star);
if (double.TryParse(strValue, NumberStyles.Number, CultureInfo.InvariantCulture, out length))
return new GridLength(length);

throw new FormatException();
return ParseStringToGridLength(strValue);
}

#if NET6_0_OR_GREATER
internal static GridLength ParseStringToGridLength(ReadOnlySpan<char> value)
#else
internal static GridLength ParseStringToGridLength(string value)
#endif
{
value = value.Trim();

if (value.Length != 0)
{
if (value.Length == 4 && value.Equals("auto", StringComparison.OrdinalIgnoreCase))
return GridLength.Auto;

if (value.Length == 1 && value[0] == '*')
return GridLength.Star;

#if NET6_0_OR_GREATER
var lastChar = value[^1];
#else
var lastChar = value[value.Length - 1];
#endif
if (lastChar == '*')
{
#if NET6_0_OR_GREATER
var prefix = value[..^1];
#else
var prefix = value.Substring(0, value.Length - 1);
#endif

if (double.TryParse(prefix, NumberStyles.Number, CultureInfo.InvariantCulture, out var starLength))
return new GridLength(starLength, GridUnitType.Star);
}

if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out var absoluteLength))
return new GridLength(absoluteLength);
}

throw new FormatException($"Invalid GridLength format: {value.ToString()}");
}

public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is not GridLength length)
throw new NotSupportedException();

return ConvertToString(length);
}

internal static string ConvertToString(GridLength length)
{
if (length.IsAuto)
return "auto";
if (length.IsStar)
return $"{length.Value.ToString(CultureInfo.InvariantCulture)}*";
return $"{length.Value.ToString(CultureInfo.InvariantCulture)}";
return length.Value.ToString(CultureInfo.InvariantCulture);
}
}
}
5 changes: 5 additions & 0 deletions src/Controls/src/Core/RowDefinitionCollection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable disable
using System.Collections.Generic;
namespace Microsoft.Maui.Controls
{
/// <include file="../../docs/Microsoft.Maui.Controls/RowDefinitionCollection.xml" path="Type[@FullName='Microsoft.Maui.Controls.RowDefinitionCollection']/Docs/*" />
Expand All @@ -11,5 +12,9 @@ public RowDefinitionCollection() : base()
public RowDefinitionCollection(params RowDefinition[] definitions) : base(definitions)
{
}

internal RowDefinitionCollection(List<RowDefinition> definitions, bool copy) : base(definitions, copy)
{
}
}
}
63 changes: 49 additions & 14 deletions src/Controls/src/Core/RowDefinitionCollectionTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#nullable disable
using System;
using System.Buffers;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Microsoft.Maui.Controls.Xaml;

namespace Microsoft.Maui.Controls
Expand All @@ -15,31 +16,65 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT
=> sourceType == typeof(string);

public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
=> true;
=> destinationType == typeof(string);

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var strValue = value?.ToString();
var strValue = value as string ?? value?.ToString()
?? throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(RowDefinitionCollection)));

if (strValue != null)
// fast path for no value or empty string
if (strValue.Length == 0)
return new RowDefinitionCollection();

#if NET6_0_OR_GREATER
var unsplit = (ReadOnlySpan<char>)strValue;
var count = unsplit.Count(',') + 1;
var definitions = new List<RowDefinition>(count);
foreach (var range in unsplit.Split(','))
{
var length = GridLengthTypeConverter.ParseStringToGridLength(unsplit[range]);
definitions.Add(new RowDefinition(length));
}
#else
var lengths = strValue.Split(',');
var count = lengths.Length;
var definitions = new List<RowDefinition>(count);
foreach (var lengthStr in lengths)
{
var lengths = strValue.Split(',');
var converter = new GridLengthTypeConverter();
var definitions = new RowDefinition[lengths.Length];
for (var i = 0; i < lengths.Length; i++)
definitions[i] = new RowDefinition { Height = (GridLength)converter.ConvertFromInvariantString(lengths[i]) };
return new RowDefinitionCollection(definitions);
var length = GridLengthTypeConverter.ParseStringToGridLength(lengthStr);
definitions.Add(new RowDefinition(length));
}
#endif

throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", strValue, typeof(RowDefinitionCollection)));
return new RowDefinitionCollection(definitions, copy: false);
}


public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is not RowDefinitionCollection rdc)
if (value is not RowDefinitionCollection definitions)
throw new NotSupportedException();
var converter = new GridLengthTypeConverter();
return string.Join(", ", rdc.Select(rd => converter.ConvertToInvariantString(rd.Height)));

var count = definitions.Count;

// fast path for empty or single definitions
if (count == 0)
return string.Empty;
if (count == 1)
return GridLengthTypeConverter.ConvertToString(definitions[0].Height);

// for multiple items
var pool = ArrayPool<string>.Shared;
var rentedArray = pool.Rent(definitions.Count);
for (var i = 0; i < definitions.Count; i++)
{
var definition = definitions[i];
rentedArray[i] = GridLengthTypeConverter.ConvertToString(definition.Height);
}
var result = string.Join(", ", rentedArray, 0, definitions.Count);
pool.Return(rentedArray);
return result;
}
}
}
1 change: 1 addition & 0 deletions src/Controls/src/Core/SetterSpecificity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ internal readonly struct SetterSpecificity
public const ushort StyleLocal = 0x100;

public static readonly SetterSpecificity DefaultValue = new SetterSpecificity(0);
public static readonly SetterSpecificity LowestAppliedValue = new SetterSpecificity(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

Am I missing something or is this not used anywhere? Perhaps some leftover of a rebase?

public static readonly SetterSpecificity VisualStateSetter = new SetterSpecificity(ExtrasVsm, 0, 0, 0, 0, 0, 0, 0);
public static readonly SetterSpecificity FromBinding = new SetterSpecificity(0, 0, 0, 1, 0, 0, 0, 0);

Expand Down
75 changes: 75 additions & 0 deletions src/Core/tests/Benchmarks/Benchmarks/GridDefinitionBenchmarker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.ComponentModel;
using BenchmarkDotNet.Attributes;
using Microsoft.Maui.Controls;

namespace Microsoft.Maui.Handlers.Benchmarks;

[MemoryDiagnoser]
public class GridDefinitionBenchmarker
{
private ColumnDefinitionCollectionTypeConverter _columnDefinitionConverter;
private RowDefinitionCollectionTypeConverter _rowDefinitionConverter;

private ITypeDescriptorContext _context;
private ColumnDefinitionCollection _columns;
private RowDefinitionCollection _rows;

private const int Iterations = 100;

[GlobalSetup]
public void Setup()
{
_columnDefinitionConverter = new();
_rowDefinitionConverter = new();
_context = null; // replace with actual context if needed
_columns =
[
new ColumnDefinition(new GridLength(1, GridUnitType.Auto)),
new ColumnDefinition(new GridLength(2, GridUnitType.Star)),
new ColumnDefinition(new GridLength(3, GridUnitType.Absolute))
];

_rows =
[
new RowDefinition(new GridLength(1, GridUnitType.Auto)),
new RowDefinition(new GridLength(2, GridUnitType.Star)),
new RowDefinition(new GridLength(3, GridUnitType.Absolute))
];
}

[Benchmark]
public void ColumnConvertFrom()
{
for (int i = 0; i < Iterations; i++)
{
_columnDefinitionConverter.ConvertFromInvariantString(_context, "Auto, 2*, 3");
}
}

[Benchmark]
public void ColumnConvertTo()
{
for (int i = 0; i < Iterations; i++)
{
_columnDefinitionConverter.ConvertToInvariantString(_context, _columns);
}
}

[Benchmark]
public void RowConvertFrom()
{
for (int i = 0; i < Iterations; i++)
{
_rowDefinitionConverter.ConvertFromInvariantString(_context, "Auto, 2*, 3");
}
}

[Benchmark]
public void RowConvertTo()
{
for (int i = 0; i < Iterations; i++)
{
_rowDefinitionConverter.ConvertToInvariantString(_context, _rows);
}
}
}
Loading