KeySplineTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. using System;
  2. using Avalonia.Animation;
  3. using Avalonia.Animation.Easings;
  4. using Avalonia.Controls.Shapes;
  5. using Avalonia.Media;
  6. using Avalonia.Styling;
  7. using Xunit;
  8. namespace Avalonia.Base.UnitTests.Animation
  9. {
  10. public class KeySplineTests
  11. {
  12. [Theory]
  13. [InlineData("1,2 3,4")]
  14. [InlineData("1 2 3 4")]
  15. [InlineData("1 2,3 4")]
  16. [InlineData("1,2,3,4")]
  17. public void Can_Parse_KeySpline_Via_TypeConverter(string input)
  18. {
  19. var conv = new KeySplineTypeConverter();
  20. var keySpline = (KeySpline)conv.ConvertFrom(input);
  21. Assert.NotNull(keySpline);
  22. Assert.Equal(1, keySpline.ControlPointX1);
  23. Assert.Equal(2, keySpline.ControlPointY1);
  24. Assert.Equal(3, keySpline.ControlPointX2);
  25. Assert.Equal(4, keySpline.ControlPointY2);
  26. }
  27. [Theory]
  28. [InlineData("1,2F,3,4")]
  29. [InlineData("Foo,Bar,Fee,Buzz")]
  30. public void Can_Handle_Invalid_String_KeySpline_Via_TypeConverter(string input)
  31. {
  32. var conv = new KeySplineTypeConverter();
  33. Assert.ThrowsAny<Exception>(() => (KeySpline)conv.ConvertFrom(input));
  34. }
  35. [Theory]
  36. [InlineData(0.00)]
  37. [InlineData(0.50)]
  38. [InlineData(1.00)]
  39. public void KeySpline_X_Values_In_Range_Do_Not_Throw(double input)
  40. {
  41. var keySpline = new KeySpline();
  42. keySpline.ControlPointX1 = input; // no exception will be thrown -- test will fail if exception thrown
  43. keySpline.ControlPointX2 = input; // no exception will be thrown -- test will fail if exception thrown
  44. }
  45. [Theory]
  46. [InlineData(-0.01)]
  47. [InlineData(1.01)]
  48. public void KeySpline_X_Values_Cannot_Be_Out_Of_Range(double input)
  49. {
  50. var keySpline = new KeySpline();
  51. Assert.Throws<ArgumentException>(() => keySpline.ControlPointX1 = input);
  52. Assert.Throws<ArgumentException>(() => keySpline.ControlPointX2 = input);
  53. }
  54. [Fact]
  55. public void SplineEasing_Can_Be_Mutated()
  56. {
  57. var easing = new SplineEasing();
  58. Assert.Equal(0, easing.Ease(0));
  59. Assert.Equal(1, easing.Ease(1));
  60. easing.X1 = 0.25;
  61. easing.Y1 = 0.5;
  62. easing.X2 = 0.75;
  63. easing.Y2 = 1.0;
  64. Assert.NotEqual(0.5, easing.Ease(0.5));
  65. }
  66. /*
  67. To get the test values for the KeySpline test, you can:
  68. 1) Grab the WPF sample for KeySpline animations from https://github.com/microsoft/WPF-Samples/tree/master/Animation/KeySplineAnimations
  69. 2) Add the following xaml somewhere:
  70. <Button Content="Capture"
  71. Click="Button_Click"/>
  72. <ScrollViewer VerticalScrollBarVisibility="Visible">
  73. <TextBlock Name="CaptureData"
  74. Text="---"
  75. TextWrapping="Wrap" />
  76. </ScrollViewer>
  77. 3) Add the following code to the code behind:
  78. private void Button_Click(object sender, RoutedEventArgs e)
  79. {
  80. CaptureData.Text += string.Format("\n{0} | {1}", myTranslateTransform3D.OffsetX, (TimeSpan)ExampleStoryboard.GetCurrentTime(this));
  81. CaptureData.Text +=
  82. "\nKeySpline=\"" + mySplineKeyFrame.KeySpline.ControlPoint1.X.ToString() + "," +
  83. mySplineKeyFrame.KeySpline.ControlPoint1.Y.ToString() + " " +
  84. mySplineKeyFrame.KeySpline.ControlPoint2.X.ToString() + "," +
  85. mySplineKeyFrame.KeySpline.ControlPoint2.Y.ToString() + "\"";
  86. CaptureData.Text += "\n-----";
  87. }
  88. 4) Run the app, mess with the slider values, then click the button to capture output values
  89. **/
  90. [Fact]
  91. public void Check_KeySpline_Handled_properly()
  92. {
  93. var keyframe1 = new KeyFrame()
  94. {
  95. Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0)
  96. };
  97. var keyframe2 = new KeyFrame()
  98. {
  99. Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), },
  100. KeyTime = TimeSpan.FromSeconds(5),
  101. KeySpline = new KeySpline(0.1123555056179775,
  102. 0.657303370786517,
  103. 0.8370786516853934,
  104. 0.499999999999999999)
  105. };
  106. var animation = new Avalonia.Animation.Animation()
  107. {
  108. Duration = TimeSpan.FromSeconds(5),
  109. Children = { keyframe1, keyframe2 },
  110. IterationCount = new IterationCount(5),
  111. PlaybackDirection = PlaybackDirection.Alternate
  112. };
  113. var rotateTransform = new RotateTransform(-2.5);
  114. var rect = new Rectangle() { RenderTransform = rotateTransform };
  115. var clock = new TestClock();
  116. animation.RunAsync(rect, clock);
  117. // position is what you'd expect at end and beginning
  118. clock.Step(TimeSpan.Zero);
  119. Assert.Equal(rotateTransform.Angle, -2.5);
  120. clock.Step(TimeSpan.FromSeconds(5));
  121. Assert.Equal(rotateTransform.Angle, 2.5);
  122. // test some points in between end and beginning
  123. var tolerance = 0.01;
  124. clock.Step(TimeSpan.Parse("00:00:10.0153932"));
  125. var expected = -2.4122350198982545;
  126. Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
  127. clock.Step(TimeSpan.Parse("00:00:11.2655407"));
  128. expected = -0.37153223002125113;
  129. Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
  130. clock.Step(TimeSpan.Parse("00:00:12.6158773"));
  131. expected = 0.3967885416786294;
  132. Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
  133. clock.Step(TimeSpan.Parse("00:00:14.6495256"));
  134. expected = 1.8016358493761722;
  135. Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
  136. }
  137. [Fact]
  138. public void Check_KeySpline_Parsing_Is_Correct()
  139. {
  140. var keyframe1 = new KeyFrame()
  141. {
  142. Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0)
  143. };
  144. var keyframe2 = new KeyFrame()
  145. {
  146. Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), }, KeyTime = TimeSpan.FromSeconds(5),
  147. };
  148. var animation = new Avalonia.Animation.Animation()
  149. {
  150. Duration = TimeSpan.FromSeconds(5),
  151. Children = { keyframe1, keyframe2 },
  152. IterationCount = new IterationCount(5),
  153. PlaybackDirection = PlaybackDirection.Alternate,
  154. Easing = Easing.Parse(
  155. "0.1123555056179775,0.657303370786517,0.8370786516853934,0.499999999999999999")
  156. };
  157. var rotateTransform = new RotateTransform(-2.5);
  158. var rect = new Rectangle() { RenderTransform = rotateTransform };
  159. var clock = new TestClock();
  160. animation.RunAsync(rect, clock);
  161. // position is what you'd expect at end and beginning
  162. clock.Step(TimeSpan.Zero);
  163. Assert.Equal(rotateTransform.Angle, -2.5);
  164. clock.Step(TimeSpan.FromSeconds(5));
  165. Assert.Equal(rotateTransform.Angle, 2.5);
  166. // test some points in between end and beginning
  167. var tolerance = 0.01;
  168. clock.Step(TimeSpan.Parse("00:00:10.0153932"));
  169. var expected = -2.4122350198982545;
  170. Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
  171. clock.Step(TimeSpan.Parse("00:00:11.2655407"));
  172. expected = -0.37153223002125113;
  173. Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
  174. clock.Step(TimeSpan.Parse("00:00:12.6158773"));
  175. expected = 0.3967885416786294;
  176. Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
  177. clock.Step(TimeSpan.Parse("00:00:14.6495256"));
  178. expected = 1.8016358493761722;
  179. Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
  180. }
  181. // https://github.com/AvaloniaUI/Avalonia/issues/15704
  182. [Theory]
  183. [InlineData(nameof(BackEaseIn))]
  184. [InlineData(nameof(BackEaseOut))]
  185. [InlineData(nameof(BackEaseInOut))]
  186. [InlineData(nameof(ElasticEaseIn))]
  187. [InlineData(nameof(ElasticEaseOut))]
  188. [InlineData(nameof(ElasticEaseInOut))]
  189. public void KeySpline_Progress_Less_Than_Zero_Or_Greater_Than_One_Works(string easingType)
  190. {
  191. var easing = Easing.Parse(easingType);
  192. var animation = new Avalonia.Animation.Animation
  193. {
  194. Duration = TimeSpan.FromSeconds(1.0),
  195. Children =
  196. {
  197. new KeyFrame
  198. {
  199. Cue = new Cue(0.0),
  200. Setters = { new Setter(TranslateTransform.YProperty, 10.0) }
  201. },
  202. new KeyFrame
  203. {
  204. Cue = new Cue(1.0),
  205. Setters = { new Setter(TranslateTransform.YProperty, 20.0) }
  206. }
  207. },
  208. IterationCount = new IterationCount(5),
  209. PlaybackDirection = PlaybackDirection.Alternate,
  210. Easing = easing
  211. };
  212. var transform = new TranslateTransform(0.0, 50.0);
  213. var rect = new Rectangle { RenderTransform = transform };
  214. var clock = new TestClock();
  215. animation.RunAsync(rect, clock);
  216. clock.Step(TimeSpan.Zero);
  217. Assert.Equal(10.0, transform.Y, 0.0001);
  218. for (var time = TimeSpan.FromSeconds(0.1); time < animation.Duration; time += TimeSpan.FromSeconds(0.1))
  219. {
  220. clock.Step(time);
  221. Assert.True(double.IsFinite(transform.Y));
  222. Assert.NotEqual(10.0, transform.Y);
  223. Assert.NotEqual(20.0, transform.Y);
  224. }
  225. clock.Step(animation.Duration);
  226. Assert.Equal(20.0, transform.Y, 0.0001);
  227. }
  228. [Theory]
  229. [InlineData(nameof(BackEaseIn))]
  230. [InlineData(nameof(BackEaseOut))]
  231. [InlineData(nameof(BackEaseInOut))]
  232. [InlineData(nameof(ElasticEaseIn))]
  233. [InlineData(nameof(ElasticEaseOut))]
  234. [InlineData(nameof(ElasticEaseInOut))]
  235. public void KeySpline_Progress_Less_Than_Zero_Or_Greater_Than_One_Works_With_Single_KeyFrame(string easingType)
  236. {
  237. var easing = Easing.Parse(easingType);
  238. var animation = new Avalonia.Animation.Animation
  239. {
  240. Duration = TimeSpan.FromSeconds(1.0),
  241. Children =
  242. {
  243. new KeyFrame
  244. {
  245. Cue = new Cue(1.0),
  246. Setters = { new Setter(TranslateTransform.YProperty, 10.0) }
  247. }
  248. },
  249. IterationCount = new IterationCount(5),
  250. PlaybackDirection = PlaybackDirection.Alternate,
  251. Easing = easing
  252. };
  253. var transform = new TranslateTransform(0.0, 50.0);
  254. var rect = new Rectangle { RenderTransform = transform };
  255. var clock = new TestClock();
  256. animation.RunAsync(rect, clock);
  257. clock.Step(TimeSpan.Zero);
  258. Assert.Equal(50.0, transform.Y, 0.0001);
  259. for (var time = TimeSpan.FromSeconds(0.1); time < animation.Duration; time += TimeSpan.FromSeconds(0.1))
  260. {
  261. clock.Step(time);
  262. Assert.True(double.IsFinite(transform.Y));
  263. Assert.NotEqual(50.0, transform.Y);
  264. Assert.NotEqual(10.0, transform.Y);
  265. }
  266. clock.Step(animation.Duration);
  267. Assert.Equal(10.0, transform.Y, 0.0001);
  268. }
  269. }
  270. }