Skip to content

Commit

Permalink
Remove LINQ and reflection from EasingTypeConverter (#19260)
Browse files Browse the repository at this point in the history
* Clean up / Linq removal

Cleaned up the methods and added more up to date .NET syntax as well as removing the usage of LINQ.

* Removed ToLowerInvariant

As the switch makes use of nameof the usage of ToLowerInvariant is redundant.

* Fix return value / further improvements

The value now returns an easing instead of a string (oops!), I have also implemented a local method to check case sensitive strings for the switch statement allowing the same functionality as previous.

* Tests

* Update EasingTypeConverter.cs

---------

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
  • Loading branch information
imememani and mattleibow authored Jun 7, 2024
1 parent 9d71d32 commit d8d9488
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 72 deletions.
105 changes: 40 additions & 65 deletions src/Core/src/Converters/EasingTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using static Microsoft.Maui.Easing;

#nullable disable
Expand All @@ -20,81 +18,58 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var strValue = value?.ToString();
if (value is null)
return null;

var strValue = value as string ?? value.ToString();

if (string.IsNullOrWhiteSpace(strValue))
return null;

strValue = strValue?.Trim() ?? "";
var parts = strValue.Split('.');

if (parts.Length == 2 && parts[0] == nameof(Easing))
strValue = parts[parts.Length - 1];

if (strValue.Equals(nameof(Linear), StringComparison.OrdinalIgnoreCase))
return Linear;
if (strValue.Equals(nameof(SinIn), StringComparison.OrdinalIgnoreCase))
return SinIn;
if (strValue.Equals(nameof(SinOut), StringComparison.OrdinalIgnoreCase))
return SinOut;
if (strValue.Equals(nameof(SinInOut), StringComparison.OrdinalIgnoreCase))
return SinInOut;
if (strValue.Equals(nameof(CubicIn), StringComparison.OrdinalIgnoreCase))
return CubicIn;
if (strValue.Equals(nameof(CubicOut), StringComparison.OrdinalIgnoreCase))
return CubicOut;
if (strValue.Equals(nameof(CubicInOut), StringComparison.OrdinalIgnoreCase))
return CubicInOut;
if (strValue.Equals(nameof(BounceIn), StringComparison.OrdinalIgnoreCase))
return BounceIn;
if (strValue.Equals(nameof(BounceOut), StringComparison.OrdinalIgnoreCase))
return BounceOut;
if (strValue.Equals(nameof(SpringIn), StringComparison.OrdinalIgnoreCase))
return SpringIn;
if (strValue.Equals(nameof(SpringOut), StringComparison.OrdinalIgnoreCase))
return SpringOut;

var fallbackValue = typeof(Easing)
.GetTypeInfo()
.DeclaredFields
.FirstOrDefault(f => f.Name.Equals(strValue, StringComparison.OrdinalIgnoreCase))
?.GetValue(null);

if (fallbackValue is Easing fallbackEasing)
return fallbackEasing;

throw new InvalidOperationException($"Cannot convert \"{strValue}\" into {typeof(Easing)}");
if (parts.Length == 2 && Compare(parts[0], nameof(Easing)))
strValue = parts[1];

return strValue switch
{
_ when Compare(strValue, nameof(Linear)) => Linear,
_ when Compare(strValue, nameof(SinIn)) => SinIn,
_ when Compare(strValue, nameof(SinOut)) => SinOut,
_ when Compare(strValue, nameof(SinInOut)) => SinInOut,
_ when Compare(strValue, nameof(CubicIn)) => CubicIn,
_ when Compare(strValue, nameof(CubicOut)) => CubicOut,
_ when Compare(strValue, nameof(CubicInOut)) => CubicInOut,
_ when Compare(strValue, nameof(BounceIn)) => BounceIn,
_ when Compare(strValue, nameof(BounceOut)) => BounceOut,
_ when Compare(strValue, nameof(SpringIn)) => SpringIn,
_ when Compare(strValue, nameof(SpringOut)) => SpringOut,
_ => throw new InvalidOperationException($"Cannot convert \"{strValue}\" into {typeof(Easing)}")
};

static bool Compare(string left, string right) =>
left.Equals(right, StringComparison.OrdinalIgnoreCase);
}

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

