AnimationIterationTests.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. using System;
  2. using System.Threading.Tasks;
  3. using Avalonia.Animation;
  4. using Avalonia.Controls;
  5. using Avalonia.Styling;
  6. using Xunit;
  7. using Avalonia.Animation.Easings;
  8. using System.Threading;
  9. using System.Reactive.Linq;
  10. using Avalonia.Layout;
  11. namespace Avalonia.Base.UnitTests.Animation
  12. {
  13. using Animation = Avalonia.Animation.Animation;
  14. public class AnimationIterationTests
  15. {
  16. [Fact]
  17. public void Check_KeyTime_Correctly_Converted_To_Cue()
  18. {
  19. var keyframe1 = new KeyFrame()
  20. {
  21. Setters = { new Setter(Layoutable.WidthProperty, 100d), }, KeyTime = TimeSpan.FromSeconds(0.5)
  22. };
  23. var keyframe2 = new KeyFrame()
  24. {
  25. Setters = { new Setter(Layoutable.WidthProperty, 0d), }, KeyTime = TimeSpan.FromSeconds(0)
  26. };
  27. var animation = new Animation() { Duration = TimeSpan.FromSeconds(1), Children = { keyframe2, keyframe1 } };
  28. var border = new Border() { Height = 100d, Width = 100d };
  29. var clock = new TestClock();
  30. animation.RunAsync(border, clock);
  31. clock.Step(TimeSpan.Zero);
  32. Assert.Equal(border.Width, 0d);
  33. clock.Step(TimeSpan.FromSeconds(1));
  34. Assert.Equal(border.Width, 100d);
  35. }
  36. [Fact]
  37. public void Check_Initial_Inter_and_Trailing_Delay_Values()
  38. {
  39. var keyframe1 = new KeyFrame()
  40. {
  41. Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
  42. };
  43. var keyframe2 = new KeyFrame()
  44. {
  45. Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
  46. };
  47. var animation = new Animation()
  48. {
  49. Duration = TimeSpan.FromSeconds(3),
  50. Delay = TimeSpan.FromSeconds(3),
  51. DelayBetweenIterations = TimeSpan.FromSeconds(3),
  52. IterationCount = new IterationCount(2),
  53. Children = { keyframe2, keyframe1 }
  54. };
  55. var border = new Border() { Height = 100d, Width = 100d };
  56. var clock = new TestClock();
  57. var animationRun = animation.RunAsync(border, clock);
  58. border.Measure(Size.Infinity);
  59. border.Arrange(new Rect(border.DesiredSize));
  60. clock.Step(TimeSpan.Zero);
  61. // Initial Delay.
  62. clock.Step(TimeSpan.FromSeconds(0));
  63. Assert.Equal(100d, border.Width);
  64. clock.Step(TimeSpan.FromSeconds(6));
  65. // First Inter-Iteration delay.
  66. clock.Step(TimeSpan.FromSeconds(8));
  67. Assert.Equal(border.Width, 200d);
  68. // Trailing Delay should be non-existent.
  69. clock.Step(TimeSpan.FromSeconds(14));
  70. Assert.True(animationRun.Status == TaskStatus.RanToCompletion);
  71. Assert.Equal(border.Width, 100d);
  72. }
  73. [Fact]
  74. public void Check_FillModes_Start_and_End_Values_if_Retained()
  75. {
  76. var keyframe1 = new KeyFrame()
  77. {
  78. Setters = { new Setter(Layoutable.WidthProperty, 0d), }, Cue = new Cue(0.0d)
  79. };
  80. var keyframe2 = new KeyFrame()
  81. {
  82. Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(1.0d)
  83. };
  84. var animation = new Animation()
  85. {
  86. Duration = TimeSpan.FromSeconds(0.05d),
  87. Delay = TimeSpan.FromSeconds(0.05d),
  88. Easing = new SineEaseInOut(),
  89. FillMode = FillMode.Both,
  90. Children = { keyframe1, keyframe2 }
  91. };
  92. var border = new Border() { Height = 100d, Width = 100d, };
  93. var clock = new TestClock();
  94. animation.RunAsync(border, clock);
  95. clock.Step(TimeSpan.FromSeconds(0d));
  96. Assert.Equal(border.Width, 0d);
  97. clock.Step(TimeSpan.FromSeconds(0.050d));
  98. Assert.Equal(border.Width, 0d);
  99. clock.Step(TimeSpan.FromSeconds(0.100d));
  100. Assert.Equal(border.Width, 300d);
  101. }
  102. [Theory]
  103. [InlineData(FillMode.Backward, 50.0, 0.0, 0.7, false)]
  104. [InlineData(FillMode.Backward, 50.0, 0.0, 0.7, true )]
  105. [InlineData(FillMode.Both, 50.0, 0.0, 0.7, false)]
  106. [InlineData(FillMode.Both, 50.0, 0.0, 0.7, true )]
  107. [InlineData(FillMode.Forward, 50.0, 0.0, 0.7, false)] // no delay but cue 0.0: the animation has started normally, explaining the 50.0 target without fill
  108. [InlineData(FillMode.Forward, 100.0, 0.0, 0.7, true )]
  109. [InlineData(FillMode.Backward, 50.0, 0.3, 0.7, false)]
  110. [InlineData(FillMode.Backward, 50.0, 0.3, 0.7, true )]
  111. [InlineData(FillMode.Both, 50.0, 0.3, 0.7, false)]
  112. [InlineData(FillMode.Both, 50.0, 0.3, 0.7, true )]
  113. [InlineData(FillMode.Forward, 100.0, 0.3, 0.7, false)]
  114. [InlineData(FillMode.Forward, 100.0, 0.3, 0.7, true )]
  115. public void Check_FillMode_Start_Value(FillMode fillMode, double target, double startCue, double endCue, bool delay)
  116. {
  117. var keyframe1 = new KeyFrame()
  118. {
  119. Setters = { new Setter(Layoutable.WidthProperty, 50d), }, Cue = new Cue(startCue)
  120. };
  121. var keyframe2 = new KeyFrame()
  122. {
  123. Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(endCue)
  124. };
  125. var animation = new Animation()
  126. {
  127. Duration = TimeSpan.FromSeconds(10d),
  128. Delay = delay ? TimeSpan.FromSeconds(5d) : TimeSpan.Zero,
  129. FillMode = fillMode,
  130. Children = { keyframe1, keyframe2 }
  131. };
  132. var border = new Border() { Height = 100d, Width = 100d, };
  133. var clock = new TestClock();
  134. animation.RunAsync(border, clock);
  135. clock.Step(TimeSpan.Zero);
  136. Assert.Equal(target, border.Width);
  137. }
  138. [Theory]
  139. [InlineData(FillMode.Backward, 100.0, 0.3, 1.0, false)]
  140. [InlineData(FillMode.Backward, 100.0, 0.3, 1.0, true )]
  141. [InlineData(FillMode.Both, 300.0, 0.3, 1.0, false)]
  142. [InlineData(FillMode.Both, 300.0, 0.3, 1.0, true )]
  143. [InlineData(FillMode.Forward, 300.0, 0.3, 1.0, false)]
  144. [InlineData(FillMode.Forward, 300.0, 0.3, 1.0, true )]
  145. [InlineData(FillMode.Backward, 100.0, 0.3, 0.7, false)]
  146. [InlineData(FillMode.Backward, 100.0, 0.3, 0.7, true )]
  147. [InlineData(FillMode.Both, 300.0, 0.3, 0.7, false)]
  148. [InlineData(FillMode.Both, 300.0, 0.3, 0.7, true )]
  149. [InlineData(FillMode.Forward, 300.0, 0.3, 0.7, false)]
  150. [InlineData(FillMode.Forward, 300.0, 0.3, 0.7, true )]
  151. public void Check_FillMode_End_Value(FillMode fillMode, double target, double startCue, double endCue, bool delay)
  152. {
  153. var keyframe1 = new KeyFrame()
  154. {
  155. Setters = { new Setter(Layoutable.WidthProperty, 0d), }, Cue = new Cue(startCue)
  156. };
  157. var keyframe2 = new KeyFrame()
  158. {
  159. Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(endCue)
  160. };
  161. var animation = new Animation()
  162. {
  163. Duration = TimeSpan.FromSeconds(10d),
  164. Delay = delay ? TimeSpan.FromSeconds(5d) : TimeSpan.Zero,
  165. FillMode = fillMode,
  166. Children = { keyframe1, keyframe2 }
  167. };
  168. var border = new Border() { Height = 100d, Width = 100d, };
  169. var clock = new TestClock();
  170. animation.RunAsync(border, clock);
  171. clock.Step(TimeSpan.FromSeconds(0));
  172. clock.Step(TimeSpan.FromSeconds(20));
  173. Assert.Equal(target, border.Width);
  174. }
  175. [Fact]
  176. public void Dispose_Subscription_Should_Stop_Animation()
  177. {
  178. var keyframe1 = new KeyFrame()
  179. {
  180. Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
  181. };
  182. var keyframe2 = new KeyFrame()
  183. {
  184. Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
  185. };
  186. var animation = new Animation()
  187. {
  188. Duration = TimeSpan.FromSeconds(10),
  189. Delay = TimeSpan.FromSeconds(0),
  190. DelayBetweenIterations = TimeSpan.FromSeconds(0),
  191. IterationCount = new IterationCount(1),
  192. Children = { keyframe2, keyframe1 }
  193. };
  194. var border = new Border() { Height = 100d, Width = 50d };
  195. var propertyChangedCount = 0;
  196. var animationCompletedCount = 0;
  197. border.PropertyChanged += (_, e) =>
  198. {
  199. if (e.Property == Layoutable.WidthProperty)
  200. {
  201. propertyChangedCount++;
  202. }
  203. };
  204. var clock = new TestClock();
  205. var disposable = animation.Apply(border, clock, Observable.Return(true), () => animationCompletedCount++);
  206. Assert.Equal(0, propertyChangedCount);
  207. clock.Step(TimeSpan.FromSeconds(0));
  208. Assert.Equal(0, animationCompletedCount);
  209. Assert.Equal(1, propertyChangedCount);
  210. disposable.Dispose();
  211. // Clock ticks should be ignored after Dispose
  212. clock.Step(TimeSpan.FromSeconds(5));
  213. clock.Step(TimeSpan.FromSeconds(6));
  214. clock.Step(TimeSpan.FromSeconds(7));
  215. // On animation disposing (cancellation) on completed is not invoked (is it expected?)
  216. Assert.Equal(0, animationCompletedCount);
  217. // Initial property changed before cancellation + animation value removal.
  218. Assert.Equal(2, propertyChangedCount);
  219. }
  220. [Fact]
  221. public void Do_Not_Run_Cancelled_Animation()
  222. {
  223. var keyframe1 = new KeyFrame()
  224. {
  225. Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
  226. };
  227. var keyframe2 = new KeyFrame()
  228. {
  229. Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
  230. };
  231. var animation = new Animation()
  232. {
  233. Duration = TimeSpan.FromSeconds(10),
  234. Delay = TimeSpan.FromSeconds(0),
  235. DelayBetweenIterations = TimeSpan.FromSeconds(0),
  236. IterationCount = new IterationCount(1),
  237. Children = { keyframe2, keyframe1 }
  238. };
  239. var border = new Border() { Height = 100d, Width = 100d };
  240. var propertyChangedCount = 0;
  241. border.PropertyChanged += (_, e) =>
  242. {
  243. if (e.Property == Layoutable.WidthProperty)
  244. {
  245. propertyChangedCount++;
  246. }
  247. };
  248. var clock = new TestClock();
  249. var cancellationTokenSource = new CancellationTokenSource();
  250. cancellationTokenSource.Cancel();
  251. var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
  252. clock.Step(TimeSpan.FromSeconds(10));
  253. Assert.Equal(0, propertyChangedCount);
  254. Assert.True(animationRun.IsCompleted);
  255. }
  256. [Fact]
  257. public async Task Cancellation_Should_Stop_Animation()
  258. {
  259. var keyframe1 = new KeyFrame()
  260. {
  261. Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
  262. };
  263. var keyframe2 = new KeyFrame()
  264. {
  265. Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
  266. };
  267. var animation = new Animation()
  268. {
  269. Duration = TimeSpan.FromSeconds(10),
  270. Delay = TimeSpan.FromSeconds(0),
  271. DelayBetweenIterations = TimeSpan.FromSeconds(0),
  272. IterationCount = new IterationCount(1),
  273. Children = { keyframe2, keyframe1 }
  274. };
  275. var border = new Border() { Height = 100d, Width = 50d };
  276. var propertyChangedCount = 0;
  277. border.PropertyChanged += (_, e) =>
  278. {
  279. if (e.Property == Layoutable.WidthProperty)
  280. {
  281. propertyChangedCount++;
  282. }
  283. };
  284. var clock = new TestClock();
  285. var cancellationTokenSource = new CancellationTokenSource();
  286. var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
  287. Assert.False(animationRun.IsCompleted);
  288. Assert.Equal(0, propertyChangedCount);
  289. clock.Step(TimeSpan.FromSeconds(0));
  290. Assert.False(animationRun.IsCompleted);
  291. Assert.Equal(1, propertyChangedCount);
  292. cancellationTokenSource.Cancel();
  293. clock.Step(TimeSpan.FromSeconds(1));
  294. clock.Step(TimeSpan.FromSeconds(2));
  295. clock.Step(TimeSpan.FromSeconds(3));
  296. await animationRun;
  297. clock.Step(TimeSpan.FromSeconds(6));
  298. Assert.True(animationRun.IsCompleted);
  299. Assert.Equal(2, propertyChangedCount);
  300. }
  301. [Fact]
  302. public void Dont_Run_Infinite_Iteration_Animation_On_RunAsync_Method()
  303. {
  304. var keyframe1 = new KeyFrame()
  305. {
  306. Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
  307. };
  308. var keyframe2 = new KeyFrame()
  309. {
  310. Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
  311. };
  312. var animation = new Animation()
  313. {
  314. Duration = TimeSpan.FromSeconds(10),
  315. Delay = TimeSpan.FromSeconds(0),
  316. DelayBetweenIterations = TimeSpan.FromSeconds(0),
  317. IterationCount = IterationCount.Infinite,
  318. Children = { keyframe2, keyframe1 }
  319. };
  320. var border = new Border() { Height = 100d, Width = 50d };
  321. var clock = new TestClock();
  322. var cancellationTokenSource = new CancellationTokenSource();
  323. var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
  324. Assert.True(animationRun.IsCompleted);
  325. Assert.NotNull(animationRun.Exception);
  326. }
  327. [Fact]
  328. public async Task Cancellation_Of_Completed_Animation_Does_Not_Fail()
  329. {
  330. var keyframe1 = new KeyFrame()
  331. {
  332. Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
  333. };
  334. var keyframe2 = new KeyFrame()
  335. {
  336. Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
  337. };
  338. var animation = new Animation()
  339. {
  340. Duration = TimeSpan.FromSeconds(10),
  341. Delay = TimeSpan.FromSeconds(0),
  342. DelayBetweenIterations = TimeSpan.FromSeconds(0),
  343. IterationCount = new IterationCount(1),
  344. Children = { keyframe2, keyframe1 }
  345. };
  346. var border = new Border() { Height = 100d, Width = 50d };
  347. var propertyChangedCount = 0;
  348. border.PropertyChanged += (_, e) =>
  349. {
  350. if (e.Property == Layoutable.WidthProperty)
  351. {
  352. propertyChangedCount++;
  353. }
  354. };
  355. var clock = new TestClock();
  356. var cancellationTokenSource = new CancellationTokenSource();
  357. var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
  358. Assert.Equal(0, propertyChangedCount);
  359. clock.Step(TimeSpan.FromSeconds(0));
  360. Assert.False(animationRun.IsCompleted);
  361. Assert.Equal(1, propertyChangedCount);
  362. clock.Step(TimeSpan.FromSeconds(10));
  363. Assert.True(animationRun.IsCompleted);
  364. Assert.Equal(2, propertyChangedCount);
  365. cancellationTokenSource.Cancel();
  366. await animationRun;
  367. }
  368. // https://github.com/AvaloniaUI/Avalonia/issues/12582
  369. [Fact]
  370. public async Task Interpolator_Is_Not_Called_After_Last_Iteration()
  371. {
  372. var animator = new FakeAnimator();
  373. Setter CreateWidthSetter(double value)
  374. {
  375. var setter = new Setter(Layoutable.WidthProperty, value);
  376. Animation.SetAnimator(setter, animator);
  377. return setter;
  378. }
  379. var animation = new Animation
  380. {
  381. Duration = TimeSpan.FromSeconds(1),
  382. Delay = TimeSpan.FromSeconds(0),
  383. DelayBetweenIterations = TimeSpan.FromSeconds(0),
  384. IterationCount = new IterationCount(1),
  385. Easing = new LinearEasing(),
  386. Children =
  387. {
  388. new KeyFrame
  389. {
  390. Setters = { CreateWidthSetter(100d) },
  391. Cue = new Cue(0d)
  392. },
  393. new KeyFrame
  394. {
  395. Setters = { CreateWidthSetter(200d) },
  396. Cue = new Cue(1d)
  397. }
  398. }
  399. };
  400. var border = new Border
  401. {
  402. Height = 100d,
  403. Width = 50d
  404. };
  405. var clock = new TestClock();
  406. var animationRun = animation.RunAsync(border, clock);
  407. clock.Step(TimeSpan.Zero);
  408. Assert.Equal(1, animator.CallCount);
  409. Assert.Equal(0.0d, animator.LastProgress);
  410. animator.LastProgress = double.NaN;
  411. clock.Step(TimeSpan.FromSeconds(0.5d));
  412. Assert.Equal(2, animator.CallCount);
  413. Assert.Equal(0.5d, animator.LastProgress);
  414. animator.LastProgress = double.NaN;
  415. clock.Step(TimeSpan.FromSeconds(1.5d));
  416. Assert.Equal(3, animator.CallCount);
  417. Assert.Equal(1.0d, animator.LastProgress);
  418. await animationRun;
  419. }
  420. [Theory]
  421. [InlineData(0, 1, 2)]
  422. [InlineData(0, 2, 1)]
  423. [InlineData(1, 0, 2)]
  424. [InlineData(1, 2, 0)]
  425. [InlineData(2, 0, 1)]
  426. [InlineData(2, 1, 0)]
  427. public void KeyFrames_Order_Does_Not_Matter(int index0, int index1, int index2)
  428. {
  429. static KeyFrame CreateKeyFrame(double width, double cue)
  430. => new()
  431. {
  432. Setters = { new Setter(Layoutable.WidthProperty, width) },
  433. Cue = new Cue(cue)
  434. };
  435. var keyFrames = new[]
  436. {
  437. CreateKeyFrame(100.0, 0.0),
  438. CreateKeyFrame(200.0, 0.5),
  439. CreateKeyFrame(300.0, 1.0)
  440. };
  441. var animation = new Animation
  442. {
  443. Duration = TimeSpan.FromSeconds(1.0),
  444. IterationCount = new IterationCount(1),
  445. Easing = new LinearEasing(),
  446. FillMode = FillMode.Forward
  447. };
  448. animation.Children.Add(keyFrames[index0]);
  449. animation.Children.Add(keyFrames[index1]);
  450. animation.Children.Add(keyFrames[index2]);
  451. var border = new Border
  452. {
  453. Height = 100.0,
  454. Width = 50.0
  455. };
  456. var clock = new TestClock();
  457. animation.RunAsync(border, clock);
  458. clock.Step(TimeSpan.Zero);
  459. Assert.Equal(100.0, border.Width);
  460. clock.Step(TimeSpan.FromSeconds(0.5));
  461. Assert.Equal(200.0, border.Width);
  462. clock.Step(TimeSpan.FromSeconds(1.0));
  463. Assert.Equal(300.0, border.Width);
  464. }
  465. [Theory]
  466. [InlineData(0.0)]
  467. [InlineData(0.5)]
  468. [InlineData(1.0)]
  469. public void Single_KeyFrame_Works(double cue)
  470. {
  471. var animation = new Animation
  472. {
  473. Duration = TimeSpan.FromSeconds(1.0),
  474. IterationCount = new IterationCount(1),
  475. Easing = new LinearEasing(),
  476. FillMode = FillMode.Forward,
  477. Children =
  478. {
  479. new KeyFrame
  480. {
  481. Setters = { new Setter(Layoutable.WidthProperty, 100.0) },
  482. Cue = new Cue(cue)
  483. }
  484. }
  485. };
  486. var border = new Border
  487. {
  488. Height = 100.0,
  489. Width = 50.0
  490. };
  491. var clock = new TestClock();
  492. animation.RunAsync(border, clock);
  493. clock.Step(TimeSpan.Zero);
  494. clock.Step(TimeSpan.FromSeconds(cue));
  495. Assert.Equal(100.0, border.Width);
  496. }
  497. private sealed class FakeAnimator : InterpolatingAnimator<double>
  498. {
  499. public double LastProgress { get; set; } = double.NaN;
  500. public int CallCount { get; set; }
  501. public override double Interpolate(double progress, double oldValue, double newValue)
  502. {
  503. ++CallCount;
  504. LastProgress = progress;
  505. return newValue;
  506. }
  507. }
  508. }
  509. }