diff --git a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
index 626a3e7c77..c76d4db513 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 (Timing.GlobalPlayState)
{
case PlayState.Run:
PlayStateText = "Resume all animations";
- Timing.SetGlobalPlayState(PlayState.Pause);
+ Timing.GlobalPlayState = PlayState.Pause;
break;
case PlayState.Pause:
PlayStateText = "Pause all animations";
- Timing.SetGlobalPlayState(PlayState.Run);
+ Timing.GlobalPlayState = PlayState.Run;
break;
}
}
@@ -36,5 +36,5 @@ namespace RenderDemo.ViewModels
}
public ReactiveCommand ToggleGlobalPlayState { get; }
- }
+ }
}
diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs
index 85317af1a8..303e01aed8 100644
--- a/src/Avalonia.Animation/Animatable.cs
+++ b/src/Avalonia.Animation/Animatable.cs
@@ -23,25 +23,8 @@ namespace Avalonia.Animation
///
public Animatable()
{
- Transitions = new Transitions();
- AnimatableTimer = Timing.AnimationStateTimer
- .Select(p =>
- {
- if (this._playState == PlayState.Pause)
- {
- return (PlayState.Pause, p.Item2);
- }
- else return p;
- })
- .Publish()
- .RefCount();
- }
-
- ///
- /// The specific animations timer for this control.
- ///
- ///
- public IObservable<(PlayState, long)> AnimatableTimer;
+ Transitions = new Transitions();
+ }
///
/// Defines the property.
diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs
index 344b63d15c..da2fc75c0b 100644
--- a/src/Avalonia.Animation/Animation.cs
+++ b/src/Avalonia.Animation/Animation.cs
@@ -21,70 +21,81 @@ namespace Avalonia.Animation
///
public class Animation : AvaloniaList, IAnimation
{
- private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)>
- {
- ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
- };
-
- public static void RegisterAnimator(Func condition)
- where TAnimator : IAnimator
- {
- Animators.Insert(0, (condition, typeof(TAnimator)));
- }
-
- private static Type GetAnimatorType(AvaloniaProperty property)
- {
- foreach (var (condition, type) in Animators)
- {
- if (condition(property))
- {
- return type;
- }
- }
- return null;
- }
public AvaloniaList _animators { get; set; } = new AvaloniaList();
///
- /// Run time of this animation.
+ /// Gets or sets the active 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.
+ /// Gets or sets the repeat count for this animation.
///
public RepeatCount RepeatCount { get; set; }
///
- /// The playback direction for this animation.
+ /// Gets or sets the playback direction for this animation.
///
public PlaybackDirection PlaybackDirection { get; set; }
///
- /// The value fill mode for this animation.
+ /// Gets or sets the value fill mode for this animation.
///
public FillMode FillMode { get; set; }
///
- /// Easing function to be used.
+ /// Gets or sets the easing function to be used for this animation.
///
public Easing Easing { get; set; } = new LinearEasing();
-
+
///
- /// Sets the speed multiple for this animation.
+ /// Gets or sets the speed multiple for this animation.
///
public double SpeedRatio { get; set; } = 1d;
- ///
- /// Sets the behavior for having a delay between repeats for this animation.
- ///
- public bool DelayBetweenRepeats { get; set; }
+ ///
+ /// 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) )
+ };
+
+ public static void RegisterAnimator(Func condition)
+ where TAnimator : IAnimator
+ {
+ Animators.Insert(0, (condition, typeof(TAnimator)));
+ }
+
+ private static Type GetAnimatorType(AvaloniaProperty property)
+ {
+ foreach (var (condition, type) in Animators)
+ {
+ if (condition(property))
+ {
+ return type;
+ }
+ }
+ return null;
+ }
private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable control)
{
diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs
index 38ccce6cb5..47bfa1c321 100644
--- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs
+++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Animation
/// Provides statefulness for an iteration of a keyframe animation.
///
internal class AnimatorStateMachine : IObservable, IDisposable
- {
+ {
T lastInterpValue;
T firstKFValue;
@@ -34,6 +34,11 @@ namespace Avalonia.Animation
internal bool unsubscribe;
private bool isDisposed;
+ private long? internalClock;
+
+ private long? previousClock = null;
+ private long currentDiscreteTime;
+
private Easings.Easing EaseFunc;
private IObserver targetObserver;
private readonly Action onComplete;
@@ -55,7 +60,7 @@ namespace Avalonia.Animation
speedRatio = animation.SpeedRatio;
delayFC = ((animation.Delay.Ticks / Timing.FrameTick.Ticks) * speedRatio);
durationFC = ((animation.Duration.Ticks / Timing.FrameTick.Ticks) * speedRatio);
- delayBetweenIterations = animation.DelayBetweenRepeats;
+ delayBetweenIterations = animation.DelayBetweenIterations;
switch (animation.RepeatCount.RepeatType)
{
@@ -72,15 +77,15 @@ namespace Avalonia.Animation
}
animationDirection = animation.PlaybackDirection;
- fillMode = animation.FillMode;
+ fillMode = animation.FillMode;
this.onComplete = onComplete;
}
- public void Step(PlayState playState, long frameTick, Func Interpolator)
+ public void Step(long frameTick, Func Interpolator)
{
try
{
- InternalStep(playState, frameTick, Interpolator);
+ InternalStep(frameTick, Interpolator);
}
catch (Exception e)
{
@@ -107,8 +112,31 @@ namespace Avalonia.Animation
targetObserver.OnNext(lastInterpValue);
}
- private void InternalStep(PlayState playState, long frameTick, Func Interpolator)
+ private void InternalStep(long time, Func Interpolator)
{
+ if (Timing.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop)
+ DoComplete();
+
+ if (!previousClock.HasValue)
+ {
+ previousClock = time;
+ internalClock = 0;
+ }
+ else
+ {
+ if (Timing.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause)
+ {
+ previousClock = time;
+ return;
+ }
+ var delta = time - previousClock;
+ internalClock += delta;
+ previousClock = time;
+ }
+
+ // currentDiscreteTime = internalClock.Value;
+ currentDiscreteTime++;
+
if (!gotFirstKFValue)
{
firstKFValue = (T)parent.First().Value;
@@ -117,42 +145,23 @@ namespace Avalonia.Animation
if (!gotFirstFrameCount)
{
- firstFrameCount = frameTick;
+ firstFrameCount = currentDiscreteTime;
gotFirstFrameCount = true;
}
if (isDisposed)
throw new InvalidProgramException("This KeyFrames Animation is already disposed.");
- if (playState == PlayState.Stop)
- DoComplete();
-
- // Save state and pause the machine
- // if (playState == PlayState.Pause && currentState != KeyFramesStates.Pause)
- // {
- // savedState = currentState;
- // currentState = KeyFramesStates.Pause;
- // }
-
- // // Resume the previous state
- // if (playState != PlayState.Pause && currentState == KeyFramesStates.Pause)
- // currentState = savedState;
-
// get the time with the initial fc as point of origin.
- double t = (frameTick - firstFrameCount);
+ double t = (currentDiscreteTime - firstFrameCount);
// check if t is within the zeroth iteration
- if (t <= (delayFC + durationFC))
- {
- currentIteration = 0;
- t = t % (delayFC + durationFC + 1);
- }
- else
- {
- var totalDur = (double)((delayBetweenIterations ? delayFC : 0) + durationFC + 1);
- currentIteration = Math.Floor(t / totalDur);
- t = t % totalDur;
- }
+
+ double delayEndpoint = delayFC;
+ double iterationEndpoint = delayEndpoint + durationFC;
+
+ currentIteration = Math.Floor(t / iterationEndpoint);
+ t = t % iterationEndpoint;
// check if it's over the repeat count
if (currentIteration > (repeatCount - 1) && !isLooping)
@@ -166,19 +175,17 @@ namespace Avalonia.Animation
animationDirection == PlaybackDirection.AlternateReverse ? (currentIteration % 2 == 0) ? true : false :
animationDirection == PlaybackDirection.Reverse ? true : false;
- double delayEndpoint = delayFC;
- double iterationEndpoint = delayEndpoint + durationFC;
- if (delayFC > 0 & t >= 0 & t <= delayEndpoint)
+ if (delayFC > 0 & t <= delayEndpoint)
{
- if (currentIteration == 0 || delayBetweenIterations)
+ if (currentIteration == 0)
DoDelay();
-
}
- else if (t >= delayEndpoint & t <= iterationEndpoint)
+ else if (t > delayEndpoint & t < iterationEndpoint)
{
- var interpVal = t / durationFC;
-
+ double k = t - delayFC;
+ var interpVal = k / (double)durationFC;
+
if (isCurIterReverse)
interpVal = 1 - interpVal;
@@ -187,7 +194,7 @@ namespace Avalonia.Animation
lastInterpValue = Interpolator(easedTime, neutralValue);
targetObserver.OnNext(lastInterpValue);
}
- else if (t > iterationEndpoint & (currentIteration + 1 > repeatCount & !isLooping))
+ else if (t > iterationEndpoint && !isLooping)
{
DoComplete();
}
@@ -198,7 +205,6 @@ namespace Avalonia.Animation
targetObserver = observer;
return this;
}
-
public void Dispose()
{
unsubscribe = true;
diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs
index 607ccee947..a8b5ce7a27 100644
--- a/src/Avalonia.Animation/Animator`1.cs
+++ b/src/Avalonia.Animation/Animator`1.cs
@@ -21,7 +21,7 @@ namespace Avalonia.Animation
///
private readonly SortedList _convertedKeyframes = new SortedList();
- private bool _isVerfifiedAndConverted;
+ private bool isVerfifiedAndConverted;
///
/// Gets or sets the target property for the keyframe.
@@ -31,18 +31,17 @@ namespace Avalonia.Animation
public Animator()
{
// Invalidate keyframes when changed.
- this.CollectionChanged += delegate { _isVerfifiedAndConverted = false; };
+ this.CollectionChanged += delegate { isVerfifiedAndConverted = false; };
}
///
- public virtual IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete)
+ public virtual IDisposable Apply(Animation animation, Animatable control, IObservable Match, Action onComplete)
{
- if (!_isVerfifiedAndConverted)
+ if (!isVerfifiedAndConverted)
VerifyConvertKeyFrames();
- return obsMatch
- // Ignore triggers when global timers are paused.
- .Where(p => p && Timing.GetGlobalPlayState() != PlayState.Pause)
+ return Match
+ .Where(p => p)
.Subscribe(_ =>
{
var timerObs = RunKeyFrames(animation, control, onComplete);
@@ -101,9 +100,9 @@ namespace Avalonia.Animation
{
var stateMachine = new AnimatorStateMachine(animation, control, this, onComplete);
- Timing.AnimationStateTimer
+ Timing.AnimationsTimer
.TakeWhile(_ => !stateMachine.unsubscribe)
- .Subscribe(p => stateMachine.Step(p.Item1, p.Item2, DoInterpolation));
+ .Subscribe(p => stateMachine.Step(p, DoInterpolation));
return control.Bind((AvaloniaProperty)Property, stateMachine, BindingPriority.Animation);
}
@@ -124,7 +123,7 @@ namespace Avalonia.Animation
}
AddNeutralKeyFramesIfNeeded();
- _isVerfifiedAndConverted = true;
+ isVerfifiedAndConverted = true;
}
@@ -133,7 +132,7 @@ namespace Avalonia.Animation
bool hasStartKey, hasEndKey;
hasStartKey = hasEndKey = false;
- // Make start and end keyframe mandatory.
+ // Check if there's start and end keyframes.
foreach (var converted in _convertedKeyframes.Keys)
{
if (DoubleUtils.AboutEqual(converted, 0.0))
diff --git a/src/Avalonia.Animation/Timing.cs b/src/Avalonia.Animation/Timing.cs
index fb61e15f05..c6def06b15 100644
--- a/src/Avalonia.Animation/Timing.cs
+++ b/src/Avalonia.Animation/Timing.cs
@@ -16,16 +16,18 @@ namespace Avalonia.Animation
public static class Timing
{
static long _tickStartTimeStamp;
- static PlayState _globalState = PlayState.Run;
static long TicksPerFrame = Stopwatch.Frequency / FramesPerSecond;
+ ///
+ /// Gets or sets the animation play state for all animations
+ ///
+ public static PlayState GlobalPlayState { get; set; } = PlayState.Run;
///
/// The number of frames per second.
///
public const int FramesPerSecond = 60;
-
///
/// The time span of each frame.
///
@@ -36,45 +38,20 @@ namespace Avalonia.Animation
///
static Timing()
{
-
_tickStartTimeStamp = Stopwatch.GetTimestamp();
var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance);
-
- AnimationStateTimer = globalTimer
+ AnimationsTimer = globalTimer
.Select(_ =>
{
- return (_globalState, (Stopwatch.GetTimestamp() - _tickStartTimeStamp)
- / TicksPerFrame);
+ return (Stopwatch.GetTimestamp() - _tickStartTimeStamp)
+ / TicksPerFrame * 2;
})
.Publish()
.RefCount();
-
- TransitionsTimer = globalTimer
- .Select(p => p)
- .Publish()
- .RefCount();
- }
-
-
- ///
- /// Sets the animation play state for all animations
- ///
- public static void SetGlobalPlayState(PlayState playState)
- {
- Dispatcher.UIThread.VerifyAccess();
- _globalState = playState;
}
- ///
- /// Gets the animation play state for all animations
- ///
- public static PlayState GetGlobalPlayState()
- {
- Dispatcher.UIThread.VerifyAccess();
- return _globalState;
- }
///
/// Gets the animation timer.
@@ -84,21 +61,7 @@ namespace Avalonia.Animation
/// defined in .
/// The parameter passed to a subsciber is the current playstate of the animation.
///
- internal static IObservable<(PlayState, long)> AnimationStateTimer
- {
- get;
- }
-
- ///
- /// Gets the transitions timer.
- ///
- ///
- /// The transitions timer increments usually 60 times per second as
- /// defined in .
- /// The parameter passed to a subsciber is the number of frames since the animation system was
- /// initialized.
- ///
- public static IObservable TransitionsTimer
+ internal static IObservable AnimationsTimer
{
get;
}
@@ -116,10 +79,12 @@ namespace Avalonia.Animation
///
public static IObservable GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan))
{
+ // TODO: Fix this mess.
var _duration = (duration.Ticks / FrameTick.Ticks);
- var endTime = _duration;
+ long? endTime = ((Stopwatch.GetTimestamp() - _tickStartTimeStamp)
+ / TicksPerFrame) + _duration;
- return TransitionsTimer
+ return AnimationsTimer
.TakeWhile(x => x < endTime)
.Select(x => (double)x / _duration)
.StartWith(0.0)