|
|
|
@ -1,7 +1,9 @@ |
|
|
|
using System; |
|
|
|
using System.Linq; |
|
|
|
using System.Reactive.Linq; |
|
|
|
using Avalonia.Animation.Utils; |
|
|
|
using Avalonia.Data; |
|
|
|
using Avalonia.Reactive; |
|
|
|
|
|
|
|
namespace Avalonia.Animation |
|
|
|
{ |
|
|
|
@ -9,182 +11,184 @@ namespace Avalonia.Animation |
|
|
|
/// Handles interpolatoin and time-related functions
|
|
|
|
/// for keyframe animations.
|
|
|
|
/// </summary>
|
|
|
|
internal class AnimationsEngine<T> : IObservable<T>, IDisposable |
|
|
|
internal class AnimationsEngine<T> : SingleSubscriberObservableBase<T> |
|
|
|
{ |
|
|
|
T lastInterpValue; |
|
|
|
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<T> parent; |
|
|
|
private Animatable targetControl; |
|
|
|
private T neutralValue; |
|
|
|
private double speedRatio; |
|
|
|
internal bool unsubscribe; |
|
|
|
private bool isDisposed; |
|
|
|
|
|
|
|
private TimeSpan delay; |
|
|
|
private TimeSpan duration; |
|
|
|
private TimeSpan firstFrameCount; |
|
|
|
private TimeSpan internalClock; |
|
|
|
private TimeSpan? previousClock; |
|
|
|
|
|
|
|
private Easings.Easing easeFunc; |
|
|
|
private IObserver<T> targetObserver; |
|
|
|
private readonly Action onCompleteAction; |
|
|
|
|
|
|
|
public AnimationsEngine(Animation animation, Animatable control, Animator<T> animator, Action OnComplete) |
|
|
|
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<T> _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<double, T, T> _interpolator; |
|
|
|
private IDisposable _timerSubscription; |
|
|
|
|
|
|
|
public AnimationsEngine(Animation animation, Animatable control, Animator<T> animator, Action OnComplete, Func<double, T, T> Interpolator) |
|
|
|
{ |
|
|
|
if (animation.SpeedRatio <= 0 || DoubleUtils.AboutEqual(animation.SpeedRatio, 0)) |
|
|
|
throw new InvalidOperationException("Speed ratio cannot be negative or zero."); |
|
|
|
|
|
|
|
if (animation.Duration.TotalSeconds <= 0 || DoubleUtils.AboutEqual(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; |
|
|
|
_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; |
|
|
|
_delay = animation.Delay; |
|
|
|
_duration = animation.Duration; |
|
|
|
_iterationDelay = animation.DelayBetweenIterations; |
|
|
|
|
|
|
|
switch (animation.RepeatCount.RepeatType) |
|
|
|
{ |
|
|
|
case RepeatType.None: |
|
|
|
repeatCount = 1; |
|
|
|
_repeatCount = 1; |
|
|
|
break; |
|
|
|
case RepeatType.Loop: |
|
|
|
isLooping = true; |
|
|
|
_isLooping = true; |
|
|
|
break; |
|
|
|
case RepeatType.Repeat: |
|
|
|
repeatCount = (long)animation.RepeatCount.Value; |
|
|
|
_repeatCount = (long)animation.RepeatCount.Value; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
animationDirection = animation.PlaybackDirection; |
|
|
|
fillMode = animation.FillMode; |
|
|
|
onCompleteAction = OnComplete; |
|
|
|
_animationDirection = animation.PlaybackDirection; |
|
|
|
_fillMode = animation.FillMode; |
|
|
|
_onCompleteAction = OnComplete; |
|
|
|
_interpolator = Interpolator; |
|
|
|
} |
|
|
|
|
|
|
|
protected override void Unsubscribed() |
|
|
|
{ |
|
|
|
_timerSubscription?.Dispose(); |
|
|
|
} |
|
|
|
|
|
|
|
public void Step(TimeSpan frameTick, Func<double, T, T> Interpolator) |
|
|
|
protected override void Subscribed() |
|
|
|
{ |
|
|
|
_timerSubscription = Timing.AnimationsTimer |
|
|
|
.Subscribe(p => this.Step(p)); |
|
|
|
} |
|
|
|
|
|
|
|
public void Step(TimeSpan frameTick) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
InternalStep(frameTick, Interpolator); |
|
|
|
InternalStep(frameTick); |
|
|
|
} |
|
|
|
catch (Exception e) |
|
|
|
{ |
|
|
|
targetObserver?.OnError(e); |
|
|
|
PublishError(e); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void DoComplete() |
|
|
|
{ |
|
|
|
if (fillMode == FillMode.Forward || fillMode == FillMode.Both) |
|
|
|
targetControl.SetValue(parent.Property, lastInterpValue, BindingPriority.LocalValue); |
|
|
|
if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both) |
|
|
|
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue); |
|
|
|
|
|
|
|
targetObserver.OnCompleted(); |
|
|
|
onCompleteAction?.Invoke(); |
|
|
|
Dispose(); |
|
|
|
_onCompleteAction?.Invoke(); |
|
|
|
PublishCompleted(); |
|
|
|
} |
|
|
|
|
|
|
|
private void DoDelay() |
|
|
|
{ |
|
|
|
if (fillMode == FillMode.Backward || fillMode == FillMode.Both) |
|
|
|
if (currentIteration == 0) |
|
|
|
targetObserver.OnNext(firstKFValue); |
|
|
|
if (_fillMode == FillMode.Backward || _fillMode == FillMode.Both) |
|
|
|
if (_currentIteration == 0) |
|
|
|
PublishNext(_firstKFValue); |
|
|
|
else |
|
|
|
targetObserver.OnNext(lastInterpValue); |
|
|
|
PublishNext(_lastInterpValue); |
|
|
|
} |
|
|
|
|
|
|
|
private void DoPlayStatesAndTime(TimeSpan systemTime) |
|
|
|
{ |
|
|
|
if (Animation.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop) |
|
|
|
if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop) |
|
|
|
DoComplete(); |
|
|
|
|
|
|
|
if (!previousClock.HasValue) |
|
|
|
if (!_previousClock.HasValue) |
|
|
|
{ |
|
|
|
previousClock = systemTime; |
|
|
|
internalClock = TimeSpan.Zero; |
|
|
|
_previousClock = systemTime; |
|
|
|
_internalClock = TimeSpan.Zero; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
if (Animation.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause) |
|
|
|
if (Animation.GlobalPlayState == PlayState.Pause || _targetControl.PlayState == PlayState.Pause) |
|
|
|
{ |
|
|
|
previousClock = systemTime; |
|
|
|
_previousClock = systemTime; |
|
|
|
return; |
|
|
|
} |
|
|
|
var delta = systemTime - previousClock; |
|
|
|
internalClock += delta.Value; |
|
|
|
previousClock = systemTime; |
|
|
|
var delta = systemTime - _previousClock; |
|
|
|
_internalClock += delta.Value; |
|
|
|
_previousClock = systemTime; |
|
|
|
} |
|
|
|
|
|
|
|
if (!gotFirstKFValue) |
|
|
|
if (!_gotFirstKFValue) |
|
|
|
{ |
|
|
|
firstKFValue = (T)parent.First().Value; |
|
|
|
gotFirstKFValue = true; |
|
|
|
_firstKFValue = (T)_parent.First().Value; |
|
|
|
_gotFirstKFValue = true; |
|
|
|
} |
|
|
|
|
|
|
|
if (!gotFirstFrameCount) |
|
|
|
if (!_gotFirstFrameCount) |
|
|
|
{ |
|
|
|
firstFrameCount = internalClock; |
|
|
|
gotFirstFrameCount = true; |
|
|
|
_firstFrameCount = _internalClock; |
|
|
|
_gotFirstFrameCount = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void InternalStep(TimeSpan systemTime, Func<double, T, T> Interpolator) |
|
|
|
private void InternalStep(TimeSpan systemTime) |
|
|
|
{ |
|
|
|
DoPlayStatesAndTime(systemTime); |
|
|
|
|
|
|
|
if (isDisposed) |
|
|
|
throw new InvalidProgramException("This KeyFrames Animation is already disposed."); |
|
|
|
|
|
|
|
var time = internalClock - firstFrameCount; |
|
|
|
var delayEndpoint = delay; |
|
|
|
var iterationEndpoint = delayEndpoint + duration; |
|
|
|
|
|
|
|
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; |
|
|
|
_currentIteration = 1; |
|
|
|
} |
|
|
|
else if (time > iterationEndpoint) |
|
|
|
{ |
|
|
|
//Subtract first iteration to properly get the subsequent iteration time
|
|
|
|
time -= iterationEndpoint; |
|
|
|
|
|
|
|
if (!iterationDelay & delayEndpoint > TimeSpan.Zero) |
|
|
|
if (!_iterationDelay & delayEndpoint > TimeSpan.Zero) |
|
|
|
{ |
|
|
|
delayEndpoint = TimeSpan.Zero; |
|
|
|
iterationEndpoint = duration; |
|
|
|
iterationEndpoint = _duration; |
|
|
|
} |
|
|
|
|
|
|
|
//Calculate the current iteration number
|
|
|
|
currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2; |
|
|
|
_currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
previousClock = systemTime; |
|
|
|
_previousClock = systemTime; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
time = TimeSpan.FromTicks(time.Ticks % iterationEndpoint.Ticks); |
|
|
|
|
|
|
|
if (!isLooping) |
|
|
|
if (!_isLooping) |
|
|
|
{ |
|
|
|
if (currentIteration > repeatCount) |
|
|
|
if (_currentIteration > _repeatCount) |
|
|
|
DoComplete(); |
|
|
|
|
|
|
|
if (time > iterationEndpoint) |
|
|
|
@ -192,10 +196,10 @@ namespace Avalonia.Animation |
|
|
|
} |
|
|
|
|
|
|
|
// 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; |
|
|
|
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) |
|
|
|
{ |
|
|
|
@ -214,23 +218,11 @@ namespace Avalonia.Animation |
|
|
|
interpVal = 1 - interpVal; |
|
|
|
|
|
|
|
// Ease and interpolate
|
|
|
|
var easedTime = easeFunc.Ease(interpVal); |
|
|
|
lastInterpValue = Interpolator(easedTime, neutralValue); |
|
|
|
var easedTime = _easeFunc.Ease(interpVal); |
|
|
|
_lastInterpValue = _interpolator(easedTime, _neutralValue); |
|
|
|
|
|
|
|
targetObserver.OnNext(lastInterpValue); |
|
|
|
PublishNext(_lastInterpValue); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public IDisposable Subscribe(IObserver<T> observer) |
|
|
|
{ |
|
|
|
targetObserver = observer; |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
unsubscribe = true; |
|
|
|
isDisposed = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |