From a2a135206524e0c53def447ac426c5f9328e6e96 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sun, 31 May 2020 19:36:02 +0800 Subject: [PATCH 1/3] Implement transition delay --- src/Avalonia.Animation/TransitionInstance.cs | 36 +++++++++++++++++--- src/Avalonia.Animation/Transition`1.cs | 9 +++-- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index ad2001d621..5184341324 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -4,6 +4,7 @@ using System.Reactive.Linq; using Avalonia.Animation.Easings; using Avalonia.Animation.Utils; using Avalonia.Reactive; +using Avalonia.Utilities; namespace Avalonia.Animation { @@ -13,31 +14,56 @@ namespace Avalonia.Animation internal class TransitionInstance : SingleSubscriberObservableBase { private IDisposable _timerSubscription; + private TimeSpan _delay; private TimeSpan _duration; private readonly IClock _baseClock; private IClock _clock; - public TransitionInstance(IClock clock, TimeSpan Duration) + public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration) { clock = clock ?? throw new ArgumentNullException(nameof(clock)); - _duration = Duration; + _delay = delay; + _duration = duration; _baseClock = clock; } private void TimerTick(TimeSpan t) { - var interpVal = _duration.Ticks == 0 ? 1d : (double)t.Ticks / _duration.Ticks; + + // [<------------- normalizedTotalDur ------------------>] + // [<---- Delay ---->][<---------- Duration ------------>] + // ^- normalizedDelayEnd + // [<---- normalizedInterpVal --->] + + var normalizedInterpVal = 1d; + + if (!MathUtilities.AreClose(_duration.TotalSeconds, 0d)) + { + var normalizedTotalDur = _delay + _duration; + var normalizedDelayEnd = _delay.TotalSeconds / normalizedTotalDur.TotalSeconds; + var normalizedPresentationTime = t.TotalSeconds / normalizedTotalDur.TotalSeconds; + + if (normalizedPresentationTime < normalizedDelayEnd + || MathUtilities.AreClose(normalizedPresentationTime, normalizedDelayEnd)) + { + normalizedInterpVal = 0d; + } + else + { + normalizedInterpVal = (t.TotalSeconds - _delay.TotalSeconds) / _duration.TotalSeconds; + } + } // Clamp interpolation value. - if (interpVal >= 1d | interpVal < 0d) + if (normalizedInterpVal >= 1d || normalizedInterpVal < 0d) { PublishNext(1d); PublishCompleted(); } else { - PublishNext(interpVal); + PublishNext(normalizedInterpVal); } } diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition`1.cs index 138131acb9..4542a137e5 100644 --- a/src/Avalonia.Animation/Transition`1.cs +++ b/src/Avalonia.Animation/Transition`1.cs @@ -13,10 +13,15 @@ namespace Avalonia.Animation private AvaloniaProperty _prop; /// - /// Gets the duration of the animation. + /// Gets or sets the duration of the transition. /// public TimeSpan Duration { get; set; } + /// + /// Gets or sets delay before starting the transition. + /// + public TimeSpan Delay { get; set; } = TimeSpan.Zero; + /// /// Gets the easing class to be used. /// @@ -47,7 +52,7 @@ namespace Avalonia.Animation /// public virtual IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue) { - var transition = DoTransition(new TransitionInstance(clock, Duration), (T)oldValue, (T)newValue); + var transition = DoTransition(new TransitionInstance(clock, Delay, Duration), (T)oldValue, (T)newValue); return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } } From a7536aee6789bd97eb68fc61f392a53b71420b91 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sun, 31 May 2020 20:34:27 +0800 Subject: [PATCH 2/3] add unit test --- .../Avalonia.Animation.UnitTests/TestClock.cs | 10 ++- .../TransitionsTests.cs | 88 ++++++++++++------- 2 files changed, 65 insertions(+), 33 deletions(-) diff --git a/tests/Avalonia.Animation.UnitTests/TestClock.cs b/tests/Avalonia.Animation.UnitTests/TestClock.cs index a1c4ff9277..4812880c03 100644 --- a/tests/Avalonia.Animation.UnitTests/TestClock.cs +++ b/tests/Avalonia.Animation.UnitTests/TestClock.cs @@ -5,10 +5,12 @@ namespace Avalonia.Animation.UnitTests { internal class TestClock : IClock, IDisposable { + private TimeSpan _curTime; + private IObserver _observer; public PlayState PlayState { get; set; } = PlayState.Run; - + public void Dispose() { _observer?.OnCompleted(); @@ -19,6 +21,12 @@ namespace Avalonia.Animation.UnitTests _observer?.OnNext(time); } + public void Pulse(TimeSpan time) + { + _curTime += time; + _observer?.OnNext(_curTime); + } + public IDisposable Subscribe(IObserver observer) { _observer = observer; diff --git a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs index 70ffd781a1..640013dedd 100644 --- a/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs +++ b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs @@ -10,13 +10,11 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Check_Transitions_Interpolation_Negative_Bounds_Clamp() { - var clock = new MockGlobalClock(); + var clock = new TestClock(); - using (UnitTestApplication.Start(new TestServices(globalClock: clock))) + var border = new Border { - var border = new Border - { - Transitions = new Transitions + Transitions = new Transitions { new DoubleTransition { @@ -24,27 +22,25 @@ namespace Avalonia.Animation.UnitTests Property = Border.OpacityProperty, } } - }; + }; - border.Opacity = 0; + border.Opacity = 0; - clock.Pulse(TimeSpan.FromSeconds(0)); - clock.Pulse(TimeSpan.FromSeconds(-0.5)); + clock.Pulse(TimeSpan.FromSeconds(0)); + clock.Pulse(TimeSpan.FromSeconds(-0.5)); + + Assert.Equal(0, border.Opacity); - Assert.Equal(0, border.Opacity); - } } [Fact] public void Check_Transitions_Interpolation_Positive_Bounds_Clamp() { - var clock = new MockGlobalClock(); + var clock = new TestClock(); - using (UnitTestApplication.Start(new TestServices(globalClock: clock))) + var border = new Border { - var border = new Border - { - Transitions = new Transitions + Transitions = new Transitions { new DoubleTransition { @@ -52,34 +48,62 @@ namespace Avalonia.Animation.UnitTests Property = Border.OpacityProperty, } } - }; + }; - border.Opacity = 0; + border.Opacity = 0; - clock.Pulse(TimeSpan.FromSeconds(0)); - clock.Pulse(TimeSpan.FromMilliseconds(1001)); + clock.Pulse(TimeSpan.FromSeconds(0)); + clock.Pulse(TimeSpan.FromMilliseconds(1001)); + + Assert.Equal(0, border.Opacity); - Assert.Equal(0, border.Opacity); - } } [Fact] public void TransitionInstance_With_Zero_Duration_Is_Completed_On_First_Tick() { - var clock = new MockGlobalClock(); + var clock = new TestClock(); - using (UnitTestApplication.Start(new TestServices(globalClock: clock))) + int i = 0; + var inst = new TransitionInstance(clock, TimeSpan.Zero, TimeSpan.Zero).Subscribe(nextValue => { - int i = 0; - var inst = new TransitionInstance(clock, TimeSpan.Zero).Subscribe(nextValue => + switch (i++) { - switch (i++) - { - case 0: Assert.Equal(0, nextValue); break; - case 1: Assert.Equal(1d, nextValue); break; - } - }); + case 0: Assert.Equal(0, nextValue); break; + case 1: Assert.Equal(1d, nextValue); break; + } + }); + + clock.Pulse(TimeSpan.FromMilliseconds(10)); + } + + [Fact] + public void TransitionInstance_Properly_Calculates_Delay_And_Duration_Values() + { + var clock = new TestClock(); + + int i = -1; + var inst = new TransitionInstance(clock, TimeSpan.FromMilliseconds(30), TimeSpan.FromMilliseconds(70)).Subscribe(nextValue => + { + switch (i++) + { + case 0: Assert.Equal(0, nextValue); break; + case 1: Assert.Equal(0, nextValue); break; + case 2: Assert.Equal(0, nextValue); break; + case 3: Assert.Equal(0, nextValue); break; + case 4: Assert.Equal(Math.Round(10d / 70d, 4), Math.Round(nextValue, 4)); break; + case 5: Assert.Equal(Math.Round(20d / 70d, 4), Math.Round(nextValue, 4)); break; + case 6: Assert.Equal(Math.Round(30d / 70d, 4), Math.Round(nextValue, 4)); break; + case 7: Assert.Equal(Math.Round(40d / 70d, 4), Math.Round(nextValue, 4)); break; + case 8: Assert.Equal(Math.Round(50d / 70d, 4), Math.Round(nextValue, 4)); break; + case 9: Assert.Equal(Math.Round(60d / 70d, 4), Math.Round(nextValue, 4)); break; + case 10: Assert.Equal(1d, nextValue); break; + } + }); + + for (int z = 0; z <= 10; z++) + { clock.Pulse(TimeSpan.FromMilliseconds(10)); } } From 5027de256cc2cd3e44347ff3ded1cc9d7812e8da Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Wed, 17 Jun 2020 21:33:50 +0200 Subject: [PATCH 3/3] Add an example for transition delay. --- samples/RenderDemo/Pages/TransitionsPage.xaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/samples/RenderDemo/Pages/TransitionsPage.xaml b/samples/RenderDemo/Pages/TransitionsPage.xaml index df7130a925..d6da293ff3 100644 --- a/samples/RenderDemo/Pages/TransitionsPage.xaml +++ b/samples/RenderDemo/Pages/TransitionsPage.xaml @@ -77,6 +77,19 @@ + + + + @@ -95,6 +108,7 @@ +