From ea12af0778dd598126dc1e4517a73994105ea218 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 30 Jun 2020 11:49:10 +0800 Subject: [PATCH] address review and add unit tests --- src/Avalonia.Animation/Easing/Easing.cs | 38 +++++---- src/Avalonia.Animation/Easing/SplineEasing.cs | 10 ++- src/Avalonia.Animation/KeySpline.cs | 14 ++- .../KeySplineTests.cs | 85 +++++++++++++++++++ 4 files changed, 125 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Animation/Easing/Easing.cs b/src/Avalonia.Animation/Easing/Easing.cs index b73091f8b7..8655ee791c 100644 --- a/src/Avalonia.Animation/Easing/Easing.cs +++ b/src/Avalonia.Animation/Easing/Easing.cs @@ -31,28 +31,34 @@ namespace Avalonia.Animation.Easings var k = e.Split(','); if (k.Count() != 4) + { throw new FormatException($"SplineEasing only accepts exactly 4 arguments."); + } + var splineEase = new SplineEasing(); - if (double.TryParse(k[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var x1)) - splineEase.X1 = x1; - else - throw new FormatException($"Invalid SplineEasing control point X1 value: {k[0]}"); + var setterArray = new Action[4] + { + (x) => splineEase.X1 = x, - if (double.TryParse(k[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var y1)) - splineEase.Y1 = y1; - else - throw new FormatException($"Invalid SplineEasing control point Y1 value: {k[1]}"); + (x) => splineEase.Y1 = x, - if (double.TryParse(k[2], NumberStyles.Any, CultureInfo.InvariantCulture, out var x2)) - splineEase.X2 = x2; - else - throw new FormatException($"Invalid SplineEasing control point Y1 value: {k[2]}"); + (x) => splineEase.X2 = x, - if (double.TryParse(k[3], NumberStyles.Any, CultureInfo.InvariantCulture, out var y2)) - splineEase.Y2 = y2; - else - throw new FormatException($"Invalid SplineEasing control point Y1 value: {k[3]}"); + (x) => splineEase.Y2 = x + }; + + for (int i = 0; i < 4; i++) + { + if (double.TryParse(k[i], NumberStyles.Any, CultureInfo.InvariantCulture, out var x)) + { + setterArray[i](x); + } + else + { + throw new FormatException($"Parameter string \"{k[i]}\" is not a double."); + } + } return splineEase; } diff --git a/src/Avalonia.Animation/Easing/SplineEasing.cs b/src/Avalonia.Animation/Easing/SplineEasing.cs index 7192a77531..8eaebba9c7 100644 --- a/src/Avalonia.Animation/Easing/SplineEasing.cs +++ b/src/Avalonia.Animation/Easing/SplineEasing.cs @@ -17,7 +17,8 @@ namespace Avalonia.Animation.Easings get => _x1; set { - _x1 = value; _internalKeySpline.ControlPointX1 = _x1; + _x1 = value; + _internalKeySpline.ControlPointX1 = _x1; } } @@ -30,14 +31,15 @@ namespace Avalonia.Animation.Easings get => _y1; set { - _y1 = value; _internalKeySpline.ControlPointY1 = _y1; + _y1 = value; + _internalKeySpline.ControlPointY1 = _y1; } } /// /// X coordinate of the second control point /// - private double _x2 = 1.0d; + private double _x2; public double X2 { get => _x2; @@ -51,7 +53,7 @@ namespace Avalonia.Animation.Easings /// /// Y coordinate of the second control point /// - private double _y2 = 1.0d; + private double _y2; public double Y2 { get => _y2; diff --git a/src/Avalonia.Animation/KeySpline.cs b/src/Avalonia.Animation/KeySpline.cs index 5a4f7a15a3..f20f3be563 100644 --- a/src/Avalonia.Animation/KeySpline.cs +++ b/src/Avalonia.Animation/KeySpline.cs @@ -98,6 +98,7 @@ namespace Avalonia.Animation if (IsValidXValue(value)) { _controlPointX1 = value; + _isDirty = true; } else { @@ -112,7 +113,11 @@ namespace Avalonia.Animation public double ControlPointY1 { get => _controlPointY1; - set => _controlPointY1 = value; + set + { + _controlPointY1 = value; + _isDirty = true; + } } /// @@ -126,6 +131,7 @@ namespace Avalonia.Animation if (IsValidXValue(value)) { _controlPointX2 = value; + _isDirty = true; } else { @@ -140,7 +146,11 @@ namespace Avalonia.Animation public double ControlPointY2 { get => _controlPointY2; - set => _controlPointY2 = value; + set + { + _controlPointY2 = value; + _isDirty = true; + } } /// diff --git a/tests/Avalonia.Animation.UnitTests/KeySplineTests.cs b/tests/Avalonia.Animation.UnitTests/KeySplineTests.cs index df7c0693e1..1023a59a6b 100644 --- a/tests/Avalonia.Animation.UnitTests/KeySplineTests.cs +++ b/tests/Avalonia.Animation.UnitTests/KeySplineTests.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Animation.Easings; using Avalonia.Controls.Shapes; using Avalonia.Media; using Avalonia.Styling; @@ -46,6 +47,22 @@ namespace Avalonia.Animation.UnitTests Assert.Throws(() => keySpline.ControlPointX2 = input); } + [Fact] + public void SplineEasing_Can_Be_Mutated() + { + var easing = new SplineEasing(); + + Assert.Equal(0, easing.Ease(0)); + Assert.Equal(1, easing.Ease(1)); + + easing.X1 = 0.25; + easing.Y1 = 0.5; + easing.X2 = 0.75; + easing.Y2 = 1.0; + + Assert.NotEqual(0.5, easing.Ease(0.5)); + } + /* To get the test values for the KeySpline test, you can: 1) Grab the WPF sample for KeySpline animations from https://github.com/microsoft/WPF-Samples/tree/master/Animation/KeySplineAnimations @@ -141,5 +158,73 @@ namespace Avalonia.Animation.UnitTests expected = 1.8016358493761722; Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); } + + [Fact] + public void Check_KeySpline_Parsing_Is_Correct() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(RotateTransform.AngleProperty, -2.5d), + }, + KeyTime = TimeSpan.FromSeconds(0) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(RotateTransform.AngleProperty, 2.5d), + }, + KeyTime = TimeSpan.FromSeconds(5), + }; + + var animation = new Animation() + { + Duration = TimeSpan.FromSeconds(5), + Children = + { + keyframe1, + keyframe2 + }, + IterationCount = new IterationCount(5), + PlaybackDirection = PlaybackDirection.Alternate, + Easing = Easing.Parse("0.1123555056179775,0.657303370786517,0.8370786516853934,0.499999999999999999") + }; + + var rotateTransform = new RotateTransform(-2.5); + var rect = new Rectangle() + { + RenderTransform = rotateTransform + }; + + var clock = new TestClock(); + var animationRun = animation.RunAsync(rect, clock); + + // position is what you'd expect at end and beginning + clock.Step(TimeSpan.Zero); + Assert.Equal(rotateTransform.Angle, -2.5); + clock.Step(TimeSpan.FromSeconds(5)); + Assert.Equal(rotateTransform.Angle, 2.5); + + // test some points in between end and beginning + var tolerance = 0.01; + clock.Step(TimeSpan.Parse("00:00:10.0153932")); + var expected = -2.4122350198982545; + Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); + + clock.Step(TimeSpan.Parse("00:00:11.2655407")); + expected = -0.37153223002125113; + Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); + + clock.Step(TimeSpan.Parse("00:00:12.6158773")); + expected = 0.3967885416786294; + Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); + + clock.Step(TimeSpan.Parse("00:00:14.6495256")); + expected = 1.8016358493761722; + Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); + } } }