diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs index 8a25f23e59d..226331853a7 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs @@ -30,7 +30,9 @@ protected void Initialize(CompositionProperty property, HashSet<(string name, st _trackedObjects = new (); foreach (var t in trackedObjects) { - var obj = Parameters.GetObjectParameter(t.name); + var obj = (t.name == ExpressionKeywords.Target) + ? TargetObject + : Parameters.GetObjectParameter(t.name); if (obj is ServerObject tracked) { var off = tracked.GetCompositionProperty(t.member); diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs index c70056b5aab..042e7429c6a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs @@ -62,12 +62,10 @@ bool CallWithCast(List countGroup, IReadOnlyList a var arg = arguments[c].Type; if (parameter != arg) { - var canCast = (parameter == VariantType.Double && arg == VariantType.Scalar) - || (parameter == VariantType.Vector3D && arg == VariantType.Vector3) + var canCast = (parameter == VariantType.Vector3D && arg == VariantType.Vector3) || (parameter == VariantType.Vector && arg == VariantType.Vector2) || (anyCast && ( - (arg == VariantType.Double && parameter == VariantType.Scalar) - || (arg == VariantType.Vector3D && parameter == VariantType.Vector3) + (arg == VariantType.Vector3D && parameter == VariantType.Vector3) || (arg == VariantType.Vector && parameter == VariantType.Vector2) )); if (!canCast) @@ -112,7 +110,7 @@ void Add(string name, Func, ExpressionVariant> static readonly Dictionary TypeMap = new Dictionary { [typeof(bool)] = VariantType.Boolean, - [typeof(float)] = VariantType.Scalar, + [typeof(float)] = VariantType.Double, [typeof(double)] = VariantType.Double, [typeof(Vector2)] = VariantType.Vector2, [typeof(Vector)] = VariantType.Vector, diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs index d9f39d0ce05..7f5253f476f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs @@ -104,6 +104,17 @@ internal enum ExpressionKeyword False } + internal static class ExpressionKeywords + { + public const string StartingValue = "this.startingvalue"; + public const string CurrentValue = "this.currentvalue"; + public const string FinalValue = "this.finalvalue"; + public const string Pi = "pi"; + public const string True = "true"; + public const string False = "false"; + public const string Target = "this.target"; + } + internal class ConditionalExpression : Expression { public Expression Condition { get; } @@ -202,6 +213,10 @@ public override void CollectReferences(HashSet<(string parameter, string propert Target.CollectReferences(references); if (Target is ParameterExpression pe) references.Add((pe.Name, Member)); + else if (Target is KeywordExpression { Keyword : ExpressionKeyword.Target }) + { + references.Add((ExpressionKeywords.Target, Member)); + } } public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs index 885499bc2cf..45e4e164c19 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs @@ -25,19 +25,19 @@ static bool TryParseAtomic(ref TokenParser parser, { // We can parse keywords, parameter names and constants expr = null; - if (parser.TryParseKeywordLowerCase("this.startingvalue")) + if (parser.TryParseKeywordLowerCase(ExpressionKeywords.StartingValue)) expr = new KeywordExpression(ExpressionKeyword.StartingValue); - else if(parser.TryParseKeywordLowerCase("this.currentvalue")) + else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.CurrentValue)) expr = new KeywordExpression(ExpressionKeyword.CurrentValue); - else if(parser.TryParseKeywordLowerCase("this.finalvalue")) + else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.FinalValue)) expr = new KeywordExpression(ExpressionKeyword.FinalValue); - else if(parser.TryParseKeywordLowerCase("pi")) + else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.Pi)) expr = new KeywordExpression(ExpressionKeyword.Pi); - else if(parser.TryParseKeywordLowerCase("true")) + else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.True)) expr = new KeywordExpression(ExpressionKeyword.True); - else if(parser.TryParseKeywordLowerCase("false")) + else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.False)) expr = new KeywordExpression(ExpressionKeyword.False); - else if (parser.TryParseKeywordLowerCase("this.target")) + else if (parser.TryParseKeywordLowerCase(ExpressionKeywords.Target)) expr = new KeywordExpression(ExpressionKeyword.Target); if (expr != null) diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs index 136ef69b557..bbaab572d56 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs @@ -10,7 +10,6 @@ internal enum VariantType { Invalid, Boolean, - Scalar, Double, Vector2, Vector3, @@ -33,7 +32,6 @@ internal struct ExpressionVariant [FieldOffset(0)] public VariantType Type; [FieldOffset(4)] public bool Boolean; - [FieldOffset(4)] public float Scalar; [FieldOffset(4)] public double Double; [FieldOffset(4)] public Vector2 Vector2; [FieldOffset(4)] public Vector3 Vector3; @@ -45,191 +43,194 @@ internal struct ExpressionVariant [FieldOffset(4)] public Matrix4x4 Matrix4x4; [FieldOffset(4)] public Quaternion Quaternion; [FieldOffset(4)] public Color Color; - + public ExpressionVariant GetProperty(string property) { if (Type == VariantType.Vector2) { - if (ReferenceEquals(property, "X")) + if (IsMatch(property, "X")) return Vector2.X; - if (ReferenceEquals(property, "Y")) + if (IsMatch(property, "Y")) return Vector2.Y; return default; } - + if (Type == VariantType.Vector) { - if (ReferenceEquals(property, "X")) + if (IsMatch(property, "X")) return Vector.X; - if (ReferenceEquals(property, "Y")) + if (IsMatch(property, "Y")) return Vector.Y; return default; } if (Type == VariantType.Vector3) { - if (ReferenceEquals(property, "X")) + if (IsMatch(property, "X")) return Vector3.X; - if (ReferenceEquals(property, "Y")) + if (IsMatch(property, "Y")) return Vector3.Y; - if (ReferenceEquals(property, "Z")) + if (IsMatch(property, "Z")) return Vector3.Z; - if(ReferenceEquals(property, "XY")) + if (IsMatch(property, "XY")) return new Vector2(Vector3.X, Vector3.Y); - if(ReferenceEquals(property, "YX")) + if (IsMatch(property, "YX")) return new Vector2(Vector3.Y, Vector3.X); - if(ReferenceEquals(property, "XZ")) + if (IsMatch(property, "XZ")) return new Vector2(Vector3.X, Vector3.Z); - if(ReferenceEquals(property, "ZX")) + if (IsMatch(property, "ZX")) return new Vector2(Vector3.Z, Vector3.X); - if(ReferenceEquals(property, "YZ")) + if (IsMatch(property, "YZ")) return new Vector2(Vector3.Y, Vector3.Z); - if(ReferenceEquals(property, "ZY")) + if (IsMatch(property, "ZY")) return new Vector2(Vector3.Z, Vector3.Y); return default; } - + if (Type == VariantType.Vector3D) { - if (ReferenceEquals(property, "X")) + if (IsMatch(property, "X")) return Vector3D.X; - if (ReferenceEquals(property, "Y")) + if (IsMatch(property, "Y")) return Vector3D.Y; - if (ReferenceEquals(property, "Z")) + if (IsMatch(property, "Z")) return Vector3D.Z; - if(ReferenceEquals(property, "XY")) + if (IsMatch(property, "XY")) return new Vector(Vector3D.X, Vector3D.Y); - if(ReferenceEquals(property, "YX")) + if (IsMatch(property, "YX")) return new Vector(Vector3D.Y, Vector3D.X); - if(ReferenceEquals(property, "XZ")) + if (IsMatch(property, "XZ")) return new Vector(Vector3D.X, Vector3D.Z); - if(ReferenceEquals(property, "ZX")) + if (IsMatch(property, "ZX")) return new Vector(Vector3D.Z, Vector3D.X); - if(ReferenceEquals(property, "YZ")) + if (IsMatch(property, "YZ")) return new Vector(Vector3D.Y, Vector3D.Z); - if(ReferenceEquals(property, "ZY")) + if (IsMatch(property, "ZY")) return new Vector(Vector3D.Z, Vector3D.Y); return default; } if (Type == VariantType.Vector4) { - if (ReferenceEquals(property, "X")) + if (IsMatch(property, "X")) return Vector4.X; - if (ReferenceEquals(property, "Y")) + if (IsMatch(property, "Y")) return Vector4.Y; - if (ReferenceEquals(property, "Z")) + if (IsMatch(property, "Z")) return Vector4.Z; - if (ReferenceEquals(property, "W")) + if (IsMatch(property, "W")) return Vector4.W; return default; } if (Type == VariantType.Matrix3x2) { - if (ReferenceEquals(property, "M11")) + if (IsMatch(property, "M11")) return Matrix3x2.M11; - if (ReferenceEquals(property, "M12")) + if (IsMatch(property, "M12")) return Matrix3x2.M12; - if (ReferenceEquals(property, "M21")) + if (IsMatch(property, "M21")) return Matrix3x2.M21; - if (ReferenceEquals(property, "M22")) + if (IsMatch(property, "M22")) return Matrix3x2.M22; - if (ReferenceEquals(property, "M31")) + if (IsMatch(property, "M31")) return Matrix3x2.M31; - if (ReferenceEquals(property, "M32")) + if (IsMatch(property, "M32")) return Matrix3x2.M32; return default; } - + if (Type == VariantType.AvaloniaMatrix) { - if (ReferenceEquals(property, "M11")) + if (IsMatch(property, "M11")) return AvaloniaMatrix.M11; - if (ReferenceEquals(property, "M12")) + if (IsMatch(property, "M12")) return AvaloniaMatrix.M12; - if (ReferenceEquals(property, "M13")) + if (IsMatch(property, "M13")) return AvaloniaMatrix.M13; - if (ReferenceEquals(property, "M21")) + if (IsMatch(property, "M21")) return AvaloniaMatrix.M21; - if (ReferenceEquals(property, "M22")) + if (IsMatch(property, "M22")) return AvaloniaMatrix.M22; - if (ReferenceEquals(property, "M23")) + if (IsMatch(property, "M23")) return AvaloniaMatrix.M23; - if (ReferenceEquals(property, "M31")) + if (IsMatch(property, "M31")) return AvaloniaMatrix.M31; - if (ReferenceEquals(property, "M32")) + if (IsMatch(property, "M32")) return AvaloniaMatrix.M32; - if (ReferenceEquals(property, "M33")) + if (IsMatch(property, "M33")) return AvaloniaMatrix.M33; return default; } if (Type == VariantType.Matrix4x4) { - if (ReferenceEquals(property, "M11")) + if (IsMatch(property, "M11")) return Matrix4x4.M11; - if (ReferenceEquals(property, "M12")) + if (IsMatch(property, "M12")) return Matrix4x4.M12; - if (ReferenceEquals(property, "M13")) + if (IsMatch(property, "M13")) return Matrix4x4.M13; - if (ReferenceEquals(property, "M14")) + if (IsMatch(property, "M14")) return Matrix4x4.M14; - if (ReferenceEquals(property, "M21")) + if (IsMatch(property, "M21")) return Matrix4x4.M21; - if (ReferenceEquals(property, "M22")) + if (IsMatch(property, "M22")) return Matrix4x4.M22; - if (ReferenceEquals(property, "M23")) + if (IsMatch(property, "M23")) return Matrix4x4.M23; - if (ReferenceEquals(property, "M24")) + if (IsMatch(property, "M24")) return Matrix4x4.M24; - if (ReferenceEquals(property, "M31")) + if (IsMatch(property, "M31")) return Matrix4x4.M31; - if (ReferenceEquals(property, "M32")) + if (IsMatch(property, "M32")) return Matrix4x4.M32; - if (ReferenceEquals(property, "M33")) + if (IsMatch(property, "M33")) return Matrix4x4.M33; - if (ReferenceEquals(property, "M34")) + if (IsMatch(property, "M34")) return Matrix4x4.M34; - if (ReferenceEquals(property, "M41")) + if (IsMatch(property, "M41")) return Matrix4x4.M41; - if (ReferenceEquals(property, "M42")) + if (IsMatch(property, "M42")) return Matrix4x4.M42; - if (ReferenceEquals(property, "M43")) + if (IsMatch(property, "M43")) return Matrix4x4.M43; - if (ReferenceEquals(property, "M44")) + if (IsMatch(property, "M44")) return Matrix4x4.M44; return default; } if (Type == VariantType.Quaternion) { - if (ReferenceEquals(property, "X")) + if (IsMatch(property, "X")) return Quaternion.X; - if (ReferenceEquals(property, "Y")) + if (IsMatch(property, "Y")) return Quaternion.Y; - if (ReferenceEquals(property, "Z")) + if (IsMatch(property, "Z")) return Quaternion.Z; - if (ReferenceEquals(property, "W")) + if (IsMatch(property, "W")) return Quaternion.W; return default; } - + if (Type == VariantType.Color) { - if (ReferenceEquals(property, "A")) + if (IsMatch(property, "A")) return Color.A; - if (ReferenceEquals(property, "R")) + if (IsMatch(property, "R")) return Color.R; - if (ReferenceEquals(property, "G")) + if (IsMatch(property, "G")) return Color.G; - if (ReferenceEquals(property, "B")) + if (IsMatch(property, "B")) return Color.B; return default; } return default; + + static bool IsMatch(string propertyName, string memberName) => + string.Equals(propertyName, memberName, StringComparison.Ordinal); } public static implicit operator ExpressionVariant(bool value) => @@ -238,13 +239,6 @@ public static implicit operator ExpressionVariant(bool value) => Type = VariantType.Boolean, Boolean = value }; - - public static implicit operator ExpressionVariant(float scalar) => - new ExpressionVariant - { - Type = VariantType.Scalar, - Scalar = scalar - }; public static implicit operator ExpressionVariant(double d) => new ExpressionVariant @@ -300,7 +294,7 @@ public static implicit operator ExpressionVariant(Matrix3x2 value) => public static implicit operator ExpressionVariant(Matrix value) => new ExpressionVariant { - Type = VariantType.Matrix3x2, + Type = VariantType.AvaloniaMatrix, AvaloniaMatrix = value }; @@ -330,15 +324,12 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => if (left.Type != right.Type || left.Type == VariantType.Invalid) return default; - if (left.Type == VariantType.Scalar) - return left.Scalar + right.Scalar; - if (left.Type == VariantType.Double) return left.Double + right.Double; if (left.Type == VariantType.Vector2) return left.Vector2 + right.Vector2; - + if (left.Type == VariantType.Vector) return left.Vector + right.Vector; @@ -350,16 +341,16 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => if (left.Type == VariantType.Vector4) return left.Vector4 + right.Vector4; - + if (left.Type == VariantType.Matrix3x2) return left.Matrix3x2 + right.Matrix3x2; - + if (left.Type == VariantType.Matrix4x4) return left.Matrix4x4 + right.Matrix4x4; - + if (left.Type == VariantType.Quaternion) return left.Quaternion + right.Quaternion; - + return default; } @@ -368,15 +359,12 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => if (left.Type != right.Type || left.Type == VariantType.Invalid) return default; - if (left.Type == VariantType.Scalar) - return left.Scalar - right.Scalar; - if (left.Type == VariantType.Double) return left.Double - right.Double; if (left.Type == VariantType.Vector2) return left.Vector2 - right.Vector2; - + if (left.Type == VariantType.Vector) return left.Vector - right.Vector; @@ -388,13 +376,13 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => if (left.Type == VariantType.Vector4) return left.Vector4 - right.Vector4; - + if (left.Type == VariantType.Matrix3x2) return left.Matrix3x2 - right.Matrix3x2; - + if (left.Type == VariantType.Matrix4x4) return left.Matrix4x4 - right.Matrix4x4; - + if (left.Type == VariantType.Quaternion) return left.Quaternion - right.Quaternion; @@ -403,34 +391,31 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => public static ExpressionVariant operator -(ExpressionVariant left) { - - if (left.Type == VariantType.Scalar) - return -left.Scalar; if (left.Type == VariantType.Double) return -left.Double; if (left.Type == VariantType.Vector2) return -left.Vector2; - + if (left.Type == VariantType.Vector) return -left.Vector; if (left.Type == VariantType.Vector3) return -left.Vector3; - + if (left.Type == VariantType.Vector3D) return -left.Vector3D; if (left.Type == VariantType.Vector4) return -left.Vector4; - + if (left.Type == VariantType.Matrix3x2) return -left.Matrix3x2; - + if (left.Type == VariantType.AvaloniaMatrix) return -left.AvaloniaMatrix; - + if (left.Type == VariantType.Matrix4x4) return -left.Matrix4x4; @@ -445,9 +430,6 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => if (left.Type == VariantType.Invalid || right.Type == VariantType.Invalid) return default; - if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) - return left.Scalar * right.Scalar; - if (left.Type == VariantType.Double && right.Type == VariantType.Double) return left.Double * right.Double; @@ -457,53 +439,50 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => if (left.Type == VariantType.Vector && right.Type == VariantType.Vector) return Vector.Multiply(left.Vector, right.Vector); - if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar) - return left.Vector2 * right.Scalar; - - if (left.Type == VariantType.Vector && right.Type == VariantType.Scalar) - return left.Vector * right.Scalar; - + if (left.Type == VariantType.Vector2 && right.Type == VariantType.Double) + return left.Vector2 * (float)right.Double; + if (left.Type == VariantType.Vector && right.Type == VariantType.Double) return left.Vector * right.Double; if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3) return left.Vector3 * right.Vector3; - + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Vector3D) return Vector3D.Multiply(left.Vector3D, right.Vector3D); - if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar) - return left.Vector3 * right.Scalar; - - if (left.Type == VariantType.Vector3D && right.Type == VariantType.Scalar) - return Vector3D.Multiply(left.Vector3D, right.Scalar); + if (left.Type == VariantType.Vector3 && right.Type == VariantType.Double) + return left.Vector3 * (float)right.Double; + + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Double) + return Vector3D.Multiply(left.Vector3D, right.Double); if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4) return left.Vector4 * right.Vector4; - if (left.Type == VariantType.Vector4 && right.Type == VariantType.Scalar) - return left.Vector4 * right.Scalar; - + if (left.Type == VariantType.Vector4 && right.Type == VariantType.Double) + return left.Vector4 * (float)right.Double; + if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Matrix3x2) return left.Matrix3x2 * right.Matrix3x2; - if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Scalar) - return left.Matrix3x2 * right.Scalar; - + if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Double) + return left.Matrix3x2 * (float)right.Double; + if (left.Type == VariantType.AvaloniaMatrix && right.Type == VariantType.AvaloniaMatrix) return left.AvaloniaMatrix * right.AvaloniaMatrix; - + if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Matrix4x4) return left.Matrix4x4 * right.Matrix4x4; - if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Scalar) - return left.Matrix4x4 * right.Scalar; - + if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Double) + return left.Matrix4x4 * (float)right.Double; + if (left.Type == VariantType.Quaternion && right.Type == VariantType.Quaternion) return left.Quaternion * right.Quaternion; - if (left.Type == VariantType.Quaternion && right.Type == VariantType.Scalar) - return left.Quaternion * right.Scalar; + if (left.Type == VariantType.Quaternion && right.Type == VariantType.Double) + return left.Quaternion * (float)right.Double; return default; } @@ -513,9 +492,6 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => if (left.Type == VariantType.Invalid || right.Type == VariantType.Invalid) return default; - if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) - return left.Scalar / right.Scalar; - if (left.Type == VariantType.Double && right.Type == VariantType.Double) return left.Double / right.Double; @@ -525,14 +501,11 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => if (left.Type == VariantType.Vector && right.Type == VariantType.Vector) return Vector.Divide(left.Vector, right.Vector); - if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar) - return left.Vector2 / right.Scalar; - - if (left.Type == VariantType.Vector && right.Type == VariantType.Scalar) - return left.Vector / right.Scalar; - + if (left.Type == VariantType.Vector2 && right.Type == VariantType.Double) + return left.Vector2 / (float)right.Double; + if (left.Type == VariantType.Vector && right.Type == VariantType.Double) - return left.Vector / right.Scalar; + return left.Vector / right.Double; if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3) return left.Vector3 / right.Vector3; @@ -540,21 +513,18 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => if (left.Type == VariantType.Vector3D && right.Type == VariantType.Vector3D) return Vector3D.Divide(left.Vector3D, right.Vector3D); - if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar) - return left.Vector3 / right.Scalar; - - if (left.Type == VariantType.Vector3D && right.Type == VariantType.Scalar) - return Avalonia.Vector3D.Divide(left.Vector3D, right.Scalar); - + if (left.Type == VariantType.Vector3 && right.Type == VariantType.Double) + return left.Vector3 / (float)right.Double; + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Double) return Avalonia.Vector3D.Divide(left.Vector3D, right.Double); if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4) return left.Vector4 / right.Vector4; - if (left.Type == VariantType.Vector4 && right.Type == VariantType.Scalar) - return left.Vector4 / right.Scalar; - + if (left.Type == VariantType.Vector4 && right.Type == VariantType.Double) + return left.Vector4 / (float)right.Double; + if (left.Type == VariantType.Quaternion && right.Type == VariantType.Quaternion) return left.Quaternion / right.Quaternion; @@ -564,11 +534,7 @@ public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => public ExpressionVariant EqualsTo(ExpressionVariant right) { if (Type != right.Type || Type == VariantType.Invalid) - return default; - - if (Type == VariantType.Scalar) - return Scalar == right.Scalar; - + return default; if (Type == VariantType.Double) return Double == right.Double; @@ -623,8 +589,6 @@ public ExpressionVariant NotEqualsTo(ExpressionVariant right) public static ExpressionVariant operator %(ExpressionVariant left, ExpressionVariant right) { - if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) - return left.Scalar % right.Scalar; if (left.Type == VariantType.Double && right.Type == VariantType.Double) return left.Double % right.Double; return default; @@ -632,18 +596,13 @@ public ExpressionVariant NotEqualsTo(ExpressionVariant right) public static ExpressionVariant operator <(ExpressionVariant left, ExpressionVariant right) { - if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) - return left.Scalar < right.Scalar; if (left.Type == VariantType.Double && right.Type == VariantType.Double) return left.Double < right.Double; return default; } public static ExpressionVariant operator >(ExpressionVariant left, ExpressionVariant right) - { - if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) - return left.Scalar > right.Scalar; - + { if (left.Type == VariantType.Double && right.Type == VariantType.Double) return left.Double > right.Double; return default; @@ -659,207 +618,89 @@ public ExpressionVariant And(ExpressionVariant right) public ExpressionVariant Or(ExpressionVariant right) { if (Type == VariantType.Boolean && right.Type == VariantType.Boolean) - return Boolean && right.Boolean; + return Boolean || right.Boolean; return default; } public bool TryCast(out T res) where T : struct { - if (typeof(T) == typeof(bool)) - { - if (Type == VariantType.Boolean) - { - res = (T) (object) Boolean; - return true; - } - } - - if (typeof(T) == typeof(float)) + switch (default(T)) { - if (Type == VariantType.Scalar) - { - res = (T) (object) Scalar; - return true; - } - if (Type == VariantType.Double) - { - res = (T)(object)Scalar; + case bool when Type is VariantType.Boolean: + res = (T)(object)Boolean; return true; - } - } - - if (typeof(T) == typeof(double)) - { - if (Type == VariantType.Double) - { - res = (T) (object) Double; - return true; - } - - if (Type == VariantType.Scalar) - { + case float when Type is VariantType.Double: res = (T)(object)(float)Double; return true; - } - } - - if (typeof(T) == typeof(Vector2)) - { - if (Type == VariantType.Vector2) - { - res = (T) (object) Vector2; + case double when Type is VariantType.Double: + res = (T)(object)Double; return true; - } - - if (Type == VariantType.Vector) - { - res = (T) (object) Vector.ToVector2(); + case System.Numerics.Vector2 when Type is VariantType.Vector2: + res = (T)(object)Vector2; return true; - } - } - - if (typeof(T) == typeof(Vector)) - { - if (Type == VariantType.Vector) - { - res = (T) (object) Vector; + case System.Numerics.Vector2 when Type is VariantType.Vector: + res = (T)(object)Vector.ToVector2(); return true; - } - - if (Type == VariantType.Vector2) - { + case Avalonia.Vector when Type is VariantType.Vector: + res = (T)(object)Vector; + return true; + case Avalonia.Vector when Type is VariantType.Vector2: res = (T)(object)new Vector(Vector2); return true; - } - } - - if (typeof(T) == typeof(Vector3)) - { - if (Type == VariantType.Vector3) - { - res = (T) (object) Vector3; + case System.Numerics.Vector3 when Type is VariantType.Vector3: + res = (T)(object)Vector3; return true; - } - if (Type == VariantType.Vector3D) - { - res = (T) (object) Vector3D.ToVector3(); + case System.Numerics.Vector3 when Type is VariantType.Vector3D: + res = (T)(object)Vector3D.ToVector3(); return true; - } - } - - if (typeof(T) == typeof(Vector3D)) - { - if (Type == VariantType.Vector3D) - { - res = (T) (object) Vector3D; + case Avalonia.Vector3D when Type is VariantType.Vector3D: + res = (T)(object)Vector3D; return true; - } - - if (Type == VariantType.Vector3) - { + case Avalonia.Vector3D when Type is VariantType.Vector3: res = (T)(object)new Vector3D(Vector3); return true; - } - } - - if (typeof(T) == typeof(Vector4)) - { - if (Type == VariantType.Vector4) - { - res = (T) (object) Vector4; + case System.Numerics.Vector4 when Type is VariantType.Vector4: + res = (T)(object)Vector4; return true; - } - } - - if (typeof(T) == typeof(Matrix3x2)) - { - if (Type == VariantType.Matrix3x2) - { - res = (T) (object) Matrix3x2; + case System.Numerics.Matrix3x2 when Type is VariantType.Matrix3x2: + res = (T)(object)Matrix3x2; return true; - } - } - - if (typeof(T) == typeof(Matrix)) - { - if (Type == VariantType.AvaloniaMatrix) - { - res = (T) (object) Matrix3x2; + case Avalonia.Matrix when Type is VariantType.AvaloniaMatrix: + res = (T)(object)AvaloniaMatrix; return true; - } - } - - if (typeof(T) == typeof(Matrix4x4)) - { - if (Type == VariantType.Matrix4x4) - { - res = (T) (object) Matrix4x4; + case System.Numerics.Matrix4x4 when Type is VariantType.Matrix4x4: + res = (T)(object)Matrix4x4; return true; - } - } - - if (typeof(T) == typeof(Quaternion)) - { - if (Type == VariantType.Quaternion) - { - res = (T) (object) Quaternion; + case System.Numerics.Quaternion when Type is VariantType.Quaternion: + res = (T)(object)Quaternion; return true; - } - } - - if (typeof(T) == typeof(Avalonia.Media.Color)) - { - if (Type == VariantType.Color) - { - res = (T) (object) Color; + case Avalonia.Media.Color when Type is VariantType.Color: + res = (T)(object)Color; return true; - } + default: + res = default; + return false; } - - res = default; - return false; } public static ExpressionVariant Create(T v) where T : struct - { - if (typeof(T) == typeof(bool)) - return (bool) (object) v; - - if (typeof(T) == typeof(float)) - return (float) (object) v; - - if (typeof(T) == typeof(Vector2)) - return (Vector2) (object) v; - - if (typeof(T) == typeof(Vector)) - return (Vector) (object) v; - - if (typeof(T) == typeof(Vector3)) - return (Vector3) (object) v; - - if (typeof(T) == typeof(Vector3D)) - return (Vector3D) (object) v; - - if (typeof(T) == typeof(Vector4)) - return (Vector4) (object) v; - - if (typeof(T) == typeof(Matrix3x2)) - return (Matrix3x2) (object) v; - - if (typeof(T) == typeof(Matrix)) - return (Matrix) (object) v; - - if (typeof(T) == typeof(Matrix4x4)) - return (Matrix4x4) (object) v; - - if (typeof(T) == typeof(Quaternion)) - return (Quaternion) (object) v; - - if (typeof(T) == typeof(Avalonia.Media.Color)) - return (Avalonia.Media.Color) (object) v; - - throw new ArgumentException("Invalid variant type: " + typeof(T)); - } + => default(T) switch + { + bool => (bool)(object)v, + float => (float)(object)v, + double => (double)(object)v, + System.Numerics.Vector2 => (Vector2)(object)v, + Avalonia.Vector => (Vector)(object)v, + System.Numerics.Vector3 => (Vector3)(object)v, + Avalonia.Vector3D => (Vector3D)(object)v, + System.Numerics.Vector4 => (Vector4)(object)v, + System.Numerics.Matrix3x2 => (Matrix3x2)(object)v, + Avalonia.Matrix => (Matrix)(object)v, + System.Numerics.Matrix4x4 => (Matrix4x4)(object)v, + System.Numerics.Quaternion => (Quaternion)(object)v, + Avalonia.Media.Color => (Avalonia.Media.Color)(object)v, + _ => throw new ArgumentException("Invalid variant type: " + typeof(T)) + }; public T CastOrDefault() where T : struct { @@ -869,35 +710,23 @@ public T CastOrDefault() where T : struct public override string ToString() { - if (Type == VariantType.Boolean) - return Boolean.ToString(); - if (Type == VariantType.Scalar) - return Scalar.ToString(CultureInfo.InvariantCulture); - if (Type == VariantType.Double) - return Double.ToString(CultureInfo.InvariantCulture); - if (Type == VariantType.Vector2) - return Vector2.ToString(); - if (Type == VariantType.Vector) - return Vector.ToString(); - if (Type == VariantType.Vector3) - return Vector3.ToString(); - if (Type == VariantType.Vector3D) - return Vector3D.ToString(); - if (Type == VariantType.Vector4) - return Vector4.ToString(); - if (Type == VariantType.Quaternion) - return Quaternion.ToString(); - if (Type == VariantType.Matrix3x2) - return Matrix3x2.ToString(); - if (Type == VariantType.AvaloniaMatrix) - return AvaloniaMatrix.ToString(); - if (Type == VariantType.Matrix4x4) - return Matrix4x4.ToString(); - if (Type == VariantType.Color) - return Color.ToString(); - if (Type == VariantType.Invalid) - return "Invalid"; - return "Unknown"; + return Type switch + { + VariantType.Boolean => Boolean.ToString(), + VariantType.Double => Double.ToString(CultureInfo.InvariantCulture), + VariantType.Vector2 => Vector2.ToString(), + VariantType.Vector => Vector.ToString(), + VariantType.Vector3 => Vector3.ToString(), + VariantType.Vector3D => Vector3D.ToString(), + VariantType.Vector4 => Vector4.ToString(), + VariantType.Quaternion => Quaternion.ToString(), + VariantType.Matrix3x2 => Matrix3x2.ToString(), + VariantType.AvaloniaMatrix => AvaloniaMatrix.ToString(), + VariantType.Matrix4x4 => Matrix4x4.ToString(), + VariantType.Color => Color.ToString(), + VariantType.Invalid => "Invalid", + _ => "Unknown" + }; } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs b/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs index 180c717803f..a0aea862de6 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using Avalonia.Rendering.Composition.Expressions; namespace Avalonia.Rendering.Composition.Server; @@ -11,15 +8,6 @@ internal class CompositionProperty private static int s_nextId = 1; private static readonly object _lock = new(); - private static Dictionary> s_dynamicRegistry = new(); - - class ReadOnlyRegistry : Dictionary> - { - - } - - private static volatile ReadOnlyRegistry? s_ReadOnlyRegistry; - public CompositionProperty(int id, string name, Type owner, Func? getVariant) { Id = id; @@ -43,59 +31,8 @@ public static CompositionProperty Register(string name, prop = new CompositionProperty(id, name, typeof(TOwner), getField, setField, getVariant); } - s_ReadOnlyRegistry = null; return prop; } - - static void PopulatePropertiesForType(Type type, List l) - { - Type? t = type; - while (t != null && t != typeof(object)) - { - if (s_dynamicRegistry.TryGetValue(t, out var lst)) - l.AddRange(lst); - t = t.BaseType; - } - } - - static ReadOnlyRegistry Build() - { - var reg = new ReadOnlyRegistry(); - foreach (var type in s_dynamicRegistry.Keys) - { - var lst = new List(); - PopulatePropertiesForType(type, lst); - reg[type] = lst.ToDictionary(x => x.Name); - } - - return reg; - } - - public static IReadOnlyDictionary? TryGetPropertiesForType(Type t) - { - GetRegistry().TryGetValue(t, out var rv); - return rv; - } - - public static CompositionProperty? Find(Type owner, string name) - { - if (TryGetPropertiesForType(owner)?.TryGetValue(name, out var prop) == true) - return prop; - return null; - } - - static ReadOnlyRegistry GetRegistry() - { - var reg = s_ReadOnlyRegistry; - if (reg != null) - return reg; - lock (_lock) - { - // ReSharper disable once NonAtomicCompoundOperator - // This is the only line ever that would set the field to a not-null value, and we are inside of a lock - return s_ReadOnlyRegistry ??= Build(); - } - } } internal class CompositionProperty : CompositionProperty @@ -112,4 +49,4 @@ public CompositionProperty(int id, string name, Type owner, GetField = getField; SetField = setField; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositorAnimations.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositorAnimations.cs index 0e59cd8f03d..525bafb8a10 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositorAnimations.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositorAnimations.cs @@ -26,7 +26,12 @@ public void Process() _clockItemsToUpdate.Clear(); while (_dirtyAnimatedObjectQueue.Count > 0) - _dirtyAnimatedObjectQueue.Dequeue().EvaluateAnimations(); + { + var animation = _dirtyAnimatedObjectQueue.Dequeue(); + _dirtyAnimatedObjects.Remove(animation); + animation.EvaluateAnimations(); + } + _dirtyAnimatedObjects.Clear(); } @@ -37,4 +42,4 @@ public void AddDirtyAnimatedObject(ServerObjectAnimations obj) if (_dirtyAnimatedObjects.Add(obj)) _dirtyAnimatedObjectQueue.Enqueue(obj); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs index f975e8e726f..ac022f5356d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs @@ -71,7 +71,7 @@ protected void SetAnimatedValue(CompositionProperty property, out T field, T ExpressionVariant IExpressionObject.GetProperty(string name) { if (_animations == null) - return CompositionProperty.Find(this.GetType(), name)?.GetVariant?.Invoke(this) ?? default; + return GetCompositionProperty(name)?.GetVariant?.Invoke(this) ?? default; return _animations.GetPropertyForAnimation(name); } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerObjectAnimations.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerObjectAnimations.cs index 1d213781c78..c62c82ea5cd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerObjectAnimations.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerObjectAnimations.cs @@ -12,18 +12,15 @@ class ServerObjectAnimations private readonly ServerObject _owner; private InlineDictionary _subscriptions; private InlineDictionary _animations; - private readonly IReadOnlyDictionary _properties; public ServerObjectAnimations(ServerObject owner) { _owner = owner; - _properties = CompositionProperty.TryGetPropertiesForType(owner.GetType()) ?? - new Dictionary(); } private class ServerObjectSubscriptionStore { - public bool IsValid; + public bool IsValid = true; public RefTrackingDictionary? Subscribers; public void Invalidate() @@ -84,6 +81,7 @@ public override void UpdateTargetProperty() NeedsUpdate = false; _property.SetField(Owner._owner, GetVariant().CastOrDefault()); Owner._owner.NotifyAnimatedValueChanged(_property); + Owner.OnSetDirectValue(_property); } } } @@ -143,7 +141,8 @@ public void UnsubscribeFromInvalidation(CompositionProperty member, IAnimationIn public ExpressionVariant GetPropertyForAnimation(string name) { - if (!_properties.TryGetValue(name, out var prop)) + var prop = _owner.GetCompositionProperty(name); + if (prop is null) return default; if (_subscriptions.TryGetValue(prop, out var subs)) @@ -172,4 +171,4 @@ public void NotifyAnimationInstanceInvalidated(CompositionProperty property) else Debug.Assert(false); } -} \ No newline at end of file +} diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.cs index 4210dfc3081..b6b7eccb783 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.cs @@ -426,6 +426,7 @@ static BlockSyntax ApplyStartAnimation(BlockSyntax body, GClass cl, GProperty pr { "bool", "float", + "double", "Vector2", "Vector3", "Vector4", @@ -435,6 +436,7 @@ static BlockSyntax ApplyStartAnimation(BlockSyntax body, GClass cl, GProperty pr "Quaternion", "Color", "Avalonia.Media.Color", + "Vector", "Vector3D" }; diff --git a/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationParserTests.cs b/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationParserTests.cs index fc6ea969d8e..54571384062 100644 --- a/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationParserTests.cs +++ b/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationParserTests.cs @@ -28,12 +28,10 @@ public void EvaluatesExpressionCorrectly(string expression, double value) }; var res = expr.Evaluate(ref ctx); double doubleRes; - if (res.Type == VariantType.Scalar) - doubleRes = res.Scalar; - else if (res.Type == VariantType.Double) + if (res.Type == VariantType.Double) doubleRes = res.Double; else throw new Exception("Invalid result type: " + res.Type); Assert.Equal(value, doubleRes); } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs b/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs index 21ac9c1ae18..9faca90841a 100644 --- a/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs +++ b/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs @@ -1,10 +1,9 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; using Avalonia.Animation.Easings; -using Avalonia.Base.UnitTests.Rendering; +using Avalonia.Controls; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Expressions; @@ -81,7 +80,7 @@ public void VerifyAccess() public void Post(Action action, DispatcherPriority priority = default) => throw new NotSupportedException(); } - + [AnimationDataProvider] [Theory] public void GenericCheck(AnimationData data) @@ -100,11 +99,11 @@ public void GenericCheck(AnimationData data) foreach (var check in data.Checks) { currentValue = instance.Evaluate(TimeSpan.FromSeconds(check.time), currentValue); - Assert.Equal(check.value, currentValue.Scalar); + Assert.Equal(check.value, currentValue.Double); } - + } - + public class AnimationData { public AnimationData(string name) @@ -112,7 +111,7 @@ public AnimationData(string name) Name = name; } - public string Name { get; } + public string Name { get; } public List<(float key, float value)> Frames { get; set; } = new(); public List<(float time, float value)> Checks { get; set; } = new(); public float StartingValue { get; set; } @@ -122,4 +121,124 @@ public override string ToString() return Name; } } + + [Theory] + [InlineData("Color")] + [InlineData("Offset")] + + public void GetCompositionProperty_ReturnsRegisteredProperties(string propName) + { + using var scope = AvaloniaLocator.EnterScope(); + var compositor = new Compositor(RenderLoop.FromTimer(new CompositorTestServices.ManualRenderTimer()), null); + var target = compositor.CreateSolidColorVisual(); + + var property = target.Server.GetCompositionProperty(propName); + + Assert.NotNull(property); + Assert.Equal(propName, property.Name); + Assert.NotNull(property.GetVariant); + } + + [Fact] + public void ExpressionAnimation_Operations_WorksCorrectly() + { + using var scope = AvaloniaLocator.EnterScope(); + var compositor = new Compositor(RenderLoop.FromTimer(new CompositorTestServices.ManualRenderTimer()), null); + var target = compositor.CreateSolidColorVisual(); + target.Server.Offset = new Vector3D(100, 200, 0); + + var ani = compositor.CreateExpressionAnimation("this.Target.Offset.X * 0.5 + 10"); + var instance = ani.CreateInstance(target.Server, null); + instance.Initialize(TimeSpan.Zero, ExpressionVariant.Create(0f), + ServerCompositionVisual.s_IdOfRotationAngleProperty); + + var result = instance.Evaluate(TimeSpan.Zero, ExpressionVariant.Create(0f)); + + Assert.Equal(VariantType.Double, result.Type); + Assert.Equal(60.0, result.Double); + } + + + [Fact] + public void ExpressionAnimation_Tracks_ReferenceParameter() + { + using var scope = AvaloniaLocator.EnterScope(); + var compositor = new Compositor(RenderLoop.FromTimer(new CompositorTestServices.ManualRenderTimer()), null); + var target = compositor.CreateSolidColorVisual(); + var obj = compositor.CreateSolidColorVisual(); + obj.Server.Offset = new Vector3D(100, 200, 0); + + var ani = compositor.CreateExpressionAnimation("obj.Offset.X * 0.5 + 10"); + ani.SetReferenceParameter("obj", obj); + var instance = ani.CreateInstance(target.Server, null); + + target.Server.Activate(); + + // Invoke OnSetAnimatedValue manually to create ServerObjectAnimationInstance. + target.Server.GetOrCreateAnimations(); + var tmp = 0f; + target.Server.Animations!.OnSetAnimatedValue(ServerCompositionVisual.s_IdOfRotationAngleProperty, ref tmp, TimeSpan.Zero, instance); + + var initialResult = instance.Evaluate(TimeSpan.Zero, ExpressionVariant.Create(0f)); + Assert.Equal(60.0, initialResult.Double); + + obj.Server.Offset = new Vector3D(200, 300, 0); + var updatedResult = instance.Evaluate(TimeSpan.Zero, ExpressionVariant.Create(0f)); + Assert.Equal(110.0, updatedResult.Double); + } + + [Fact] + public void ExpressionAnimation_Tracks_Target() + { + using var scope = AvaloniaLocator.EnterScope(); + var compositor = new Compositor(RenderLoop.FromTimer(new CompositorTestServices.ManualRenderTimer()), null); + var target = compositor.CreateSolidColorVisual(); + + target.Server.Offset = new Vector3D(100, 200, 0); + + var ani = compositor.CreateExpressionAnimation("this.Target.Offset.X * 0.5 + 10"); + var instance = ani.CreateInstance(target.Server, null); + + target.Server.Activate(); + + // Invoke OnSetAnimatedValue manually to create ServerObjectAnimationInstance. + target.Server.GetOrCreateAnimations(); + var tmp = 0f; + target.Server.Animations!.OnSetAnimatedValue(ServerCompositionVisual.s_IdOfRotationAngleProperty, ref tmp, TimeSpan.Zero, instance); + + var initialResult = instance.Evaluate(TimeSpan.Zero, ExpressionVariant.Create(0f)); + Assert.Equal(60, initialResult.Double); + + target.Server.Offset = new Vector3D(200, 300, 0); + var updatedResult = instance.Evaluate(TimeSpan.Zero, ExpressionVariant.Create(0f)); + Assert.Equal(110.0, updatedResult.Double); + } + + [Fact] + public void ExpressionAnimation_Requeues_Target_When_Another_Animation_Is_Invalidated_During_Evaluation() + { + using var services = new CompositorTestServices(); + var border = new Border + { + Width = 10, + Height = 10 + }; + + services.TopLevel.Content = border; + services.RunJobs(); + + var visual = ElementComposition.GetElementVisual(border)!; + var opacityAnimation = visual.Compositor.CreateExpressionAnimation("this.Target.RotationAngle * 0.1"); + var rotationAnimation = visual.Compositor.CreateExpressionAnimation("this.Target.Offset.X * 0.5"); + + visual.StartAnimation("Opacity", opacityAnimation); + visual.StartAnimation("RotationAngle", rotationAnimation); + + services.RunJobs(); + visual.Offset = new Vector3D(100, 0, 0); + services.RunJobs(); + + Assert.Equal(50, visual.Server.RotationAngle); + Assert.Equal(5, visual.Server.Opacity); + } }