diff --git a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationBinaryExpressionSyntax.cs b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationBinaryExpressionSyntax.cs index a40614b44ac5..080ea45894ef 100644 --- a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationBinaryExpressionSyntax.cs +++ b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationBinaryExpressionSyntax.cs @@ -78,6 +78,58 @@ public override object Evaluate(ExpressionAnimation expressionAnimation) _ => throw new ArgumentException($"Cannot evaluate binary / between types '{leftValue.GetType()}' and '{rightValue.GetType()}'.") }; } + else if (_operatorToken.Kind == ExpressionAnimationTokenKind.GreaterThanToken) + { + return (leftValue, rightValue) switch + { + (float leftFloat, float rightFloat) => leftFloat > rightFloat, + (byte leftByte, byte rightByte) => leftByte > rightByte, + (short leftShort, short rightShort) => leftShort > rightShort, + (int leftInt, int rightInt) => leftInt > rightInt, + (int leftInt, float rightFloat) => leftInt > rightFloat, + (float leftFloat, int rightInt) => leftFloat > rightInt, + _ => throw new ArgumentException($"Cannot evaluate binary > between types '{leftValue.GetType()}' and '{rightValue.GetType()}'.") + }; + } + else if (_operatorToken.Kind == ExpressionAnimationTokenKind.GreaterThanEqualsToken) + { + return (leftValue, rightValue) switch + { + (float leftFloat, float rightFloat) => leftFloat >= rightFloat, + (byte leftByte, byte rightByte) => leftByte >= rightByte, + (short leftShort, short rightShort) => leftShort >= rightShort, + (int leftInt, int rightInt) => leftInt >= rightInt, + (int leftInt, float rightFloat) => leftInt >= rightFloat, + (float leftFloat, int rightInt) => leftFloat >= rightInt, + _ => throw new ArgumentException($"Cannot evaluate binary >= between types '{leftValue.GetType()}' and '{rightValue.GetType()}'.") + }; + } + else if (_operatorToken.Kind == ExpressionAnimationTokenKind.LessThanToken) + { + return (leftValue, rightValue) switch + { + (float leftFloat, float rightFloat) => leftFloat < rightFloat, + (byte leftByte, byte rightByte) => leftByte < rightByte, + (short leftShort, short rightShort) => leftShort < rightShort, + (int leftInt, int rightInt) => leftInt < rightInt, + (int leftInt, float rightFloat) => leftInt < rightFloat, + (float leftFloat, int rightInt) => leftFloat < rightInt, + _ => throw new ArgumentException($"Cannot evaluate binary < between types '{leftValue.GetType()}' and '{rightValue.GetType()}'.") + }; + } + else if (_operatorToken.Kind == ExpressionAnimationTokenKind.LessThanEqualsToken) + { + return (leftValue, rightValue) switch + { + (float leftFloat, float rightFloat) => leftFloat <= rightFloat, + (byte leftByte, byte rightByte) => leftByte <= rightByte, + (short leftShort, short rightShort) => leftShort <= rightShort, + (int leftInt, int rightInt) => leftInt <= rightInt, + (int leftInt, float rightFloat) => leftInt <= rightFloat, + (float leftFloat, int rightInt) => leftFloat <= rightInt, + _ => throw new ArgumentException($"Cannot evaluate binary <= between types '{leftValue.GetType()}' and '{rightValue.GetType()}'.") + }; + } throw new ArgumentException($"Unable to binary expression for operator '{_operatorToken.Kind}'."); } diff --git a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationIdentifierNameSyntax.cs b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationIdentifierNameSyntax.cs index 7a83f127e5db..2caaafa9099d 100644 --- a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationIdentifierNameSyntax.cs +++ b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationIdentifierNameSyntax.cs @@ -31,22 +31,29 @@ public override object Evaluate(ExpressionAnimation expressionAnimation) { value.AddContext(expressionAnimation, null); _result = value; - return value; } - - if (expressionAnimation.ScalarParameters.TryGetValue(identifierValue, out var scalarValue)) + else if (expressionAnimation.ScalarParameters.TryGetValue(identifierValue, out var scalarValue)) { _result = scalarValue; - return scalarValue; } - - if (identifierValue.Equals("Pi", StringComparison.Ordinal)) + else if (identifierValue.Equals("Pi", StringComparison.OrdinalIgnoreCase)) + { + _result = (float)Math.PI; + } + else if (identifierValue.Equals("True", StringComparison.OrdinalIgnoreCase)) + { + _result = true; + } + else if (identifierValue.Equals("False", StringComparison.OrdinalIgnoreCase)) + { + _result = false; + } + else { - _result = Math.PI; - return Math.PI; + throw new ArgumentException($"Unrecognized identifier '{Identifier.Value}'."); } - throw new ArgumentException($"Unrecognized identifier '{Identifier.Value}'."); + return _result; } public override void Dispose() diff --git a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationTernaryExpressionSyntax.cs b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationTernaryExpressionSyntax.cs new file mode 100644 index 000000000000..a55ac410a4d1 --- /dev/null +++ b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/AnimationTernaryExpressionSyntax.cs @@ -0,0 +1,34 @@ +using System; +using System.Numerics; + +namespace Microsoft.UI.Composition; + +internal sealed class AnimationTernaryExpressionSyntax : AnimationExpressionSyntax +{ + private readonly AnimationExpressionSyntax _condition; + private readonly AnimationExpressionSyntax _whenTrue; + private readonly AnimationExpressionSyntax _whenFalse; + + public AnimationTernaryExpressionSyntax(AnimationExpressionSyntax condition, AnimationExpressionSyntax whenTrue, AnimationExpressionSyntax whenFalse) + { + _condition = condition; + _whenTrue = whenTrue; + _whenFalse = whenFalse; + } + + public override object Evaluate(ExpressionAnimation expressionAnimation) + { + var value = _condition.Evaluate(expressionAnimation); + if (value is not bool valueBool) + { + throw new Exception($"Ternary expression condition evaluated to '{value}'. It must evaluate to a bool value."); + } + + if (valueBool) + { + return _whenTrue.Evaluate(expressionAnimation); + } + + return _whenFalse.Evaluate(expressionAnimation); + } +} diff --git a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationLexer.cs b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationLexer.cs index 69a30957bf74..aedf91156306 100644 --- a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationLexer.cs +++ b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationLexer.cs @@ -23,6 +23,8 @@ internal sealed class ExpressionAnimationLexer [','] = ExpressionAnimationTokenKind.CommaToken, ['('] = ExpressionAnimationTokenKind.OpenParenToken, [')'] = ExpressionAnimationTokenKind.CloseParenToken, + ['?'] = ExpressionAnimationTokenKind.QuestionMarkToken, + [':'] = ExpressionAnimationTokenKind.ColonToken, }; public ExpressionAnimationLexer(string text) @@ -85,6 +87,30 @@ private char Peek(int i) return new ExpressionAnimationToken(kind, null); } + if (Current == '>') + { + _position++; + if (Current == '=') + { + _position++; + return new ExpressionAnimationToken(ExpressionAnimationTokenKind.GreaterThanEqualsToken, null); + } + + return new ExpressionAnimationToken(ExpressionAnimationTokenKind.GreaterThanToken, null); + } + + if (Current == '<') + { + _position++; + if (Current == '=') + { + _position++; + return new ExpressionAnimationToken(ExpressionAnimationTokenKind.LessThanEqualsToken, null); + } + + return new ExpressionAnimationToken(ExpressionAnimationTokenKind.LessThanToken, null); + } + if (char.IsLetter(Current)) { int start = _position; diff --git a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationParser.cs b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationParser.cs index 0fc3365ff9ab..b1f86dcbb5e1 100644 --- a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationParser.cs +++ b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationParser.cs @@ -49,8 +49,10 @@ internal static int GetBinaryPrecedence(ExpressionAnimationToken token) { return token.Kind switch { - ExpressionAnimationTokenKind.PlusToken or ExpressionAnimationTokenKind.MinusToken => 1, - ExpressionAnimationTokenKind.MultiplyToken or ExpressionAnimationTokenKind.DivisionToken => 2, + ExpressionAnimationTokenKind.QuestionMarkToken => 1, + ExpressionAnimationTokenKind.LessThanToken or ExpressionAnimationTokenKind.LessThanEqualsToken or ExpressionAnimationTokenKind.GreaterThanToken or ExpressionAnimationTokenKind.GreaterThanToken => 2, + ExpressionAnimationTokenKind.PlusToken or ExpressionAnimationTokenKind.MinusToken => 3, + ExpressionAnimationTokenKind.MultiplyToken or ExpressionAnimationTokenKind.DivisionToken => 4, _ => 0 }; } @@ -59,7 +61,7 @@ internal static int GetUnaryPrecedence(ExpressionAnimationToken token) { return token.Kind switch { - ExpressionAnimationTokenKind.PlusToken or ExpressionAnimationTokenKind.MinusToken => 3, + ExpressionAnimationTokenKind.PlusToken or ExpressionAnimationTokenKind.MinusToken => 5, _ => 0 }; } @@ -88,9 +90,20 @@ private AnimationExpressionSyntax ParseExpression(int parentPrecedence = 0) break; } - var operatorToken = NextToken(); - var right = ParseExpression(precedence); - left = new AnimationBinaryExpressionSyntax(left, operatorToken, right); + if (HasCurrent && Current.Kind == ExpressionAnimationTokenKind.QuestionMarkToken) + { + _ = NextToken(); + var whenTrue = ParseExpression(); + _ = Match(ExpressionAnimationTokenKind.ColonToken); + var whenFalse = ParseExpression(); + left = new AnimationTernaryExpressionSyntax(left, whenTrue, whenFalse); + } + else + { + var operatorToken = NextToken(); + var right = ParseExpression(precedence); + left = new AnimationBinaryExpressionSyntax(left, operatorToken, right); + } } return left; diff --git a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationTokenKind.cs b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationTokenKind.cs index c0abce2024bc..e661a77ebf95 100644 --- a/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationTokenKind.cs +++ b/src/Uno.UI.Composition/Composition/ExpressionAnimationParser/ExpressionAnimationTokenKind.cs @@ -10,7 +10,12 @@ internal enum ExpressionAnimationTokenKind DivisionToken, OpenParenToken, CloseParenToken, + QuestionMarkToken, + ColonToken, + GreaterThanEqualsToken, + GreaterThanToken, + LessThanEqualsToken, + LessThanToken, IdentifierToken, NumericLiteralToken, - // TODO: QuestionMarkToken and ColonToken to support ternary operators. } diff --git a/src/Uno.UI.Composition/Composition/InteractionTracker/InteractionTracker.cs b/src/Uno.UI.Composition/Composition/InteractionTracker/InteractionTracker.cs index 6e05221cdcdc..f8901f9d726e 100644 --- a/src/Uno.UI.Composition/Composition/InteractionTracker/InteractionTracker.cs +++ b/src/Uno.UI.Composition/Composition/InteractionTracker/InteractionTracker.cs @@ -122,6 +122,14 @@ private protected override void SetAnimatableProperty(ReadOnlySpan propert { MaxPosition = UpdateVector3(subPropertyName, MaxPosition, propertyValue); } + else if (propertyName.Equals(nameof(MinScale), StringComparison.OrdinalIgnoreCase)) + { + MinScale = ValidateValue(propertyValue); + } + else if (propertyName.Equals(nameof(MaxScale), StringComparison.OrdinalIgnoreCase)) + { + MaxScale = ValidateValue(propertyValue); + } else { base.SetAnimatableProperty(propertyName, subPropertyName, propertyValue); @@ -142,6 +150,18 @@ internal override object GetAnimatableProperty(string propertyName, string subPr { return GetVector3(subPropertyName, MinPosition); } + else if (propertyName.Equals(nameof(Scale), StringComparison.OrdinalIgnoreCase)) + { + return ValidateValue(Scale); + } + else if (propertyName.Equals(nameof(MinScale), StringComparison.OrdinalIgnoreCase)) + { + return ValidateValue(MinScale); + } + else if (propertyName.Equals(nameof(MaxScale), StringComparison.OrdinalIgnoreCase)) + { + return ValidateValue(MaxScale); + } else { return base.GetAnimatableProperty(propertyName, subPropertyName); diff --git a/src/Uno.UI.Tests/CompositionTests/ExpressionAnimationParserTests.cs b/src/Uno.UI.Tests/CompositionTests/ExpressionAnimationParserTests.cs index d39da83c4844..e62a690b3dca 100644 --- a/src/Uno.UI.Tests/CompositionTests/ExpressionAnimationParserTests.cs +++ b/src/Uno.UI.Tests/CompositionTests/ExpressionAnimationParserTests.cs @@ -25,4 +25,48 @@ public void TestUnaryMinusExpressionWithMemberAccess() var result = expression.Evaluate(expressionAnimation); Assert.AreEqual(-5.0f, result); } + + [TestMethod] + public void TestTernaryExpression_Simple_When_True() + { + var compositor = Compositor.GetSharedCompositor(); + var expressionAnimation = compositor.CreateExpressionAnimation("true ? 5 : 4"); + var parser = new ExpressionAnimationParser(expressionAnimation.Expression); + var expression = parser.Parse(); + var result = expression.Evaluate(expressionAnimation); + Assert.AreEqual(5.0f, result); + } + + [TestMethod] + public void TestTernaryExpression_Simple_When_False() + { + var compositor = Compositor.GetSharedCompositor(); + var expressionAnimation = compositor.CreateExpressionAnimation("false ? 5 : 4"); + var parser = new ExpressionAnimationParser(expressionAnimation.Expression); + var expression = parser.Parse(); + var result = expression.Evaluate(expressionAnimation); + Assert.AreEqual(4.0f, result); + } + + [TestMethod] + public void TestTernaryExpression_Condition_Is_Binary_When_True() + { + var compositor = Compositor.GetSharedCompositor(); + var expressionAnimation = compositor.CreateExpressionAnimation("5 > 4 ? 8.0f : 9.0f"); + var parser = new ExpressionAnimationParser(expressionAnimation.Expression); + var expression = parser.Parse(); + var result = expression.Evaluate(expressionAnimation); + Assert.AreEqual(8.0f, result); + } + + [TestMethod] + public void TestTernaryExpression_Condition_Is_Binary_When_False() + { + var compositor = Compositor.GetSharedCompositor(); + var expressionAnimation = compositor.CreateExpressionAnimation("5 <= 4 ? 8.0f : 9.0f"); + var parser = new ExpressionAnimationParser(expressionAnimation.Expression); + var expression = parser.Parse(); + var result = expression.Evaluate(expressionAnimation); + Assert.AreEqual(9.0f, result); + } }