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 @@
+
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);
}
}
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));
}
}