if (easing == Linear)
return nameof(Linear);
if (easing == SinIn)
return nameof(SinIn);
if (easing == SinOut)
return nameof(SinOut);
if (easing == SinInOut)
return nameof(SinInOut);
if (easing == CubicIn)
return nameof(CubicIn);
if (easing == CubicOut)
return nameof(CubicOut);
if (easing == CubicInOut)
return nameof(CubicInOut);
if (easing == BounceIn)
return nameof(BounceIn);
if (easing == BounceOut)
return nameof(BounceOut);
if (easing == SpringIn)
return nameof(SpringIn);
if (easing == SpringOut)
return nameof(SpringOut);

throw new NotSupportedException();
return easing switch
{
_ when easing.Equals(Linear) => nameof(Linear),
_ when easing.Equals(SinIn) => nameof(SinIn),
_ when easing.Equals(SinOut) => nameof(SinOut),
_ when easing.Equals(SinInOut) => nameof(SinInOut),
_ when easing.Equals(CubicIn) => nameof(CubicIn),
_ when easing.Equals(CubicOut) => nameof(CubicOut),
_ when easing.Equals(CubicInOut) => nameof(CubicInOut),
_ when easing.Equals(BounceIn) => nameof(BounceIn),
_ when easing.Equals(BounceOut) => nameof(BounceOut),
_ when easing.Equals(SpringIn) => nameof(SpringIn),
_ when easing.Equals(SpringOut) => nameof(SpringOut),
_ => throw new NotSupportedException(),
};
}

public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
using Microsoft.Maui.Converters;
using Xunit;

