From 6f661f15a1fc3c010d6c2a9da077085ceb84c2b2 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 27 Jun 2020 15:41:21 +0800 Subject: [PATCH 01/12] Add spline easing class --- src/Avalonia.Animation/Easing/SplineEasing.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/Avalonia.Animation/Easing/SplineEasing.cs diff --git a/src/Avalonia.Animation/Easing/SplineEasing.cs b/src/Avalonia.Animation/Easing/SplineEasing.cs new file mode 100644 index 0000000000..7192a77531 --- /dev/null +++ b/src/Avalonia.Animation/Easing/SplineEasing.cs @@ -0,0 +1,85 @@ +namespace Avalonia.Animation.Easings +{ + /// + /// Eases a value + /// using a user-defined cubic bezier curve. + /// Good for custom easing functions that doesn't quite + /// fit with the built-in ones. + /// + public class SplineEasing : Easing + { + /// + /// X coordinate of the first control point + /// + private double _x1; + public double X1 + { + get => _x1; + set + { + _x1 = value; _internalKeySpline.ControlPointX1 = _x1; + } + } + + /// + /// Y coordinate of the first control point + /// + private double _y1; + public double Y1 + { + get => _y1; + set + { + _y1 = value; _internalKeySpline.ControlPointY1 = _y1; + } + } + + /// + /// X coordinate of the second control point + /// + private double _x2 = 1.0d; + public double X2 + { + get => _x2; + set + { + _x2 = value; + _internalKeySpline.ControlPointX2 = _x2; + } + } + + /// + /// Y coordinate of the second control point + /// + private double _y2 = 1.0d; + public double Y2 + { + get => _y2; + set + { + _y2 = value; + _internalKeySpline.ControlPointY2 = _y2; + } + } + + private KeySpline _internalKeySpline; + + public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d) : base() + { + this._internalKeySpline = new KeySpline(); + this.X1 = x1; + this.Y1 = y1; + this.X2 = x2; + this.Y1 = y2; + } + + public SplineEasing() + { + this._internalKeySpline = new KeySpline(); + } + + /// + public override double Ease(double progress) => + _internalKeySpline.GetSplineProgress(progress); + } +} From 82f3c2f0c0aaabc994fe0193a58d762967959ceb Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 27 Jun 2020 16:00:20 +0800 Subject: [PATCH 02/12] Add parser for spline easing --- src/Avalonia.Animation/Easing/Easing.cs | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Avalonia.Animation/Easing/Easing.cs b/src/Avalonia.Animation/Easing/Easing.cs index 5b0dea6c60..b73091f8b7 100644 --- a/src/Avalonia.Animation/Easing/Easing.cs +++ b/src/Avalonia.Animation/Easing/Easing.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Globalization; using System.Linq; namespace Avalonia.Animation.Easings @@ -25,6 +26,37 @@ namespace Avalonia.Animation.Easings /// Returns the instance of the parsed type. public static Easing Parse(string e) { + if (e.Contains(',')) + { + 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]}"); + + 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]}"); + + 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]}"); + + 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]}"); + + return splineEase; + } + if (_easingTypes == null) { _easingTypes = new Dictionary(); From 3f16bc52545c01f68b179b0553178efe93419242 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 27 Jun 2020 20:13:27 +0800 Subject: [PATCH 03/12] Update SplineEasing.cs --- src/Avalonia.Animation/Easing/SplineEasing.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Animation/Easing/SplineEasing.cs b/src/Avalonia.Animation/Easing/SplineEasing.cs index 7192a77531..09b56796c5 100644 --- a/src/Avalonia.Animation/Easing/SplineEasing.cs +++ b/src/Avalonia.Animation/Easing/SplineEasing.cs @@ -8,10 +8,29 @@ namespace Avalonia.Animation.Easings /// public class SplineEasing : Easing { + public SplineEasing() + { + this._internalKeySpline = new KeySpline(); + } + + public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d) + { + this._internalKeySpline = new KeySpline(); + this.X1 = x1; + this.Y1 = y1; + this.X2 = x2; + this.Y1 = y2; + } + + private KeySpline _internalKeySpline; + private double _x1; + private double _y1; + private double _x2 = 1.0d; + private double _y2 = 1.0d; + /// /// X coordinate of the first control point /// - private double _x1; public double X1 { get => _x1; @@ -24,7 +43,6 @@ namespace Avalonia.Animation.Easings /// /// Y coordinate of the first control point /// - private double _y1; public double Y1 { get => _y1; @@ -37,7 +55,6 @@ namespace Avalonia.Animation.Easings /// /// X coordinate of the second control point /// - private double _x2 = 1.0d; public double X2 { get => _x2; @@ -51,7 +68,6 @@ namespace Avalonia.Animation.Easings /// /// Y coordinate of the second control point /// - private double _y2 = 1.0d; public double Y2 { get => _y2; @@ -61,23 +77,7 @@ namespace Avalonia.Animation.Easings _internalKeySpline.ControlPointY2 = _y2; } } - - private KeySpline _internalKeySpline; - - public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d) : base() - { - this._internalKeySpline = new KeySpline(); - this.X1 = x1; - this.Y1 = y1; - this.X2 = x2; - this.Y1 = y2; - } - - public SplineEasing() - { - this._internalKeySpline = new KeySpline(); - } - + /// public override double Ease(double progress) => _internalKeySpline.GetSplineProgress(progress); From ea12af0778dd598126dc1e4517a73994105ea218 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 30 Jun 2020 11:49:10 +0800 Subject: [PATCH 04/12] 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); + } } } From 64d943c2b5f1be94af928496142219336d20d219 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 30 Jun 2020 11:53:28 +0800 Subject: [PATCH 05/12] remove redundant base call --- src/Avalonia.Animation/Easing/SplineEasing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/Easing/SplineEasing.cs b/src/Avalonia.Animation/Easing/SplineEasing.cs index 1adc565f9f..9757d1aeaf 100644 --- a/src/Avalonia.Animation/Easing/SplineEasing.cs +++ b/src/Avalonia.Animation/Easing/SplineEasing.cs @@ -66,7 +66,7 @@ namespace Avalonia.Animation.Easings private readonly KeySpline _internalKeySpline; - public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d) : base() + public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d) { this._internalKeySpline = new KeySpline(); this.X1 = x1; From 3709ca92e0495b2097e85b26975600138b5f61cb Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 30 Jun 2020 11:55:30 +0800 Subject: [PATCH 06/12] use internal keyspline as backing field to save memory. --- src/Avalonia.Animation/Easing/SplineEasing.cs | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Animation/Easing/SplineEasing.cs b/src/Avalonia.Animation/Easing/SplineEasing.cs index 9757d1aeaf..2b075ed2e4 100644 --- a/src/Avalonia.Animation/Easing/SplineEasing.cs +++ b/src/Avalonia.Animation/Easing/SplineEasing.cs @@ -11,56 +11,48 @@ namespace Avalonia.Animation.Easings /// /// X coordinate of the first control point /// - private double _x1; public double X1 { - get => _x1; + get => _internalKeySpline.ControlPointX1; set { - _x1 = value; - _internalKeySpline.ControlPointX1 = _x1; + _internalKeySpline.ControlPointX1 = value; } } /// /// Y coordinate of the first control point /// - private double _y1; public double Y1 { - get => _y1; + get => _internalKeySpline.ControlPointY1; set { - _y1 = value; - _internalKeySpline.ControlPointY1 = _y1; + _internalKeySpline.ControlPointY1 = value; } } /// /// X coordinate of the second control point - /// - private double _x2; + /// public double X2 { - get => _x2; + get => _internalKeySpline.ControlPointX2; set { - _x2 = value; - _internalKeySpline.ControlPointX2 = _x2; + _internalKeySpline.ControlPointX2 = value; } } /// /// Y coordinate of the second control point /// - private double _y2; public double Y2 { - get => _y2; + get => _internalKeySpline.ControlPointY2; set { - _y2 = value; - _internalKeySpline.ControlPointY2 = _y2; + _internalKeySpline.ControlPointY2 = value; } } From 591a5ce00598d4cb8d79c56e296c119830c51c53 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 30 Jun 2020 11:56:24 +0800 Subject: [PATCH 07/12] remove redundant this call --- src/Avalonia.Animation/Easing/SplineEasing.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Animation/Easing/SplineEasing.cs b/src/Avalonia.Animation/Easing/SplineEasing.cs index 2b075ed2e4..58d30bea51 100644 --- a/src/Avalonia.Animation/Easing/SplineEasing.cs +++ b/src/Avalonia.Animation/Easing/SplineEasing.cs @@ -60,7 +60,8 @@ namespace Avalonia.Animation.Easings public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d) { - this._internalKeySpline = new KeySpline(); + _internalKeySpline = new KeySpline(); + this.X1 = x1; this.Y1 = y1; this.X2 = x2; @@ -69,7 +70,7 @@ namespace Avalonia.Animation.Easings public SplineEasing() { - this._internalKeySpline = new KeySpline(); + _internalKeySpline = new KeySpline(); } /// From dcaadd0fff71adcf88fdb8595f2878e890ba7354 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 30 Jun 2020 12:03:06 +0800 Subject: [PATCH 08/12] Split-off keysplinetypeconv --- src/Avalonia.Animation/KeySpline.cs | 16 ------------ .../KeySplineTypeConverter.cs | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 src/Avalonia.Animation/KeySplineTypeConverter.cs diff --git a/src/Avalonia.Animation/KeySpline.cs b/src/Avalonia.Animation/KeySpline.cs index f20f3be563..59d7132f7c 100644 --- a/src/Avalonia.Animation/KeySpline.cs +++ b/src/Avalonia.Animation/KeySpline.cs @@ -340,20 +340,4 @@ namespace Avalonia.Animation } } } - - /// - /// Converts string values to values - /// - public class KeySplineTypeConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - return sourceType == typeof(string); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - return KeySpline.Parse((string)value, culture); - } - } } diff --git a/src/Avalonia.Animation/KeySplineTypeConverter.cs b/src/Avalonia.Animation/KeySplineTypeConverter.cs new file mode 100644 index 0000000000..cd7427a37d --- /dev/null +++ b/src/Avalonia.Animation/KeySplineTypeConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel; +using System.Globalization; + +// Ported from WPF open-source code. +// https://github.com/dotnet/wpf/blob/ae1790531c3b993b56eba8b1f0dd395a3ed7de75/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Animation/KeySpline.cs + +namespace Avalonia.Animation +{ + /// + /// Converts string values to values + /// + public class KeySplineTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + return KeySpline.Parse((string)value, culture); + } + } +} From 4310585d7e1dfcf9b4d28c28b586fa0163cb4499 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 30 Jun 2020 12:15:52 +0800 Subject: [PATCH 09/12] simplify parsing --- src/Avalonia.Animation/Easing/Easing.cs | 34 +------------------ src/Avalonia.Animation/Easing/SplineEasing.cs | 5 +++ src/Avalonia.Animation/KeySpline.cs | 5 ++- 3 files changed, 10 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Animation/Easing/Easing.cs b/src/Avalonia.Animation/Easing/Easing.cs index 8655ee791c..e006459652 100644 --- a/src/Avalonia.Animation/Easing/Easing.cs +++ b/src/Avalonia.Animation/Easing/Easing.cs @@ -28,39 +28,7 @@ namespace Avalonia.Animation.Easings { if (e.Contains(',')) { - var k = e.Split(','); - - if (k.Count() != 4) - { - throw new FormatException($"SplineEasing only accepts exactly 4 arguments."); - } - - var splineEase = new SplineEasing(); - - var setterArray = new Action[4] - { - (x) => splineEase.X1 = x, - - (x) => splineEase.Y1 = x, - - (x) => splineEase.X2 = x, - - (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; + return new SplineEasing(KeySpline.Parse(e, CultureInfo.InvariantCulture)); } if (_easingTypes == null) diff --git a/src/Avalonia.Animation/Easing/SplineEasing.cs b/src/Avalonia.Animation/Easing/SplineEasing.cs index 58d30bea51..975fcc4746 100644 --- a/src/Avalonia.Animation/Easing/SplineEasing.cs +++ b/src/Avalonia.Animation/Easing/SplineEasing.cs @@ -68,6 +68,11 @@ namespace Avalonia.Animation.Easings this.Y1 = y2; } + public SplineEasing(KeySpline keySpline) + { + _internalKeySpline = keySpline; + } + public SplineEasing() { _internalKeySpline = new KeySpline(); diff --git a/src/Avalonia.Animation/KeySpline.cs b/src/Avalonia.Animation/KeySpline.cs index 59d7132f7c..a6e9769186 100644 --- a/src/Avalonia.Animation/KeySpline.cs +++ b/src/Avalonia.Animation/KeySpline.cs @@ -81,7 +81,10 @@ namespace Avalonia.Animation /// A with the appropriate values set public static KeySpline Parse(string value, CultureInfo culture) { - using (var tokenizer = new StringTokenizer((string)value, CultureInfo.InvariantCulture, exceptionMessage: "Invalid KeySpline.")) + if (culture is null) + culture = CultureInfo.InvariantCulture; + + using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".")) { return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble()); } From baf63bfeec0116f59f43667a7ba11add19397cfd Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 30 Jun 2020 12:19:55 +0800 Subject: [PATCH 10/12] add failing test --- tests/Avalonia.Animation.UnitTests/KeySplineTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Avalonia.Animation.UnitTests/KeySplineTests.cs b/tests/Avalonia.Animation.UnitTests/KeySplineTests.cs index 1023a59a6b..fa2ed61e65 100644 --- a/tests/Avalonia.Animation.UnitTests/KeySplineTests.cs +++ b/tests/Avalonia.Animation.UnitTests/KeySplineTests.cs @@ -26,6 +26,16 @@ namespace Avalonia.Animation.UnitTests Assert.Equal(4, keySpline.ControlPointY2); } + [Theory] + [InlineData("1,2F,3,4")] + [InlineData("Foo,Bar,Fee,Buzz")] + public void Can_Handle_Invalid_String_KeySpline_Via_TypeConverter(string input) + { + var conv = new KeySplineTypeConverter(); + + Assert.ThrowsAny(() => (KeySpline)conv.ConvertFrom(input)); + } + [Theory] [InlineData(0.00)] [InlineData(0.50)] From 3145dfa05c6ce73a4b82c995ce141060e57e02f7 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Tue, 30 Jun 2020 16:18:43 +0800 Subject: [PATCH 11/12] Delete WindowsMountedVolumeInfoListener.cs --- .../WindowsMountedVolumeInfoListener.cs | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs deleted file mode 100644 index db4c916052..0000000000 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using Avalonia.Controls.Platform; - -namespace Avalonia.Win32 -{ - internal class WindowsMountedVolumeInfoListener : IDisposable - { - private readonly CompositeDisposable _disposables; - private bool _beenDisposed = false; - private ObservableCollection mountedDrives; - - public WindowsMountedVolumeInfoListener(ObservableCollection mountedDrives) - { - this.mountedDrives = mountedDrives; - _disposables = new CompositeDisposable(); - - var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1)) - .Subscribe(Poll); - - _disposables.Add(pollTimer); - - Poll(0); - } - - private void Poll(long _) - { - var allDrives = DriveInfo.GetDrives(); - - var mountVolInfos = allDrives - .Where(p => p.IsReady) - .Select(p => new MountedVolumeInfo() - { - VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName - : $"{p.VolumeLabel} ({p.Name})", - VolumePath = p.RootDirectory.FullName, - VolumeSizeBytes = (ulong)p.TotalSize - }) - .ToArray(); - - if (mountedDrives.SequenceEqual(mountVolInfos)) - return; - else - { - mountedDrives.Clear(); - - foreach (var i in mountVolInfos) - mountedDrives.Add(i); - } - } - - protected virtual void Dispose(bool disposing) - { - if (!_beenDisposed) - { - if (disposing) - { - - } - _beenDisposed = true; - } - } - public void Dispose() - { - Dispose(true); - } - } -} From af05518a44f164e99da0ab52273604e0c127cab0 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 30 Jun 2020 22:54:31 +0800 Subject: [PATCH 12/12] reverse unintended changes --- .ncrunch/NativeEmbedSample.v3.ncrunchproject | 5 ++ .../WindowsMountedVolumeInfoListener.cs | 85 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 .ncrunch/NativeEmbedSample.v3.ncrunchproject create mode 100644 src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs diff --git a/.ncrunch/NativeEmbedSample.v3.ncrunchproject b/.ncrunch/NativeEmbedSample.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/NativeEmbedSample.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs new file mode 100644 index 0000000000..ba1bfda949 --- /dev/null +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using Avalonia.Controls.Platform; +using Avalonia.Logging; + +namespace Avalonia.Win32 +{ + internal class WindowsMountedVolumeInfoListener : IDisposable + { + private readonly CompositeDisposable _disposables; + private bool _beenDisposed = false; + private ObservableCollection mountedDrives; + + public WindowsMountedVolumeInfoListener(ObservableCollection mountedDrives) + { + this.mountedDrives = mountedDrives; + _disposables = new CompositeDisposable(); + + var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1)) + .Subscribe(Poll); + + _disposables.Add(pollTimer); + + Poll(0); + } + + private void Poll(long _) + { + var allDrives = DriveInfo.GetDrives(); + + var mountVolInfos = allDrives + .Where(p => + { + try + { + var ret = p.IsReady; + return ret; + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(this, $"Error in Windows drive enumeration: {e.Message}"); + } + return false; + }) + .Select(p => new MountedVolumeInfo() + { + VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName + : $"{p.VolumeLabel} ({p.Name})", + VolumePath = p.RootDirectory.FullName, + VolumeSizeBytes = (ulong)p.TotalSize + }) + .ToArray(); + + if (mountedDrives.SequenceEqual(mountVolInfos)) + return; + else + { + mountedDrives.Clear(); + + foreach (var i in mountVolInfos) + mountedDrives.Add(i); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!_beenDisposed) + { + if (disposing) + { + + } + _beenDisposed = true; + } + } + public void Dispose() + { + Dispose(true); + } + } +}