diff --git a/samples/ControlCatalog/Pages/CanvasPage.xaml b/samples/ControlCatalog/Pages/CanvasPage.xaml
index 10a38895a2..d6c138a4f7 100644
--- a/samples/ControlCatalog/Pages/CanvasPage.xaml
+++ b/samples/ControlCatalog/Pages/CanvasPage.xaml
@@ -11,7 +11,8 @@
-
+
+
diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml
index 7d72d1821b..cc3c31d13a 100644
--- a/samples/ControlCatalog/SideBar.xaml
+++ b/samples/ControlCatalog/SideBar.xaml
@@ -36,7 +36,7 @@
-
+
diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml
index b5f8ccaf01..26da2cc556 100644
--- a/samples/RenderDemo/SideBar.xaml
+++ b/samples/RenderDemo/SideBar.xaml
@@ -37,7 +37,7 @@
-
+
diff --git a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
index 626a3e7c77..f724baf3c6 100644
--- a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
+++ b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
@@ -10,21 +10,21 @@ namespace RenderDemo.ViewModels
public AnimationsPageViewModel()
{
- ToggleGlobalPlayState = ReactiveCommand.Create(()=>TogglePlayState());
+ ToggleGlobalPlayState = ReactiveCommand.Create(() => TogglePlayState());
}
void TogglePlayState()
{
- switch (Timing.GetGlobalPlayState())
+ switch (Animation.GlobalPlayState)
{
case PlayState.Run:
PlayStateText = "Resume all animations";
- Timing.SetGlobalPlayState(PlayState.Pause);
+ Animation.GlobalPlayState = PlayState.Pause;
break;
case PlayState.Pause:
PlayStateText = "Pause all animations";
- Timing.SetGlobalPlayState(PlayState.Run);
+ Animation.GlobalPlayState = PlayState.Run;
break;
}
}
@@ -36,5 +36,5 @@ namespace RenderDemo.ViewModels
}
public ReactiveCommand ToggleGlobalPlayState { get; }
- }
+ }
}
diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs
index 065f1a285a..1ac4b44a74 100644
--- a/samples/interop/Direct3DInteropSample/MainWindow.cs
+++ b/samples/interop/Direct3DInteropSample/MainWindow.cs
@@ -132,7 +132,7 @@ namespace Direct3DInteropSample
signature,
inputElements);
- // Instantiate Vertex buiffer from vertex data
+ // Instantiate Vertex buffer from vertex data
var vertices = Buffer.Create(
device,
BindFlags.VertexBuffer,
diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs
index 10cd65132f..8a1a17a6fc 100644
--- a/src/Avalonia.Animation/Animatable.cs
+++ b/src/Avalonia.Animation/Animatable.cs
@@ -11,35 +11,10 @@ using Avalonia.Data;
namespace Avalonia.Animation
{
///
- /// Base class for control which can have property transitions.
+ /// Base class for all animatable objects.
///
public class Animatable : AvaloniaObject
- {
- ///
- /// Initializes this object.
- ///
- public Animatable()
- {
- Transitions = new Transitions();
- AnimatableTimer = Timing.AnimationStateTimer
- .Select(p =>
- {
- if (this._playState == PlayState.Pause)
- {
- return PlayState.Pause;
- }
- else return p;
- })
- .Publish()
- .RefCount();
- }
-
- ///
- /// The specific animations timer for this control.
- ///
- ///
- public IObservable AnimatableTimer;
-
+ {
///
/// Defines the property.
///
@@ -59,27 +34,25 @@ namespace Avalonia.Animation
{
get { return _playState; }
set { SetAndRaise(PlayStateProperty, ref _playState, value); }
-
}
-
///
/// Defines the property.
///
- public static readonly DirectProperty> TransitionsProperty =
- AvaloniaProperty.RegisterDirect>(
+ public static readonly DirectProperty TransitionsProperty =
+ AvaloniaProperty.RegisterDirect(
nameof(Transitions),
o => o.Transitions,
(o, v) => o.Transitions = v);
- private IEnumerable _transitions = new AvaloniaList();
+ private Transitions _transitions;
///
/// Gets or sets the property transitions for the control.
///
- public IEnumerable Transitions
+ public Transitions Transitions
{
- get { return _transitions; }
+ get { return _transitions ?? (_transitions = new Transitions()); }
set { SetAndRaise(TransitionsProperty, ref _transitions, value); }
}
@@ -100,6 +73,5 @@ namespace Avalonia.Animation
}
}
}
-
}
-}
+}
\ No newline at end of file
diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs
index 3e90715019..2c359ecac3 100644
--- a/src/Avalonia.Animation/Animation.cs
+++ b/src/Avalonia.Animation/Animation.cs
@@ -17,6 +17,60 @@ namespace Avalonia.Animation
///
public class Animation : AvaloniaList, IAnimation
{
+ ///
+ /// Gets or sets the animation play state for all animations
+ ///
+ public static PlayState GlobalPlayState { get; set; } = PlayState.Run;
+
+ ///
+ /// Gets or sets the active time of this animation.
+ ///
+ public TimeSpan Duration { get; set; }
+
+ ///
+ /// Gets or sets the repeat count for this animation.
+ ///
+ public RepeatCount RepeatCount { get; set; }
+
+ ///
+ /// Gets or sets the playback direction for this animation.
+ ///
+ public PlaybackDirection PlaybackDirection { get; set; }
+
+ ///
+ /// Gets or sets the value fill mode for this animation.
+ ///
+ public FillMode FillMode { get; set; }
+
+ ///
+ /// Gets or sets the easing function to be used for this animation.
+ ///
+ public Easing Easing { get; set; } = new LinearEasing();
+
+ ///
+ /// Gets or sets the speed multiple for this animation.
+ ///
+ public double SpeedRatio { get; set; } = 1d;
+
+ ///
+ /// Gets or sets the delay time for this animation.
+ ///
+ ///
+ /// Describes a delay to be added before the animation starts, and optionally between
+ /// repeats of the animation if is set.
+ ///
+ public TimeSpan Delay { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether will be applied between
+ /// iterations of the animation.
+ ///
+ ///
+ /// If this property is not set, then will only be applied to the first
+ /// iteration of the animation.
+ ///
+ public bool DelayBetweenIterations { get; set; }
+
private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)>
{
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
@@ -40,38 +94,6 @@ namespace Avalonia.Animation
return null;
}
- public AvaloniaList _animators { get; set; } = new AvaloniaList();
-
- ///
- /// Run time of this animation.
- ///
- public TimeSpan Duration { get; set; }
-
- ///
- /// Delay time for this animation.
- ///
- public TimeSpan Delay { get; set; }
-
- ///
- /// The repeat count for this animation.
- ///
- public RepeatCount RepeatCount { get; set; }
-
- ///
- /// The playback direction for this animation.
- ///
- public PlaybackDirection PlaybackDirection { get; set; }
-
- ///
- /// The value fill mode for this animation.
- ///
- public FillMode FillMode { get; set; }
-
- ///
- /// Easing function to be used.
- ///
- public Easing Easing { get; set; } = new LinearEasing();
-
private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable control)
{
var handlerList = new List<(Type type, AvaloniaProperty property)>();
diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs
new file mode 100644
index 0000000000..5a72904ed2
--- /dev/null
+++ b/src/Avalonia.Animation/AnimationInstance`1.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Linq;
+using System.Reactive.Linq;
+using Avalonia.Animation.Utils;
+using Avalonia.Data;
+using Avalonia.Reactive;
+
+namespace Avalonia.Animation
+{
+ ///
+ /// Handles interpolatoin and time-related functions
+ /// for keyframe animations.
+ ///
+ internal class AnimationInstance : SingleSubscriberObservableBase
+ {
+ private T _lastInterpValue;
+ private T _firstKFValue;
+ private long _repeatCount;
+ private double _currentIteration;
+ private bool _isLooping;
+ private bool _gotFirstKFValue;
+ private bool _gotFirstFrameCount;
+ private bool _iterationDelay;
+ private FillMode _fillMode;
+ private PlaybackDirection _animationDirection;
+ private Animator _parent;
+ private Animatable _targetControl;
+ private T _neutralValue;
+ private double _speedRatio;
+ private TimeSpan _delay;
+ private TimeSpan _duration;
+ private TimeSpan _firstFrameCount;
+ private TimeSpan _internalClock;
+ private TimeSpan? _previousClock;
+ private Easings.Easing _easeFunc;
+ private Action _onCompleteAction;
+ private Func _interpolator;
+ private IDisposable _timerSubscription;
+
+ public AnimationInstance(Animation animation, Animatable control, Animator animator, Action OnComplete, Func Interpolator)
+ {
+ if (animation.SpeedRatio <= 0)
+ throw new InvalidOperationException("Speed ratio cannot be negative or zero.");
+
+ if (animation.Duration.TotalSeconds <= 0)
+ throw new InvalidOperationException("Duration cannot be negative or zero.");
+
+ _parent = animator;
+ _easeFunc = animation.Easing;
+ _targetControl = control;
+ _neutralValue = (T)_targetControl.GetValue(_parent.Property);
+
+ _speedRatio = animation.SpeedRatio;
+
+ _delay = animation.Delay;
+ _duration = animation.Duration;
+ _iterationDelay = animation.DelayBetweenIterations;
+
+ switch (animation.RepeatCount.RepeatType)
+ {
+ case RepeatType.None:
+ _repeatCount = 1;
+ break;
+ case RepeatType.Loop:
+ _isLooping = true;
+ break;
+ case RepeatType.Repeat:
+ _repeatCount = (long)animation.RepeatCount.Value;
+ break;
+ }
+
+ _animationDirection = animation.PlaybackDirection;
+ _fillMode = animation.FillMode;
+ _onCompleteAction = OnComplete;
+ _interpolator = Interpolator;
+ }
+
+ protected override void Unsubscribed()
+ {
+ _timerSubscription?.Dispose();
+ }
+
+ protected override void Subscribed()
+ {
+ _timerSubscription = Timing.AnimationsTimer
+ .Subscribe(p => this.Step(p));
+ }
+
+ public void Step(TimeSpan frameTick)
+ {
+ try
+ {
+ InternalStep(frameTick);
+ }
+ catch (Exception e)
+ {
+ PublishError(e);
+ }
+ }
+
+ private void DoComplete()
+ {
+ if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both)
+ _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
+
+ _onCompleteAction?.Invoke();
+ PublishCompleted();
+ }
+
+ private void DoDelay()
+ {
+ if (_fillMode == FillMode.Backward || _fillMode == FillMode.Both)
+ if (_currentIteration == 0)
+ PublishNext(_firstKFValue);
+ else
+ PublishNext(_lastInterpValue);
+ }
+
+ private void DoPlayStatesAndTime(TimeSpan systemTime)
+ {
+ if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop)
+ DoComplete();
+
+ if (!_previousClock.HasValue)
+ {
+ _previousClock = systemTime;
+ _internalClock = TimeSpan.Zero;
+ }
+ else
+ {
+ if (Animation.GlobalPlayState == PlayState.Pause || _targetControl.PlayState == PlayState.Pause)
+ {
+ _previousClock = systemTime;
+ return;
+ }
+ var delta = systemTime - _previousClock;
+ _internalClock += delta.Value;
+ _previousClock = systemTime;
+ }
+
+ if (!_gotFirstKFValue)
+ {
+ _firstKFValue = (T)_parent.First().Value;
+ _gotFirstKFValue = true;
+ }
+
+ if (!_gotFirstFrameCount)
+ {
+ _firstFrameCount = _internalClock;
+ _gotFirstFrameCount = true;
+ }
+ }
+
+ private void InternalStep(TimeSpan systemTime)
+ {
+ DoPlayStatesAndTime(systemTime);
+
+ var time = _internalClock - _firstFrameCount;
+ var delayEndpoint = _delay;
+ var iterationEndpoint = delayEndpoint + _duration;
+
+ //determine if time is currently in the first iteration.
+ if (time >= TimeSpan.Zero & time <= iterationEndpoint)
+ {
+ _currentIteration = 1;
+ }
+ else if (time > iterationEndpoint)
+ {
+ //Subtract first iteration to properly get the subsequent iteration time
+ time -= iterationEndpoint;
+
+ if (!_iterationDelay & delayEndpoint > TimeSpan.Zero)
+ {
+ delayEndpoint = TimeSpan.Zero;
+ iterationEndpoint = _duration;
+ }
+
+ //Calculate the current iteration number
+ _currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2;
+ }
+ else
+ {
+ _previousClock = systemTime;
+ return;
+ }
+
+ time = TimeSpan.FromTicks(time.Ticks % iterationEndpoint.Ticks);
+
+ if (!_isLooping)
+ {
+ if (_currentIteration > _repeatCount)
+ DoComplete();
+
+ if (time > iterationEndpoint)
+ DoComplete();
+ }
+
+ // Determine if the current iteration should have its normalized time inverted.
+ bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false :
+ _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true :
+ _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false :
+ _animationDirection == PlaybackDirection.Reverse ? true : false;
+
+ if (delayEndpoint > TimeSpan.Zero & time < delayEndpoint)
+ {
+ DoDelay();
+ }
+ else
+ {
+ // Offset the delay time
+ time -= delayEndpoint;
+ iterationEndpoint -= delayEndpoint;
+
+ // Normalize time
+ var interpVal = (double)time.Ticks / iterationEndpoint.Ticks;
+
+ if (isCurIterReverse)
+ interpVal = 1 - interpVal;
+
+ // Ease and interpolate
+ var easedTime = _easeFunc.Ease(interpVal);
+ _lastInterpValue = _interpolator(easedTime, _neutralValue);
+
+ PublishNext(_lastInterpValue);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Animation/AnimatorKeyFrame.cs b/src/Avalonia.Animation/AnimatorKeyFrame.cs
index 508a317b71..09f259e754 100644
--- a/src/Avalonia.Animation/AnimatorKeyFrame.cs
+++ b/src/Avalonia.Animation/AnimatorKeyFrame.cs
@@ -25,6 +25,7 @@ namespace Avalonia.Animation
Cue = cue;
}
+ internal bool isNeutral;
public Type AnimatorType { get; }
public Cue Cue { get; }
public AvaloniaProperty Property { get; private set; }
diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs
deleted file mode 100644
index 87e189c997..0000000000
--- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs
+++ /dev/null
@@ -1,273 +0,0 @@
-using System;
-using System.Linq;
-using Avalonia.Data;
-
-namespace Avalonia.Animation
-{
- ///
- /// Provides statefulness for an iteration of a keyframe animation.
- ///
- internal class AnimatorStateMachine : IObservable