namespace Microsoft.Maui.Controls.Core.UnitTests
namespace Microsoft.Maui.UnitTests
{

public class EasingTests : BaseTestFixture
[Category(TestCategory.Animations)]
public class EasingTests
{
[Theory, MemberData(nameof(TestDataHelpers.Range), 0, 10, 1, MemberType = typeof(TestDataHelpers))]
[Theory]
[InlineData(0.0)]
[InlineData(1.0)]
[InlineData(2.0)]
[InlineData(5.0)]
[InlineData(8.0)]
[InlineData(9.0)]
[InlineData(10.0)]
public void Linear(double input)
{
Assert.Equal(input, Easing.Linear.Ease(input));
Expand All @@ -19,6 +26,7 @@ public void Linear(double input)
public void AllRunFromZeroToOne(double val)
{
const double epsilon = 0.001;

Assert.True(Math.Abs(val - Easing.Linear.Ease(val)) < epsilon);
Assert.True(Math.Abs(val - Easing.BounceIn.Ease(val)) < epsilon);
Assert.True(Math.Abs(val - Easing.BounceOut.Ease(val)) < epsilon);
Expand All @@ -33,45 +41,97 @@ public void AllRunFromZeroToOne(double val)
}

[Fact]
public void TestEasingTypeConverter()
public void CanConvert()
{
var converter = new EasingTypeConverter();

Assert.True(converter.CanConvertFrom(typeof(string)));
Assert.Null(converter.ConvertFromInvariantString(null));
Assert.Null(converter.ConvertFromInvariantString(string.Empty));
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void NonTextEasingsAreNull(string input)
{
var converter = new EasingTypeConverter();

Assert.Null(converter.ConvertFromInvariantString(input));
}

[Fact]
public void CanConvertFromEasingNameToEasing()
{
var converter = new EasingTypeConverter();

Assert.Equal(Easing.Linear, converter.ConvertFromInvariantString("Linear"));
Assert.Equal(Easing.Linear, converter.ConvertFromInvariantString("linear"));
Assert.Equal(Easing.Linear, converter.ConvertFromInvariantString("Easing.Linear"));

Assert.Equal(Easing.SinOut, converter.ConvertFromInvariantString("SinOut"));
Assert.Equal(Easing.SinOut, converter.ConvertFromInvariantString("sinout"));
Assert.Equal(Easing.SinOut, converter.ConvertFromInvariantString("Easing.SinOut"));

Assert.Equal(Easing.SinIn, converter.ConvertFromInvariantString("SinIn"));
Assert.Equal(Easing.SinIn, converter.ConvertFromInvariantString("sinin"));
Assert.Equal(Easing.SinIn, converter.ConvertFromInvariantString("Easing.SinIn"));

Assert.Equal(Easing.SinInOut, converter.ConvertFromInvariantString("SinInOut"));
Assert.Equal(Easing.SinInOut, converter.ConvertFromInvariantString("sininout"));
Assert.Equal(Easing.SinInOut, converter.ConvertFromInvariantString("Easing.SinInOut"));

Assert.Equal(Easing.CubicOut, converter.ConvertFromInvariantString("CubicOut"));
Assert.Equal(Easing.CubicOut, converter.ConvertFromInvariantString("cubicout"));
Assert.Equal(Easing.CubicOut, converter.ConvertFromInvariantString("Easing.CubicOut"));

Assert.Equal(Easing.CubicIn, converter.ConvertFromInvariantString("CubicIn"));
Assert.Equal(Easing.CubicIn, converter.ConvertFromInvariantString("cubicin"));
Assert.Equal(Easing.CubicIn, converter.ConvertFromInvariantString("Easing.CubicIn"));

Assert.Equal(Easing.CubicInOut, converter.ConvertFromInvariantString("CubicInOut"));
Assert.Equal(Easing.CubicInOut, converter.ConvertFromInvariantString("cubicinout"));
Assert.Equal(Easing.CubicInOut, converter.ConvertFromInvariantString("Easing.CubicInOut"));

Assert.Equal(Easing.BounceOut, converter.ConvertFromInvariantString("BounceOut"));
Assert.Equal(Easing.BounceOut, converter.ConvertFromInvariantString("bounceout"));
Assert.Equal(Easing.BounceOut, converter.ConvertFromInvariantString("Easing.BounceOut"));

Assert.Equal(Easing.BounceIn, converter.ConvertFromInvariantString("BounceIn"));
Assert.Equal(Easing.BounceIn, converter.ConvertFromInvariantString("bouncein"));
Assert.Equal(Easing.BounceIn, converter.ConvertFromInvariantString("Easing.BounceIn"));

Assert.Equal(Easing.SpringOut, converter.ConvertFromInvariantString("SpringOut"));
Assert.Equal(Easing.SpringOut, converter.ConvertFromInvariantString("springout"));
Assert.Equal(Easing.SpringOut, converter.ConvertFromInvariantString("Easing.SpringOut"));

Assert.Equal(Easing.SpringIn, converter.ConvertFromInvariantString("SpringIn"));
Assert.Equal(Easing.SpringIn, converter.ConvertFromInvariantString("springin"));
Assert.Equal(Easing.SpringIn, converter.ConvertFromInvariantString("Easing.SpringIn"));
}

[Fact]
public void CanConvertFromEasingToEasingName()
{
var converter = new EasingTypeConverter();

Assert.Equal("Linear", converter.ConvertToInvariantString(Easing.Linear));
Assert.Equal("SinOut", converter.ConvertToInvariantString(Easing.SinOut));
Assert.Equal("SinIn", converter.ConvertToInvariantString(Easing.SinIn));
Assert.Equal("SinInOut", converter.ConvertToInvariantString(Easing.SinInOut));
Assert.Equal("CubicOut", converter.ConvertToInvariantString(Easing.CubicOut));
Assert.Equal("CubicIn", converter.ConvertToInvariantString(Easing.CubicIn));
Assert.Equal("CubicInOut", converter.ConvertToInvariantString(Easing.CubicInOut));
Assert.Equal("BounceOut", converter.ConvertToInvariantString(Easing.BounceOut));
Assert.Equal("BounceIn", converter.ConvertToInvariantString(Easing.BounceIn));
Assert.Equal("SpringOut", converter.ConvertToInvariantString(Easing.SpringOut));
Assert.Equal("SpringIn", converter.ConvertToInvariantString(Easing.SpringIn));
}

[Fact]
public void InvalidEasingNamesThrow()
{
var converter = new EasingTypeConverter();

Assert.Throws<InvalidOperationException>(() => converter.ConvertFromInvariantString("WrongEasingName"));
Assert.Throws<InvalidOperationException>(() => converter.ConvertFromInvariantString("Easing.Linear.SinInOut"));
}
Expand Down
1 change: 1 addition & 0 deletions src/Core/tests/UnitTests/TestCategory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace Microsoft.Maui.UnitTests
{
public static class TestCategory
{
public const string Animations = "Animations";
public const string Core = "Core";
public const string CommandMapping = "CommandMapping";
public const string Layout = "Layout";
Expand Down

0 comments on commit d8d9488

Please sign in to comment.