64 changed files with 780 additions and 693 deletions
@ -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 |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Handles interpolatoin and time-related functions
|
||||
|
/// for keyframe animations.
|
||||
|
/// </summary>
|
||||
|
internal class AnimationInstance<T> : SingleSubscriberObservableBase<T> |
||||
|
{ |
||||
|
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 AnimationInstance(Animation animation, Animatable control, Animator<T> animator, Action OnComplete, Func<double, T, T> 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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,273 +0,0 @@ |
|||||
using System; |
|
||||
using System.Linq; |
|
||||
using Avalonia.Data; |
|
||||
|
|
||||
namespace Avalonia.Animation |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides statefulness for an iteration of a keyframe animation.
|
|
||||
/// </summary>
|
|
||||
internal class AnimatorStateMachine<T> : IObservable<object>, IDisposable |
|
||||
{ |
|
||||
object _lastInterpValue; |
|
||||
object _firstKFValue; |
|
||||
|
|
||||
private ulong _delayTotalFrameCount; |
|
||||
private ulong _durationTotalFrameCount; |
|
||||
private ulong _delayFrameCount; |
|
||||
private ulong _durationFrameCount; |
|
||||
private ulong _repeatCount; |
|
||||
private ulong _currentIteration; |
|
||||
|
|
||||
private bool _isLooping; |
|
||||
private bool _isRepeating; |
|
||||
private bool _isReversed; |
|
||||
private bool _checkLoopAndRepeat; |
|
||||
private bool _gotFirstKFValue; |
|
||||
|
|
||||
private FillMode _fillMode; |
|
||||
private PlaybackDirection _animationDirection; |
|
||||
private KeyFramesStates _currentState; |
|
||||
private KeyFramesStates _savedState; |
|
||||
private Animator<T> _parent; |
|
||||
private Animation _targetAnimation; |
|
||||
private Animatable _targetControl; |
|
||||
private T _neutralValue; |
|
||||
internal bool _unsubscribe = false; |
|
||||
private IObserver<object> _targetObserver; |
|
||||
private readonly Action _onComplete; |
|
||||
|
|
||||
[Flags] |
|
||||
private enum KeyFramesStates |
|
||||
{ |
|
||||
Initialize, |
|
||||
DoDelay, |
|
||||
DoRun, |
|
||||
RunForwards, |
|
||||
RunBackwards, |
|
||||
RunApplyValue, |
|
||||
RunComplete, |
|
||||
Pause, |
|
||||
Stop, |
|
||||
Disposed |
|
||||
} |
|
||||
|
|
||||
public AnimatorStateMachine(Animation animation, Animatable control, Animator<T> animator, Action onComplete) |
|
||||
{ |
|
||||
_parent = animator; |
|
||||
_targetAnimation = animation; |
|
||||
_targetControl = control; |
|
||||
_neutralValue = (T)_targetControl.GetValue(_parent.Property); |
|
||||
|
|
||||
_delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks); |
|
||||
_durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks); |
|
||||
|
|
||||
switch (animation.RepeatCount.RepeatType) |
|
||||
{ |
|
||||
case RepeatType.Loop: |
|
||||
_isLooping = true; |
|
||||
_checkLoopAndRepeat = true; |
|
||||
break; |
|
||||
case RepeatType.Repeat: |
|
||||
_isRepeating = true; |
|
||||
_checkLoopAndRepeat = true; |
|
||||
_repeatCount = animation.RepeatCount.Value; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
_isReversed = (animation.PlaybackDirection & PlaybackDirection.Reverse) != 0; |
|
||||
_animationDirection = _targetAnimation.PlaybackDirection; |
|
||||
_fillMode = _targetAnimation.FillMode; |
|
||||
|
|
||||
if (_durationTotalFrameCount > 0) |
|
||||
_currentState = KeyFramesStates.DoDelay; |
|
||||
else |
|
||||
_currentState = KeyFramesStates.DoRun; |
|
||||
_onComplete = onComplete; |
|
||||
} |
|
||||
|
|
||||
public void Step(PlayState _playState, Func<double, T, T> Interpolator) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
InternalStep(_playState, Interpolator); |
|
||||
} |
|
||||
catch (Exception e) |
|
||||
{ |
|
||||
_targetObserver?.OnError(e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void InternalStep(PlayState _playState, Func<double, T, T> Interpolator) |
|
||||
{ |
|
||||
if (!_gotFirstKFValue) |
|
||||
{ |
|
||||
_firstKFValue = _parent.First().Value; |
|
||||
_gotFirstKFValue = true; |
|
||||
} |
|
||||
|
|
||||
if (_currentState == KeyFramesStates.Disposed) |
|
||||
throw new InvalidProgramException("This KeyFrames Animation is already disposed."); |
|
||||
|
|
||||
if (_playState == PlayState.Stop) |
|
||||
_currentState = KeyFramesStates.Stop; |
|
||||
|
|
||||
// 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; |
|
||||
|
|
||||
double _tempDuration = 0d, _easedTime; |
|
||||
|
|
||||
bool handled = false; |
|
||||
|
|
||||
while (!handled) |
|
||||
{ |
|
||||
switch (_currentState) |
|
||||
{ |
|
||||
case KeyFramesStates.DoDelay: |
|
||||
|
|
||||
if (_fillMode == FillMode.Backward |
|
||||
|| _fillMode == FillMode.Both) |
|
||||
{ |
|
||||
if (_currentIteration == 0) |
|
||||
{ |
|
||||
_targetObserver.OnNext(_firstKFValue); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
_targetObserver.OnNext(_lastInterpValue); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (_delayFrameCount > _delayTotalFrameCount) |
|
||||
{ |
|
||||
_currentState = KeyFramesStates.DoRun; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
handled = true; |
|
||||
_delayFrameCount++; |
|
||||
} |
|
||||
break; |
|
||||
|
|
||||
case KeyFramesStates.DoRun: |
|
||||
|
|
||||
if (_isReversed) |
|
||||
_currentState = KeyFramesStates.RunBackwards; |
|
||||
else |
|
||||
_currentState = KeyFramesStates.RunForwards; |
|
||||
|
|
||||
break; |
|
||||
|
|
||||
case KeyFramesStates.RunForwards: |
|
||||
|
|
||||
if (_durationFrameCount > _durationTotalFrameCount) |
|
||||
{ |
|
||||
_currentState = KeyFramesStates.RunComplete; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
_tempDuration = (double)_durationFrameCount / _durationTotalFrameCount; |
|
||||
_currentState = KeyFramesStates.RunApplyValue; |
|
||||
|
|
||||
} |
|
||||
break; |
|
||||
|
|
||||
case KeyFramesStates.RunBackwards: |
|
||||
|
|
||||
if (_durationFrameCount > _durationTotalFrameCount) |
|
||||
{ |
|
||||
_currentState = KeyFramesStates.RunComplete; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
_tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount; |
|
||||
_currentState = KeyFramesStates.RunApplyValue; |
|
||||
} |
|
||||
break; |
|
||||
|
|
||||
case KeyFramesStates.RunApplyValue: |
|
||||
|
|
||||
_easedTime = _targetAnimation.Easing.Ease(_tempDuration); |
|
||||
|
|
||||
_durationFrameCount++; |
|
||||
_lastInterpValue = Interpolator(_easedTime, _neutralValue); |
|
||||
_targetObserver.OnNext(_lastInterpValue); |
|
||||
_currentState = KeyFramesStates.DoRun; |
|
||||
handled = true; |
|
||||
break; |
|
||||
|
|
||||
case KeyFramesStates.RunComplete: |
|
||||
|
|
||||
if (_checkLoopAndRepeat) |
|
||||
{ |
|
||||
_delayFrameCount = 0; |
|
||||
_durationFrameCount = 0; |
|
||||
|
|
||||
if (_isLooping) |
|
||||
{ |
|
||||
_currentState = KeyFramesStates.DoRun; |
|
||||
} |
|
||||
else if (_isRepeating) |
|
||||
{ |
|
||||
if (_currentIteration >= _repeatCount) |
|
||||
{ |
|
||||
_currentState = KeyFramesStates.Stop; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
_currentState = KeyFramesStates.DoRun; |
|
||||
} |
|
||||
_currentIteration++; |
|
||||
} |
|
||||
|
|
||||
if (_animationDirection == PlaybackDirection.Alternate |
|
||||
|| _animationDirection == PlaybackDirection.AlternateReverse) |
|
||||
_isReversed = !_isReversed; |
|
||||
|
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
_currentState = KeyFramesStates.Stop; |
|
||||
break; |
|
||||
|
|
||||
case KeyFramesStates.Stop: |
|
||||
|
|
||||
if (_fillMode == FillMode.Forward |
|
||||
|| _fillMode == FillMode.Both) |
|
||||
{ |
|
||||
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue); |
|
||||
} |
|
||||
|
|
||||
_targetObserver.OnCompleted(); |
|
||||
_onComplete?.Invoke(); |
|
||||
Dispose(); |
|
||||
handled = true; |
|
||||
break; |
|
||||
default: |
|
||||
handled = true; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public IDisposable Subscribe(IObserver<object> observer) |
|
||||
{ |
|
||||
_targetObserver = observer; |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
_unsubscribe = true; |
|
||||
_currentState = KeyFramesStates.Disposed; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,54 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using Avalonia.Metadata; |
||||
|
using System; |
||||
|
using System.Reactive.Linq; |
||||
|
using Avalonia.Animation.Easings; |
||||
|
using Avalonia.Animation.Utils; |
||||
|
using Avalonia.Reactive; |
||||
|
|
||||
|
namespace Avalonia.Animation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Handles the timing and lifetime of a <see cref="Transition{T}"/>.
|
||||
|
/// </summary>
|
||||
|
internal class TransitionInstance : SingleSubscriberObservableBase<double> |
||||
|
{ |
||||
|
private IDisposable timerSubscription; |
||||
|
private TimeSpan startTime; |
||||
|
private TimeSpan duration; |
||||
|
|
||||
|
public TransitionInstance(TimeSpan Duration) |
||||
|
{ |
||||
|
duration = Duration; |
||||
|
} |
||||
|
|
||||
|
private void TimerTick(TimeSpan t) |
||||
|
{ |
||||
|
var interpVal = (double)(t.Ticks - startTime.Ticks) / duration.Ticks; |
||||
|
|
||||
|
if (interpVal > 1d |
||||
|
|| interpVal < 0d) |
||||
|
{ |
||||
|
PublishCompleted(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
PublishNext(interpVal); |
||||
|
} |
||||
|
|
||||
|
protected override void Unsubscribed() |
||||
|
{ |
||||
|
timerSubscription?.Dispose(); |
||||
|
} |
||||
|
|
||||
|
protected override void Subscribed() |
||||
|
{ |
||||
|
startTime = Timing.GetTickCount(); |
||||
|
timerSubscription = Timing.AnimationsTimer |
||||
|
.Subscribe(t => TimerTick(t)); |
||||
|
PublishNext(0.0d); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,16 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
|
|
||||
namespace Avalonia.Animation.Utils |
|
||||
{ |
|
||||
internal static class DoubleUtils |
|
||||
{ |
|
||||
internal static bool AboutEqual(double x, double y) |
|
||||
{ |
|
||||
double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15; |
|
||||
return Math.Abs(x - y) <= epsilon; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 834 B |
Loading…
Reference in new issue