Browse Source

Merge pull request #11800 from AvaloniaUI/fixes/zero-dur-and-unittestfixes

Implement stopping animations with zero duration + UI Animations maintenance sweep
pull/11811/head
Jumar Macato 3 years ago
committed by GitHub
parent
commit
58ed081812
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      src/Avalonia.Base/Animation/AnimationInstance`1.cs
  2. 365
      tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs
  3. 241
      tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs
  4. 6
      tests/Avalonia.Base.UnitTests/Animation/BrushTransitionTests.cs
  5. 68
      tests/Avalonia.Base.UnitTests/Animation/KeySplineTests.cs
  6. 34
      tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs
  7. 94
      tests/Avalonia.Base.UnitTests/Animation/TransitionsTests.cs

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

@ -53,10 +53,10 @@ namespace Avalonia.Animation
private void FetchProperties()
{
if (_animation.SpeedRatio < 0d)
throw new ArgumentOutOfRangeException("SpeedRatio value should not be negative.");
throw new InvalidOperationException("SpeedRatio value should not be negative.");
if (_animation.Duration.TotalSeconds <= 0)
throw new InvalidOperationException("Duration value cannot be negative or zero.");
if (_animation.Duration < TimeSpan.Zero)
throw new InvalidOperationException("Duration value cannot be negative.");
_easeFunc = _animation.Easing;
@ -110,8 +110,8 @@ namespace Avalonia.Animation
{
if (_animator.Property is null)
throw new InvalidOperationException("Animator has no property specified.");
if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both)
_targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue);
if (_fillMode is FillMode.Forward or FillMode.Both)
_targetControl.SetValue(_animator.Property, _lastInterpValue);
}
private void DoComplete()
@ -123,11 +123,8 @@ namespace Avalonia.Animation
private void DoDelay()
{
if (_fillMode == FillMode.Backward || _fillMode == FillMode.Both)
if (_currentIteration == 0)
PublishNext(_firstKFValue);
else
PublishNext(_lastInterpValue);
if (_fillMode is not (FillMode.Backward or FillMode.Both)) return;
PublishNext(_currentIteration == 0 ? _firstKFValue : _lastInterpValue);
}
private void DoPlayStates()
@ -167,9 +164,9 @@ namespace Avalonia.Animation
_currentIteration = (ulong)(opsTime / iterationTime);
// Stop animation when the current iteration is beyond the iteration count
// and snap the last iteration value to exact values.
if ((_currentIteration + 1) > _iterationCount)
// 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);
@ -192,10 +189,10 @@ namespace Avalonia.Animation
_playbackReversed = true;
break;
case PlaybackDirection.Alternate:
_playbackReversed = (_currentIteration % 2 == 0) ? false : true;
_playbackReversed = _currentIteration % 2 != 0;
break;
case PlaybackDirection.AlternateReverse:
_playbackReversed = (_currentIteration % 2 == 0) ? true : false;
_playbackReversed = _currentIteration % 2 == 0;
break;
default:
throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}");
@ -215,7 +212,7 @@ namespace Avalonia.Animation
iterDelay > 0)
{
// The last iteration's trailing delay should be skipped.
if ((_currentIteration + 1) < _iterationCount)
if (_currentIteration + 1 < _iterationCount)
DoDelay();
else
DoComplete();

365
tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs

@ -5,7 +5,6 @@ using Avalonia.Controls.Shapes;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
@ -19,18 +18,15 @@ namespace Avalonia.Base.UnitTests.Animation
public void Transition_Is_Not_Applied_When_Not_Attached_To_Visual_Tree()
{
var target = CreateTarget();
var control = new Control
{
Transitions = new Transitions { target.Object },
};
var control = new Control { Transitions = new Transitions { target.Object }, };
control.Opacity = 0.5;
target.Verify(x => x.Apply(
control,
It.IsAny<IClock>(),
1.0,
0.5),
control,
It.IsAny<IClock>(),
1.0,
0.5),
Times.Never);
}
@ -40,10 +36,7 @@ namespace Avalonia.Base.UnitTests.Animation
using (Start())
{
var target = CreateTarget();
var control = new Control
{
Transitions = new Transitions { target.Object },
};
var control = new Control { Transitions = new Transitions { target.Object }, };
var root = new TestRoot
{
@ -51,10 +44,7 @@ namespace Avalonia.Base.UnitTests.Animation
{
new Style(x => x.OfType<Control>())
{
Setters =
{
new Setter(Visual.OpacityProperty, 0.8),
}
Setters = { new Setter(Visual.OpacityProperty, 0.8), }
}
}
};
@ -64,10 +54,10 @@ namespace Avalonia.Base.UnitTests.Animation
Assert.Equal(0.8, control.Opacity);
target.Verify(x => x.Apply(
It.IsAny<Control>(),
It.IsAny<IClock>(),
It.IsAny<object>(),
It.IsAny<object>()),
It.IsAny<Control>(),
It.IsAny<IClock>(),
It.IsAny<object>(),
It.IsAny<object>()),
Times.Never);
}
}
@ -97,10 +87,10 @@ namespace Avalonia.Base.UnitTests.Animation
control.SetValue(Visual.OpacityProperty, 0.5, BindingPriority.Animation);
target.Verify(x => x.Apply(
control,
It.IsAny<IClock>(),
1.0,
0.5),
control,
It.IsAny<IClock>(),
1.0,
0.5),
Times.Never);
}
@ -112,53 +102,34 @@ namespace Avalonia.Base.UnitTests.Animation
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Layoutable.WidthProperty, 1d),
},
KeyTime = TimeSpan.FromSeconds(0)
Setters = { new Setter(Layoutable.WidthProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Layoutable.WidthProperty, 2d),
},
KeyTime = TimeSpan.FromSeconds(2),
Setters = { new Setter(Layoutable.WidthProperty, 2d), }, KeyTime = TimeSpan.FromSeconds(2),
};
var keyframe3 = new KeyFrame()
{
Setters =
{
new Setter(Layoutable.WidthProperty, invalidValue),
},
Setters = { new Setter(Layoutable.WidthProperty, invalidValue), },
KeyTime = TimeSpan.FromSeconds(3),
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(3),
Children =
{
keyframe1,
keyframe2,
keyframe3
},
Children = { keyframe1, keyframe2, keyframe3 },
IterationCount = new IterationCount(5),
PlaybackDirection = PlaybackDirection.Alternate,
};
var rect = new Rectangle()
{
Width = 11,
};
var rect = new Rectangle() { Width = 11, };
var originalValue = rect.Width;
var clock = new TestClock();
var animationRun = animation.RunAsync(rect, clock);
animation.RunAsync(rect, clock);
clock.Step(TimeSpan.Zero);
Assert.Equal(rect.Width, 1);
@ -188,10 +159,10 @@ namespace Avalonia.Base.UnitTests.Animation
control.SetValue(Visual.OpacityProperty, 0.8, BindingPriority.StyleTrigger);
target.Verify(x => x.Apply(
It.IsAny<Control>(),
It.IsAny<IClock>(),
It.IsAny<object>(),
It.IsAny<object>()),
It.IsAny<Control>(),
It.IsAny<IClock>(),
It.IsAny<object>(),
It.IsAny<object>()),
Times.Never);
}
@ -258,14 +229,15 @@ namespace Avalonia.Base.UnitTests.Animation
target.Invocations.Clear();
var root = (TestRoot)control.Parent;
Assert.NotNull(root);
root.Child = null;
control.Opacity = 0.8;
target.Verify(x => x.Apply(
It.IsAny<Control>(),
It.IsAny<IClock>(),
It.IsAny<object>(),
It.IsAny<object>()),
It.IsAny<Control>(),
It.IsAny<IClock>(),
It.IsAny<object>(),
It.IsAny<object>()),
Times.Never);
}
@ -284,6 +256,8 @@ namespace Avalonia.Base.UnitTests.Animation
It.IsAny<object>())).Returns(sub.Object);
control.Opacity = 0.5;
Assert.NotNull(control.Transitions);
control.Transitions.RemoveAt(0);
sub.Verify(x => x.Dispose());
@ -307,10 +281,10 @@ namespace Avalonia.Base.UnitTests.Animation
control.Opacity = 0.5;
target.Verify(x => x.Apply(
control,
It.IsAny<IClock>(),
1.0,
0.5),
control,
It.IsAny<IClock>(),
1.0,
0.5),
Times.Once);
control.Classes.Add("foo");
@ -324,18 +298,17 @@ namespace Avalonia.Base.UnitTests.Animation
{
using (Start())
{
var target = CreateTransition(Control.WidthProperty);
var target = CreateTransition(Layoutable.WidthProperty);
var control = CreateStyledControl(transition2: target.Object);
var sub = new Mock<IDisposable>();
control.Classes.Add("foo");
control.Width = 100;
target.Verify(x => x.Apply(
control,
It.IsAny<IClock>(),
double.NaN,
100.0),
control,
It.IsAny<IClock>(),
double.NaN,
100.0),
Times.Once);
}
}
@ -357,12 +330,12 @@ namespace Avalonia.Base.UnitTests.Animation
{
Setters =
{
new Setter(Border.TransitionsProperty,
new Setter(Animatable.TransitionsProperty,
new Transitions
{
new DoubleTransition
{
Property = Border.OpacityProperty,
Property = Visual.OpacityProperty,
Duration = TimeSpan.FromSeconds(1),
},
}),
@ -372,23 +345,20 @@ namespace Avalonia.Base.UnitTests.Animation
{
Setters =
{
new Setter(Border.TransitionsProperty,
new Setter(Animatable.TransitionsProperty,
new Transitions
{
new DoubleTransition
{
Property = Border.OpacityProperty,
Property = Visual.OpacityProperty,
Duration = TimeSpan.FromSeconds(1),
},
}),
new Setter(Border.OpacityProperty, 0.0),
new Setter(Visual.OpacityProperty, 0.0),
},
},
},
Child = target = new Border
{
Background = Brushes.Red,
}
Child = target = new Border { Background = Brushes.Red, }
};
root.Measure(Size.Infinity);
@ -421,7 +391,7 @@ namespace Avalonia.Base.UnitTests.Animation
// Assigning and then clearing Transitions ensures we have a transition state
// collection created.
control.ClearValue(Control.TransitionsProperty);
control.ClearValue(Animatable.TransitionsProperty);
control.GetValueStore().BeginStyling();
@ -431,8 +401,8 @@ namespace Avalonia.Base.UnitTests.Animation
{
Setters =
{
new Setter(Control.OpacityProperty, 0.5),
new Setter(Control.TransitionsProperty, new Transitions { target.Object }),
new Setter(Visual.OpacityProperty, 0.5),
new Setter(Animatable.TransitionsProperty, new Transitions { target.Object }),
}
};
@ -450,28 +420,17 @@ namespace Avalonia.Base.UnitTests.Animation
var opacityTransition = new DoubleTransition
{
Property = Control.OpacityProperty,
Duration = TimeSpan.FromSeconds(1),
Property = Visual.OpacityProperty, Duration = TimeSpan.FromSeconds(1),
};
var transitions = new Transitions { opacityTransition };
var borderTheme = new ControlTheme(typeof(Border))
{
Setters =
{
new Setter(Control.TransitionsProperty, transitions),
}
Setters = { new Setter(Animatable.TransitionsProperty, transitions), }
};
var clock = new TestClock();
var root = new TestRoot
{
Clock = clock,
Resources =
{
{ typeof(Border), borderTheme },
}
};
var root = new TestRoot { Clock = clock, Resources = { { typeof(Border), borderTheme }, } };
var border = new Border();
root.Child = border;
@ -489,13 +448,214 @@ namespace Avalonia.Base.UnitTests.Animation
// Now clear the property; a transition is now in progress but no local value is
// set.
border.ClearValue(Border.OpacityProperty);
border.ClearValue(Visual.OpacityProperty);
// Remove the transition by removing the control from the logical tree. This was
// causing an exception.
root.Child = null;
}
[Fact]
public void Run_Normal_Use_Case_Animation()
{
using (Start())
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 0.5d), }, KeyTime = TimeSpan.FromSeconds(1)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(10), Children = { keyframe1, keyframe2 },
};
Border target;
var clock = new TestClock();
var root = new TestRoot
{
Clock = clock,
Styles = { new Style(x => x.OfType<Border>()) { Animations = { animation }, } },
Child = target = new Border { Background = Brushes.Red, }
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
clock.Step(TimeSpan.FromSeconds(0));
clock.Step(TimeSpan.FromSeconds(0.99));
Assert.InRange(target.Opacity, 0.5d, 0.51d);
}
}
[Fact]
public void Run_Normal_Use_Case_Animation_With_Infinite_Iteration()
{
using (Start())
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 0d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(1)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(1),
IterationCount = IterationCount.Infinite,
Children = { keyframe1, keyframe2 },
};
Border target;
var clock = new TestClock();
var root = new TestRoot
{
Clock = clock,
Styles = { new Style(x => x.OfType<Border>()) { Animations = { animation }, } },
Child = target = new Border { Background = Brushes.Red, }
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
clock.Step(TimeSpan.FromSeconds(0));
clock.Step(TimeSpan.FromSeconds(0.5));
Assert.Equal(0.5, target.Opacity);
clock.Step(TimeSpan.FromSeconds(1));
Assert.Equal(0, target.Opacity);
clock.Step(TimeSpan.FromSeconds(1.5));
Assert.Equal(0.5, target.Opacity);
clock.Step(TimeSpan.FromSeconds(2));
Assert.Equal(0, target.Opacity);
}
}
[Fact]
public void Zero_Duration_Should_Finish_Animation()
{
using (Start())
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 0.5d), }, KeyTime = TimeSpan.FromSeconds(2)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(2),
Children = { keyframe1, keyframe2 },
FillMode = FillMode.Both
};
Border target;
var clock = new TestClock();
var root = new TestRoot
{
Clock = clock,
Styles = { new Style(x => x.OfType<Border>()) { Animations = { animation }, } },
Child = target = new Border { Background = Brushes.Red, }
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
clock.Step(TimeSpan.FromSeconds(0));
clock.Step(TimeSpan.FromSeconds(1));
Assert.True(target.IsAnimating(Visual.OpacityProperty));
Assert.Equal(0.75, target.Opacity);
// This is not the normal way to access and set the animations
// object's Duration property to zero that is defined in styles
// but this is still valid for the RunAsync version.
animation.Duration = TimeSpan.Zero;
clock.Step(TimeSpan.FromSeconds(1.2));
Assert.Equal(0.5, target.Opacity);
Assert.False(target.IsAnimating(Visual.OpacityProperty));
}
}
[Fact]
public void Zero_Duration_Should_Finish_Animation_With_Infinite_Iteration()
{
using (Start())
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 0d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(1)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(1),
IterationCount = IterationCount.Infinite,
Children = { keyframe1, keyframe2 },
};
Border target;
var clock = new TestClock();
var root = new TestRoot
{
Clock = clock,
Styles = { new Style(x => x.OfType<Border>()) { Animations = { animation }, } },
Child = target = new Border { Background = Brushes.Red, }
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
clock.Step(TimeSpan.FromSeconds(0));
Assert.True(target.IsAnimating(Visual.OpacityProperty));
clock.Step(TimeSpan.FromSeconds(0.5));
Assert.Equal(0.5, target.Opacity);
clock.Step(TimeSpan.FromSeconds(1));
Assert.Equal(0, target.Opacity);
clock.Step(TimeSpan.FromSeconds(1.5));
Assert.Equal(0.5, target.Opacity);
clock.Step(TimeSpan.FromSeconds(2));
Assert.Equal(0, target.Opacity);
// This is not the normal way to access and set the animations
// object's Duration property to zero that is defined in styles
// but this is still valid for the RunAsync version.
animation.Duration = TimeSpan.Zero;
clock.Step(TimeSpan.FromSeconds(1.2));
Assert.Equal(1, target.Opacity);
Assert.False(target.IsAnimating(Visual.OpacityProperty));
}
}
private static IDisposable Start()
{
var clock = new MockGlobalClock();
@ -510,12 +670,9 @@ namespace Avalonia.Base.UnitTests.Animation
private static Control CreateControl(ITransition transition)
{
var control = new Control
{
Transitions = new Transitions { transition },
};
var control = new Control { Transitions = new Transitions { transition }, };
var root = new TestRoot(control);
var _ = new TestRoot(control);
return control;
}
@ -524,7 +681,7 @@ namespace Avalonia.Base.UnitTests.Animation
ITransition transition2 = null)
{
transition1 = transition1 ?? CreateTarget().Object;
transition2 = transition2 ?? CreateTransition(Control.WidthProperty).Object;
transition2 = transition2 ?? CreateTransition(Layoutable.WidthProperty).Object;
var control = new Control
{
@ -536,7 +693,7 @@ namespace Avalonia.Base.UnitTests.Animation
{
new Setter
{
Property = Control.TransitionsProperty,
Property = Animatable.TransitionsProperty,
Value = new Transitions { transition1 },
}
}
@ -547,7 +704,7 @@ namespace Avalonia.Base.UnitTests.Animation
{
new Setter
{
Property = Control.TransitionsProperty,
Property = Animatable.TransitionsProperty,
Value = new Transitions { transition2 },
}
}
@ -555,7 +712,7 @@ namespace Avalonia.Base.UnitTests.Animation
}
};
var root = new TestRoot(control);
var _ = new TestRoot(control);
return control;
}

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

@ -1,16 +1,13 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.Data;
using Xunit;
using Avalonia.Animation.Easings;
using System.Threading;
using System.Reactive.Linq;
using Avalonia.Layout;
namespace Avalonia.Base.UnitTests.Animation
{
@ -23,47 +20,27 @@ namespace Avalonia.Base.UnitTests.Animation
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
KeyTime = TimeSpan.FromSeconds(0.5)
Setters = { new Setter(Layoutable.WidthProperty, 100d), }, KeyTime = TimeSpan.FromSeconds(0.5)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 0d),
},
KeyTime = TimeSpan.FromSeconds(0)
Setters = { new Setter(Layoutable.WidthProperty, 0d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(1),
Children =
{
keyframe2,
keyframe1
}
};
var animation = new Animation() { Duration = TimeSpan.FromSeconds(1), Children = { keyframe2, keyframe1 } };
var border = new Border()
{
Height = 100d,
Width = 100d
};
var border = new Border() { Height = 100d, Width = 100d };
var clock = new TestClock();
var animationRun = animation.RunAsync(border, clock);
clock.Step(TimeSpan.Zero);
animation.RunAsync(border, clock);
clock.Step(TimeSpan.Zero);
Assert.Equal(border.Width, 0d);
clock.Step(TimeSpan.FromSeconds(1));
clock.Step(TimeSpan.FromSeconds(1));
Assert.Equal(border.Width, 100d);
}
@ -72,40 +49,24 @@ namespace Avalonia.Base.UnitTests.Animation
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
};
var animation = new Avalonia.Animation.Animation()
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(3),
Delay = TimeSpan.FromSeconds(3),
DelayBetweenIterations = TimeSpan.FromSeconds(3),
IterationCount = new IterationCount(2),
Children =
{
keyframe2,
keyframe1
}
Children = { keyframe2, keyframe1 }
};
var border = new Border()
{
Height = 100d,
Width = 100d
};
var border = new Border() { Height = 100d, Width = 100d };
var clock = new TestClock();
var animationRun = animation.RunAsync(border, clock);
@ -133,43 +94,28 @@ namespace Avalonia.Base.UnitTests.Animation
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 0d),
},
Cue = new Cue(0.0d)
Setters = { new Setter(Layoutable.WidthProperty, 0d), }, Cue = new Cue(0.0d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 300d),
},
Cue = new Cue(1.0d)
Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(1.0d)
};
var animation = new Avalonia.Animation.Animation()
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(0.05d),
Delay = TimeSpan.FromSeconds(0.05d),
Easing = new SineEaseInOut(),
FillMode = FillMode.Both,
Children =
{
keyframe1,
keyframe2
}
Children = { keyframe1, keyframe2 }
};
var border = new Border()
{
Height = 100d,
Width = 100d,
};
var border = new Border() { Height = 100d, Width = 100d, };
var clock = new TestClock();
var animationRun = animation.RunAsync(border, clock);
animation.RunAsync(border, clock);
clock.Step(TimeSpan.FromSeconds(0d));
Assert.Equal(border.Width, 0d);
@ -186,20 +132,12 @@ namespace Avalonia.Base.UnitTests.Animation
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
};
var animation = new Animation()
@ -208,23 +146,15 @@ namespace Avalonia.Base.UnitTests.Animation
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
Children = { keyframe2, keyframe1 }
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var border = new Border() { Height = 100d, Width = 50d };
var propertyChangedCount = 0;
var animationCompletedCount = 0;
border.PropertyChanged += (sender, e) =>
border.PropertyChanged += (_, e) =>
{
if (e.Property == Control.WidthProperty)
if (e.Property == Layoutable.WidthProperty)
{
propertyChangedCount++;
}
@ -257,20 +187,12 @@ namespace Avalonia.Base.UnitTests.Animation
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
};
var animation = new Animation()
@ -279,22 +201,14 @@ namespace Avalonia.Base.UnitTests.Animation
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
Children = { keyframe2, keyframe1 }
};
var border = new Border()
{
Height = 100d,
Width = 100d
};
var border = new Border() { Height = 100d, Width = 100d };
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
border.PropertyChanged += (_, e) =>
{
if (e.Property == Control.WidthProperty)
if (e.Property == Layoutable.WidthProperty)
{
propertyChangedCount++;
}
@ -315,20 +229,12 @@ namespace Avalonia.Base.UnitTests.Animation
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
};
var animation = new Animation()
@ -337,22 +243,14 @@ namespace Avalonia.Base.UnitTests.Animation
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
Children = { keyframe2, keyframe1 }
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var border = new Border() { Height = 100d, Width = 50d };
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
border.PropertyChanged += (_, e) =>
{
if (e.Property == Control.WidthProperty)
if (e.Property == Layoutable.WidthProperty)
{
propertyChangedCount++;
}
@ -361,6 +259,7 @@ namespace Avalonia.Base.UnitTests.Animation
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
Assert.False(animationRun.IsCompleted);
Assert.Equal(0, propertyChangedCount);
@ -381,24 +280,16 @@ namespace Avalonia.Base.UnitTests.Animation
}
[Fact]
public void Cancellation_Of_Completed_Animation_Does_Not_Fail()
public void Dont_Run_Infinite_Iteration_Animation_On_RunAsync_Method()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
};
var animation = new Animation()
@ -406,23 +297,47 @@ namespace Avalonia.Base.UnitTests.Animation
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
IterationCount = IterationCount.Infinite,
Children = { keyframe2, keyframe1 }
};
var border = new Border()
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 void 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()
{
Height = 100d,
Width = 50d
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 += (sender, e) =>
border.PropertyChanged += (_, e) =>
{
if (e.Property == Control.WidthProperty)
if (e.Property == Layoutable.WidthProperty)
{
propertyChangedCount++;
}

6
tests/Avalonia.Base.UnitTests/Animation/BrushTransitionTests.cs

@ -36,8 +36,7 @@ namespace Avalonia.Base.UnitTests.Animation
var border = new Border() { Background = oldBrush };
BrushTransition sut = new BrushTransition
{
Duration = TimeSpan.FromSeconds(1),
Property = Border.BackgroundProperty
Duration = TimeSpan.FromSeconds(1), Property = Border.BackgroundProperty
};
sut.Apply(border, clock, oldBrush, newBrush);
@ -45,7 +44,8 @@ namespace Avalonia.Base.UnitTests.Animation
clock.Pulse(sut.Duration * progress);
Assert.NotNull(border.Background);
Assert.Equal(oldBrush.Opacity + (newBrush.Opacity - oldBrush.Opacity) * progress, border.Background.Opacity);
Assert.Equal(oldBrush.Opacity + (newBrush.Opacity - oldBrush.Opacity) * progress,
border.Background.Opacity);
}
}
}

68
tests/Avalonia.Base.UnitTests/Animation/KeySplineTests.cs

@ -21,6 +21,8 @@ namespace Avalonia.Base.UnitTests.Animation
var keySpline = (KeySpline)conv.ConvertFrom(input);
Assert.NotNull(keySpline);
Assert.Equal(1, keySpline.ControlPointX1);
Assert.Equal(2, keySpline.ControlPointY1);
Assert.Equal(3, keySpline.ControlPointX2);
@ -28,8 +30,8 @@ namespace Avalonia.Base.UnitTests.Animation
}
[Theory]
[InlineData("1,2F,3,4")]
[InlineData("Foo,Bar,Fee,Buzz")]
[InlineData("1,2F,3,4")]
[InlineData("Foo,Bar,Fee,Buzz")]
public void Can_Handle_Invalid_String_KeySpline_Via_TypeConverter(string input)
{
var conv = new KeySplineTypeConverter();
@ -104,46 +106,33 @@ namespace Avalonia.Base.UnitTests.Animation
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(RotateTransform.AngleProperty, -2.5d),
},
KeyTime = TimeSpan.FromSeconds(0)
Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(RotateTransform.AngleProperty, 2.5d),
},
Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), },
KeyTime = TimeSpan.FromSeconds(5),
KeySpline = new KeySpline(0.1123555056179775,
0.657303370786517,
0.8370786516853934,
0.499999999999999999)
0.657303370786517,
0.8370786516853934,
0.499999999999999999)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(5),
Children =
{
keyframe1,
keyframe2
},
Children = { keyframe1, keyframe2 },
IterationCount = new IterationCount(5),
PlaybackDirection = PlaybackDirection.Alternate
};
var rotateTransform = new RotateTransform(-2.5);
var rect = new Rectangle()
{
RenderTransform = rotateTransform
};
var rect = new Rectangle() { RenderTransform = rotateTransform };
var clock = new TestClock();
var animationRun = animation.RunAsync(rect, clock);
animation.RunAsync(rect, clock);
// position is what you'd expect at end and beginning
clock.Step(TimeSpan.Zero);
@ -169,49 +158,36 @@ namespace Avalonia.Base.UnitTests.Animation
expected = 1.8016358493761722;
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
}
[Fact]
public void Check_KeySpline_Parsing_Is_Correct()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(RotateTransform.AngleProperty, -2.5d),
},
KeyTime = TimeSpan.FromSeconds(0)
Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(RotateTransform.AngleProperty, 2.5d),
},
KeyTime = TimeSpan.FromSeconds(5),
Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), }, KeyTime = TimeSpan.FromSeconds(5),
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(5),
Children =
{
keyframe1,
keyframe2
},
Children = { keyframe1, keyframe2 },
IterationCount = new IterationCount(5),
PlaybackDirection = PlaybackDirection.Alternate,
Easing = Easing.Parse("0.1123555056179775,0.657303370786517,0.8370786516853934,0.499999999999999999")
Easing = Easing.Parse(
"0.1123555056179775,0.657303370786517,0.8370786516853934,0.499999999999999999")
};
var rotateTransform = new RotateTransform(-2.5);
var rect = new Rectangle()
{
RenderTransform = rotateTransform
};
var rect = new Rectangle() { RenderTransform = rotateTransform };
var clock = new TestClock();
var animationRun = animation.RunAsync(rect, clock);
animation.RunAsync(rect, clock);
// position is what you'd expect at end and beginning
clock.Step(TimeSpan.Zero);

34
tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs

@ -18,6 +18,7 @@ public class SpringTests
var spring = (Spring)conv.ConvertFrom(input);
Assert.NotNull(spring);
Assert.Equal(1, spring.Mass);
Assert.Equal(2, spring.Stiffness);
Assert.Equal(3, spring.Damping);
@ -25,8 +26,8 @@ public class SpringTests
}
[Theory]
[InlineData("1,2F,3,4")]
[InlineData("Foo,Bar,Fee,Buzz")]
[InlineData("1,2F,3,4")]
[InlineData("Foo,Bar,Fee,Buzz")]
public void Can_Handle_Invalid_String_Via_TypeConverter(string input)
{
var conv = new SpringTypeConverter();
@ -37,7 +38,7 @@ public class SpringTests
[Fact]
public void SplineEasing_Can_Be_Mutated()
{
var easing = new SpringEasing(1, 1, 1, 0);
var easing = new SpringEasing(1, 1, 1);
Assert.Equal(0, easing.Ease(0));
Assert.Equal(0.34029984660829826, easing.Ease(1));
@ -55,43 +56,28 @@ public class SpringTests
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(RotateTransform.AngleProperty, -2.5d),
},
KeyTime = TimeSpan.FromSeconds(0)
Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(RotateTransform.AngleProperty, 2.5d),
},
KeyTime = TimeSpan.FromSeconds(5)
Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), }, KeyTime = TimeSpan.FromSeconds(5)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(5),
Children =
{
keyframe1,
keyframe2
},
Children = { keyframe1, keyframe2 },
IterationCount = new IterationCount(5),
PlaybackDirection = PlaybackDirection.Alternate,
Easing = new SpringEasing(1, 10, 1, 0)
Easing = new SpringEasing(1, 10, 1)
};
var rotateTransform = new RotateTransform(-2.5);
var rect = new Rectangle()
{
RenderTransform = rotateTransform
};
var rect = new Rectangle() { RenderTransform = rotateTransform };
var clock = new TestClock();
var animationRun = animation.RunAsync(rect, clock);
animation.RunAsync(rect, clock);
clock.Step(TimeSpan.Zero);
Assert.Equal(rotateTransform.Angle, -2.5);

94
tests/Avalonia.Base.UnitTests/Animation/TransitionsTests.cs

@ -15,13 +15,12 @@ namespace Avalonia.Base.UnitTests.Animation
var border = new Border
{
Transitions = new Transitions
{
new DoubleTransition
{
new DoubleTransition
{
Duration = TimeSpan.FromSeconds(1),
Property = Border.OpacityProperty,
}
Duration = TimeSpan.FromSeconds(1), Property = Visual.OpacityProperty,
}
}
};
border.Opacity = 0;
@ -30,7 +29,6 @@ namespace Avalonia.Base.UnitTests.Animation
clock.Pulse(TimeSpan.FromSeconds(-0.5));
Assert.Equal(0, border.Opacity);
}
[Fact]
@ -41,13 +39,12 @@ namespace Avalonia.Base.UnitTests.Animation
var border = new Border
{
Transitions = new Transitions
{
new DoubleTransition
{
new DoubleTransition
{
Duration = TimeSpan.FromSeconds(1),
Property = Border.OpacityProperty,
}
Duration = TimeSpan.FromSeconds(1), Property = Visual.OpacityProperty,
}
}
};
border.Opacity = 0;
@ -56,7 +53,6 @@ namespace Avalonia.Base.UnitTests.Animation
clock.Pulse(TimeSpan.FromMilliseconds(1001));
Assert.Equal(0, border.Opacity);
}
[Fact]
@ -64,18 +60,22 @@ namespace Avalonia.Base.UnitTests.Animation
{
var clock = new TestClock();
int i = 0;
var inst = new TransitionInstance(clock, TimeSpan.Zero, TimeSpan.Zero).Subscribe(nextValue =>
var i = 0;
new TransitionInstance(clock, TimeSpan.Zero, TimeSpan.Zero).Subscribe(nextValue =>
{
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]
@ -83,26 +83,50 @@ namespace Avalonia.Base.UnitTests.Animation
{
var clock = new TestClock();
int i = -1;
var inst = new TransitionInstance(clock, TimeSpan.FromMilliseconds(30), TimeSpan.FromMilliseconds(70)).Subscribe(nextValue =>
{
switch (i++)
var i = -1;
new TransitionInstance(clock, TimeSpan.FromMilliseconds(30), TimeSpan.FromMilliseconds(70)).Subscribe(
nextValue =>
{
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;
}
});
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++)
for (var z = 0; z <= 10; z++)
{
clock.Pulse(TimeSpan.FromMilliseconds(10));
}

Loading…
Cancel
Save