Browse Source

Merge pull request #11821 from AvaloniaUI/fixes/animation-delay-zero-bug

Fix for animation delay zero bug
pull/11827/head
Jumar Macato 3 years ago
committed by GitHub
parent
commit
4eb75360da
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 119
      src/Avalonia.Base/Animation/AnimationInstance`1.cs
  2. 84
      tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs

119
src/Avalonia.Base/Animation/AnimationInstance`1.cs

@ -58,6 +58,9 @@ namespace Avalonia.Animation
if (_animation.Duration < TimeSpan.Zero)
throw new InvalidOperationException("Duration value cannot be negative.");
if (_animation.Delay < TimeSpan.Zero)
throw new InvalidOperationException("Delay value cannot be negative.");
_easeFunc = _animation.Easing;
_speedRatioConv = 1d / _animation.SpeedRatio;
@ -151,72 +154,74 @@ namespace Avalonia.Animation
var iterDelay = _iterationDelay.Ticks * _speedRatioConv;
var initDelay = _initialDelay.Ticks * _speedRatioConv;
if (indexTime > 0 & indexTime <= initDelay)
// This conditional checks if the time given is the very start/zero
// and when we have an active delay time.
if (initDelay > 0 && indexTime <= initDelay)
{
DoDelay();
return;
}
else
// Calculate timebases.
var iterationTime = iterDuration + iterDelay;
var opsTime = indexTime - initDelay;
var playbackTime = opsTime % iterationTime;
_currentIteration = (ulong)(opsTime / iterationTime);
// Stop animation when the current iteration is beyond the iteration count or
// when the duration is set to zero while animating and snap to the last iterated value.
if (_currentIteration + 1 > _iterationCount || _duration == TimeSpan.Zero)
{
// Calculate timebases.
var iterationTime = iterDuration + iterDelay;
var opsTime = indexTime - initDelay;
var playbackTime = opsTime % iterationTime;
var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
DoComplete();
}
_currentIteration = (ulong)(opsTime / iterationTime);
if (playbackTime <= iterDuration)
{
// Normalize time for interpolation.
var normalizedTime = playbackTime / iterDuration;
// Stop animation when the current iteration is beyond the iteration count or
// when the duration is set to zero while animating and snap to the last iterated value.
if (_currentIteration + 1 > _iterationCount || _duration == TimeSpan.Zero)
{
var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
DoComplete();
}
// Check if normalized time needs to be reversed according to PlaybackDirection
if (playbackTime <= iterDuration)
{
// Normalize time for interpolation.
var normalizedTime = playbackTime / iterDuration;
// Check if normalized time needs to be reversed according to PlaybackDirection
switch (_playbackDirection)
{
case PlaybackDirection.Normal:
_playbackReversed = false;
break;
case PlaybackDirection.Reverse:
_playbackReversed = true;
break;
case PlaybackDirection.Alternate:
_playbackReversed = _currentIteration % 2 != 0;
break;
case PlaybackDirection.AlternateReverse:
_playbackReversed = _currentIteration % 2 == 0;
break;
default:
throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}");
}
if (_playbackReversed)
normalizedTime = 1 - normalizedTime;
// Ease and interpolate
var easedTime = _easeFunc!.Ease(normalizedTime);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
PublishNext(_lastInterpValue);
}
else if (playbackTime > iterDuration &
playbackTime <= iterationTime &
iterDelay > 0)
switch (_playbackDirection)
{
// The last iteration's trailing delay should be skipped.
if (_currentIteration + 1 < _iterationCount)
DoDelay();
else
DoComplete();
case PlaybackDirection.Normal:
_playbackReversed = false;
break;
case PlaybackDirection.Reverse:
_playbackReversed = true;
break;
case PlaybackDirection.Alternate:
_playbackReversed = _currentIteration % 2 != 0;
break;
case PlaybackDirection.AlternateReverse:
_playbackReversed = _currentIteration % 2 == 0;
break;
default:
throw new InvalidOperationException(
$"Animation direction value is unknown: {_playbackDirection}");
}
if (_playbackReversed)
normalizedTime = 1 - normalizedTime;
// Ease and interpolate
var easedTime = _easeFunc!.Ease(normalizedTime);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
PublishNext(_lastInterpValue);
}
else if (playbackTime > iterDuration &&
playbackTime <= iterationTime &&
iterDelay > 0)
{
// The last iteration's trailing delay should be skipped.
if (_currentIteration + 1 < _iterationCount)
DoDelay();
else
DoComplete();
}
}

84
tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs

@ -71,11 +71,14 @@ namespace Avalonia.Base.UnitTests.Animation
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(1));
Assert.Equal(border.Width, 0d);
clock.Step(TimeSpan.FromSeconds(0));
Assert.Equal(100d, border.Width);
clock.Step(TimeSpan.FromSeconds(6));
@ -126,7 +129,84 @@ namespace Avalonia.Base.UnitTests.Animation
clock.Step(TimeSpan.FromSeconds(0.100d));
Assert.Equal(border.Width, 300d);
}
[Theory]
[InlineData(FillMode.Backward, 0, 0d, 0.7d)]
[InlineData(FillMode.Both, 0, 0d, 0.7d)]
[InlineData(FillMode.Forward, 100, 0d, 0.7d)]
[InlineData(FillMode.Backward, 0, 0.3d, 0.7d)]
[InlineData(FillMode.Both, 0, 0.3d, 0.7d)]
[InlineData(FillMode.Forward, 100, 0.3d, 0.7d)]
public void Check_FillMode_Start_Value(FillMode fillMode, double target, double startCue, double endCue)
{
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 = TimeSpan.FromSeconds(5d),
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.3d, 1d)]
[InlineData(FillMode.Both, 300, 0.3d, 1d)]
[InlineData(FillMode.Forward, 300, 0.3d, 1d)]
[InlineData(FillMode.Backward, 100, 0.3d, 0.7d)]
[InlineData(FillMode.Both, 300, 0.3d, 0.7d)]
[InlineData(FillMode.Forward, 300, 0.3d, 0.7d)]
public void Check_FillMode_End_Value(FillMode fillMode, double target, double startCue, double endCue)
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Layoutable.WidthProperty, 0d), }, Cue = new Cue(0.7d)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(1d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10d),
Delay = TimeSpan.FromSeconds(5d),
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()
{

Loading…
Cancel
Save