123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- using System;
- using System.Threading.Tasks;
- using Avalonia.Animation;
- using Avalonia.Controls;
- using Avalonia.Styling;
- using Xunit;
- using Avalonia.Animation.Easings;
- using System.Threading;
- using System.Reactive.Linq;
- using Avalonia.Layout;
- namespace Avalonia.Base.UnitTests.Animation
- {
- using Animation = Avalonia.Animation.Animation;
- public class AnimationIterationTests
- {
- [Fact]
- public void Check_KeyTime_Correctly_Converted_To_Cue()
- {
- var keyframe1 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 100d), }, KeyTime = TimeSpan.FromSeconds(0.5)
- };
- var keyframe2 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 0d), }, KeyTime = TimeSpan.FromSeconds(0)
- };
- var animation = new Animation() { Duration = TimeSpan.FromSeconds(1), Children = { keyframe2, keyframe1 } };
- var border = new Border() { Height = 100d, Width = 100d };
- var clock = new TestClock();
- animation.RunAsync(border, clock);
- clock.Step(TimeSpan.Zero);
- Assert.Equal(border.Width, 0d);
- clock.Step(TimeSpan.FromSeconds(1));
- Assert.Equal(border.Width, 100d);
- }
- [Fact]
- public void Check_Initial_Inter_and_Trailing_Delay_Values()
- {
- var keyframe1 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
- };
- var keyframe2 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
- };
- var animation = new Animation()
- {
- Duration = TimeSpan.FromSeconds(3),
- Delay = TimeSpan.FromSeconds(3),
- DelayBetweenIterations = TimeSpan.FromSeconds(3),
- IterationCount = new IterationCount(2),
- Children = { keyframe2, keyframe1 }
- };
- var border = new Border() { Height = 100d, Width = 100d };
- var clock = new TestClock();
- var animationRun = animation.RunAsync(border, clock);
- border.Measure(Size.Infinity);
- border.Arrange(new Rect(border.DesiredSize));
-
- clock.Step(TimeSpan.Zero);
- // Initial Delay.
- clock.Step(TimeSpan.FromSeconds(0));
- Assert.Equal(100d, border.Width);
- clock.Step(TimeSpan.FromSeconds(6));
- // First Inter-Iteration delay.
- clock.Step(TimeSpan.FromSeconds(8));
- Assert.Equal(border.Width, 200d);
- // Trailing Delay should be non-existent.
- clock.Step(TimeSpan.FromSeconds(14));
- Assert.True(animationRun.Status == TaskStatus.RanToCompletion);
- Assert.Equal(border.Width, 100d);
- }
- [Fact]
- public void Check_FillModes_Start_and_End_Values_if_Retained()
- {
- var keyframe1 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 0d), }, Cue = new Cue(0.0d)
- };
- var keyframe2 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(1.0d)
- };
- var animation = new Animation()
- {
- Duration = TimeSpan.FromSeconds(0.05d),
- Delay = TimeSpan.FromSeconds(0.05d),
- Easing = new SineEaseInOut(),
- FillMode = FillMode.Both,
- Children = { keyframe1, keyframe2 }
- };
- var border = new Border() { Height = 100d, Width = 100d, };
- var clock = new TestClock();
- animation.RunAsync(border, clock);
- clock.Step(TimeSpan.FromSeconds(0d));
- Assert.Equal(border.Width, 0d);
- clock.Step(TimeSpan.FromSeconds(0.050d));
- Assert.Equal(border.Width, 0d);
- clock.Step(TimeSpan.FromSeconds(0.100d));
- Assert.Equal(border.Width, 300d);
- }
-
- [Theory]
- [InlineData(FillMode.Backward, 50.0, 0.0, 0.7, false)]
- [InlineData(FillMode.Backward, 50.0, 0.0, 0.7, true )]
- [InlineData(FillMode.Both, 50.0, 0.0, 0.7, false)]
- [InlineData(FillMode.Both, 50.0, 0.0, 0.7, true )]
- [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
- [InlineData(FillMode.Forward, 100.0, 0.0, 0.7, true )]
- [InlineData(FillMode.Backward, 50.0, 0.3, 0.7, false)]
- [InlineData(FillMode.Backward, 50.0, 0.3, 0.7, true )]
- [InlineData(FillMode.Both, 50.0, 0.3, 0.7, false)]
- [InlineData(FillMode.Both, 50.0, 0.3, 0.7, true )]
- [InlineData(FillMode.Forward, 100.0, 0.3, 0.7, false)]
- [InlineData(FillMode.Forward, 100.0, 0.3, 0.7, true )]
- public void Check_FillMode_Start_Value(FillMode fillMode, double target, double startCue, double endCue, bool delay)
- {
- var keyframe1 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 50d), }, Cue = new Cue(startCue)
- };
- var keyframe2 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(endCue)
- };
- var animation = new Animation()
- {
- Duration = TimeSpan.FromSeconds(10d),
- Delay = delay ? TimeSpan.FromSeconds(5d) : TimeSpan.Zero,
- FillMode = fillMode,
- Children = { keyframe1, keyframe2 }
- };
- var border = new Border() { Height = 100d, Width = 100d, };
-
- var clock = new TestClock();
-
- animation.RunAsync(border, clock);
-
- clock.Step(TimeSpan.Zero);
-
- Assert.Equal(target, border.Width);
- }
-
- [Theory]
- [InlineData(FillMode.Backward, 100.0, 0.3, 1.0, false)]
- [InlineData(FillMode.Backward, 100.0, 0.3, 1.0, true )]
- [InlineData(FillMode.Both, 300.0, 0.3, 1.0, false)]
- [InlineData(FillMode.Both, 300.0, 0.3, 1.0, true )]
- [InlineData(FillMode.Forward, 300.0, 0.3, 1.0, false)]
- [InlineData(FillMode.Forward, 300.0, 0.3, 1.0, true )]
- [InlineData(FillMode.Backward, 100.0, 0.3, 0.7, false)]
- [InlineData(FillMode.Backward, 100.0, 0.3, 0.7, true )]
- [InlineData(FillMode.Both, 300.0, 0.3, 0.7, false)]
- [InlineData(FillMode.Both, 300.0, 0.3, 0.7, true )]
- [InlineData(FillMode.Forward, 300.0, 0.3, 0.7, false)]
- [InlineData(FillMode.Forward, 300.0, 0.3, 0.7, true )]
- public void Check_FillMode_End_Value(FillMode fillMode, double target, double startCue, double endCue, bool delay)
- {
- var keyframe1 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 0d), }, Cue = new Cue(startCue)
- };
- var keyframe2 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(endCue)
- };
- var animation = new Animation()
- {
- Duration = TimeSpan.FromSeconds(10d),
- Delay = delay ? TimeSpan.FromSeconds(5d) : TimeSpan.Zero,
- FillMode = fillMode,
- Children = { keyframe1, keyframe2 }
- };
- var border = new Border() { Height = 100d, Width = 100d, };
-
- var clock = new TestClock();
-
- animation.RunAsync(border, clock);
-
- clock.Step(TimeSpan.FromSeconds(0));
- clock.Step(TimeSpan.FromSeconds(20));
-
- Assert.Equal(target, border.Width);
- }
-
- [Fact]
- public void Dispose_Subscription_Should_Stop_Animation()
- {
- var keyframe1 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
- };
- var keyframe2 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
- };
- var animation = new Animation()
- {
- Duration = TimeSpan.FromSeconds(10),
- Delay = TimeSpan.FromSeconds(0),
- DelayBetweenIterations = TimeSpan.FromSeconds(0),
- IterationCount = new IterationCount(1),
- Children = { keyframe2, keyframe1 }
- };
- var border = new Border() { Height = 100d, Width = 50d };
- var propertyChangedCount = 0;
- var animationCompletedCount = 0;
- border.PropertyChanged += (_, e) =>
- {
- if (e.Property == Layoutable.WidthProperty)
- {
- propertyChangedCount++;
- }
- };
- var clock = new TestClock();
- var disposable = animation.Apply(border, clock, Observable.Return(true), () => animationCompletedCount++);
- Assert.Equal(0, propertyChangedCount);
- clock.Step(TimeSpan.FromSeconds(0));
- Assert.Equal(0, animationCompletedCount);
- Assert.Equal(1, propertyChangedCount);
- disposable.Dispose();
- // Clock ticks should be ignored after Dispose
- clock.Step(TimeSpan.FromSeconds(5));
- clock.Step(TimeSpan.FromSeconds(6));
- clock.Step(TimeSpan.FromSeconds(7));
- // On animation disposing (cancellation) on completed is not invoked (is it expected?)
- Assert.Equal(0, animationCompletedCount);
- // Initial property changed before cancellation + animation value removal.
- Assert.Equal(2, propertyChangedCount);
- }
- [Fact]
- public void Do_Not_Run_Cancelled_Animation()
- {
- var keyframe1 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
- };
- var keyframe2 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
- };
- var animation = new Animation()
- {
- Duration = TimeSpan.FromSeconds(10),
- Delay = TimeSpan.FromSeconds(0),
- DelayBetweenIterations = TimeSpan.FromSeconds(0),
- IterationCount = new IterationCount(1),
- Children = { keyframe2, keyframe1 }
- };
- var border = new Border() { Height = 100d, Width = 100d };
- var propertyChangedCount = 0;
- border.PropertyChanged += (_, e) =>
- {
- if (e.Property == Layoutable.WidthProperty)
- {
- propertyChangedCount++;
- }
- };
- var clock = new TestClock();
- var cancellationTokenSource = new CancellationTokenSource();
- cancellationTokenSource.Cancel();
- var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
- clock.Step(TimeSpan.FromSeconds(10));
- Assert.Equal(0, propertyChangedCount);
- Assert.True(animationRun.IsCompleted);
- }
- [Fact]
- public async Task Cancellation_Should_Stop_Animation()
- {
- var keyframe1 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
- };
- var keyframe2 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
- };
- var animation = new Animation()
- {
- Duration = TimeSpan.FromSeconds(10),
- Delay = TimeSpan.FromSeconds(0),
- DelayBetweenIterations = TimeSpan.FromSeconds(0),
- IterationCount = new IterationCount(1),
- Children = { keyframe2, keyframe1 }
- };
- var border = new Border() { Height = 100d, Width = 50d };
- var propertyChangedCount = 0;
- border.PropertyChanged += (_, e) =>
- {
- if (e.Property == Layoutable.WidthProperty)
- {
- propertyChangedCount++;
- }
- };
- var clock = new TestClock();
- var cancellationTokenSource = new CancellationTokenSource();
- var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
- Assert.False(animationRun.IsCompleted);
- Assert.Equal(0, propertyChangedCount);
- clock.Step(TimeSpan.FromSeconds(0));
- Assert.False(animationRun.IsCompleted);
- Assert.Equal(1, propertyChangedCount);
- cancellationTokenSource.Cancel();
- clock.Step(TimeSpan.FromSeconds(1));
- clock.Step(TimeSpan.FromSeconds(2));
- clock.Step(TimeSpan.FromSeconds(3));
- await animationRun;
- clock.Step(TimeSpan.FromSeconds(6));
- Assert.True(animationRun.IsCompleted);
- Assert.Equal(2, propertyChangedCount);
- }
- [Fact]
- public void Dont_Run_Infinite_Iteration_Animation_On_RunAsync_Method()
- {
- var keyframe1 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
- };
- var keyframe2 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
- };
- var animation = new Animation()
- {
- Duration = TimeSpan.FromSeconds(10),
- Delay = TimeSpan.FromSeconds(0),
- DelayBetweenIterations = TimeSpan.FromSeconds(0),
- IterationCount = IterationCount.Infinite,
- Children = { keyframe2, keyframe1 }
- };
- var border = new Border() { Height = 100d, Width = 50d };
- var clock = new TestClock();
- var cancellationTokenSource = new CancellationTokenSource();
- var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
- Assert.True(animationRun.IsCompleted);
- Assert.NotNull(animationRun.Exception);
- }
- [Fact]
- public async Task Cancellation_Of_Completed_Animation_Does_Not_Fail()
- {
- var keyframe1 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
- };
- var keyframe2 = new KeyFrame()
- {
- Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
- };
- var animation = new Animation()
- {
- Duration = TimeSpan.FromSeconds(10),
- Delay = TimeSpan.FromSeconds(0),
- DelayBetweenIterations = TimeSpan.FromSeconds(0),
- IterationCount = new IterationCount(1),
- Children = { keyframe2, keyframe1 }
- };
- var border = new Border() { Height = 100d, Width = 50d };
- var propertyChangedCount = 0;
- border.PropertyChanged += (_, e) =>
- {
- if (e.Property == Layoutable.WidthProperty)
- {
- propertyChangedCount++;
- }
- };
- var clock = new TestClock();
- var cancellationTokenSource = new CancellationTokenSource();
- var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
- Assert.Equal(0, propertyChangedCount);
- clock.Step(TimeSpan.FromSeconds(0));
- Assert.False(animationRun.IsCompleted);
- Assert.Equal(1, propertyChangedCount);
- clock.Step(TimeSpan.FromSeconds(10));
- Assert.True(animationRun.IsCompleted);
- Assert.Equal(2, propertyChangedCount);
- cancellationTokenSource.Cancel();
- await animationRun;
- }
- // https://github.com/AvaloniaUI/Avalonia/issues/12582
- [Fact]
- public async Task Interpolator_Is_Not_Called_After_Last_Iteration()
- {
- var animator = new FakeAnimator();
- Setter CreateWidthSetter(double value)
- {
- var setter = new Setter(Layoutable.WidthProperty, value);
- Animation.SetAnimator(setter, animator);
- return setter;
- }
- var animation = new Animation
- {
- Duration = TimeSpan.FromSeconds(1),
- Delay = TimeSpan.FromSeconds(0),
- DelayBetweenIterations = TimeSpan.FromSeconds(0),
- IterationCount = new IterationCount(1),
- Easing = new LinearEasing(),
- Children =
- {
- new KeyFrame
- {
- Setters = { CreateWidthSetter(100d) },
- Cue = new Cue(0d)
- },
- new KeyFrame
- {
- Setters = { CreateWidthSetter(200d) },
- Cue = new Cue(1d)
- }
- }
- };
- var border = new Border
- {
- Height = 100d,
- Width = 50d
- };
- var clock = new TestClock();
- var animationRun = animation.RunAsync(border, clock);
- clock.Step(TimeSpan.Zero);
- Assert.Equal(1, animator.CallCount);
- Assert.Equal(0.0d, animator.LastProgress);
- animator.LastProgress = double.NaN;
- clock.Step(TimeSpan.FromSeconds(0.5d));
- Assert.Equal(2, animator.CallCount);
- Assert.Equal(0.5d, animator.LastProgress);
- animator.LastProgress = double.NaN;
- clock.Step(TimeSpan.FromSeconds(1.5d));
- Assert.Equal(3, animator.CallCount);
- Assert.Equal(1.0d, animator.LastProgress);
- await animationRun;
- }
- [Theory]
- [InlineData(0, 1, 2)]
- [InlineData(0, 2, 1)]
- [InlineData(1, 0, 2)]
- [InlineData(1, 2, 0)]
- [InlineData(2, 0, 1)]
- [InlineData(2, 1, 0)]
- public void KeyFrames_Order_Does_Not_Matter(int index0, int index1, int index2)
- {
- static KeyFrame CreateKeyFrame(double width, double cue)
- => new()
- {
- Setters = { new Setter(Layoutable.WidthProperty, width) },
- Cue = new Cue(cue)
- };
- var keyFrames = new[]
- {
- CreateKeyFrame(100.0, 0.0),
- CreateKeyFrame(200.0, 0.5),
- CreateKeyFrame(300.0, 1.0)
- };
- var animation = new Animation
- {
- Duration = TimeSpan.FromSeconds(1.0),
- IterationCount = new IterationCount(1),
- Easing = new LinearEasing(),
- FillMode = FillMode.Forward
- };
- animation.Children.Add(keyFrames[index0]);
- animation.Children.Add(keyFrames[index1]);
- animation.Children.Add(keyFrames[index2]);
- var border = new Border
- {
- Height = 100.0,
- Width = 50.0
- };
- var clock = new TestClock();
- animation.RunAsync(border, clock);
- clock.Step(TimeSpan.Zero);
- Assert.Equal(100.0, border.Width);
- clock.Step(TimeSpan.FromSeconds(0.5));
- Assert.Equal(200.0, border.Width);
- clock.Step(TimeSpan.FromSeconds(1.0));
- Assert.Equal(300.0, border.Width);
- }
- [Theory]
- [InlineData(0.0)]
- [InlineData(0.5)]
- [InlineData(1.0)]
- public void Single_KeyFrame_Works(double cue)
- {
- var animation = new Animation
- {
- Duration = TimeSpan.FromSeconds(1.0),
- IterationCount = new IterationCount(1),
- Easing = new LinearEasing(),
- FillMode = FillMode.Forward,
- Children =
- {
- new KeyFrame
- {
- Setters = { new Setter(Layoutable.WidthProperty, 100.0) },
- Cue = new Cue(cue)
- }
- }
- };
- var border = new Border
- {
- Height = 100.0,
- Width = 50.0
- };
- var clock = new TestClock();
- animation.RunAsync(border, clock);
- clock.Step(TimeSpan.Zero);
- clock.Step(TimeSpan.FromSeconds(cue));
- Assert.Equal(100.0, border.Width);
- }
- private sealed class FakeAnimator : InterpolatingAnimator<double>
- {
- public double LastProgress { get; set; } = double.NaN;
- public int CallCount { get; set; }
- public override double Interpolate(double progress, double oldValue, double newValue)
- {
- ++CallCount;
- LastProgress = progress;
- return newValue;
- }
- }
- }
- }
|