|
|
|
@ -9,7 +9,7 @@ namespace Avalonia.Animation |
|
|
|
/// Provides statefulness for an iteration of a keyframe animation.
|
|
|
|
/// </summary>
|
|
|
|
internal class AnimatorStateMachine<T> : IObservable<T>, 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<T> 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<double, T, T> Interpolator) |
|
|
|
public void Step(long frameTick, Func<double, T, T> 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<double, T, T> Interpolator) |
|
|
|
private void InternalStep(long time, Func<double, T, T> 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; |
|
|
|
|