| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- using System;
- using System.Linq;
- using Avalonia.Data;
- namespace Avalonia.Animation.Keyframes
- {
- /// <summary>
- /// Provides statefulness for an iteration of a keyframe group animation.
- /// </summary>
- internal class KeyFramesStateMachine<T> : IObservable<object>, IDisposable
- {
- object _lastInterpValue;
- object _firstKFValue;
- private ulong _delayTotalFrameCount,
- _durationTotalFrameCount,
- _delayFrameCount,
- _durationFrameCount,
- _repeatCount,
- _currentIteration;
- private bool _isLooping,
- _isRepeating,
- _isReversed,
- _checkLoopAndRepeat,
- _gotFirstKFValue;
- private FillMode _fillMode;
- private PlaybackDirection _animationDirection;
- private KeyFramesStates _currentState, _savedState;
- private KeyFrames<T> _parent;
- private Animation _targetAnimation;
- private Animatable _targetControl;
- internal bool _unsubscribe = false;
- private IObserver<object> _targetObserver;
- private enum KeyFramesStates
- {
- INITIALIZE,
- DO_DELAY,
- DO_RUN,
- RUN_FORWARDS,
- RUN_BACKWARDS,
- RUN_APPLYVALUE,
- RUN_COMPLETE,
- PAUSE,
- STOP,
- DISPOSED
- }
- public void Initialize(Animation animation, Animatable control, KeyFrames<T> keyframes)
- {
- _parent = keyframes;
- _targetAnimation = animation;
- _targetControl = control;
- _delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks);
- _durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks);
- switch (animation.RepeatBehavior)
- {
- case RepeatBehavior.Loop:
- _isLooping = true;
- _checkLoopAndRepeat = true;
- break;
- case RepeatBehavior.Repeat:
- if (animation.RepeatCount != null)
- {
- if (animation.RepeatCount == 0)
- {
- throw new InvalidOperationException
- ($"RepeatCount should be greater than zero when RepeatBehavior is set to Repeat.");
- }
- _isRepeating = true;
- _checkLoopAndRepeat = true;
- _repeatCount = (ulong)animation.RepeatCount;
- }
- else
- {
- throw new InvalidOperationException
- ($"RepeatCount should be defined when RepeatBehavior is set to Repeat.");
- }
- break;
- }
- switch (animation.PlaybackDirection)
- {
- case PlaybackDirection.Reverse:
- case PlaybackDirection.AlternateReverse:
- _isReversed = true;
- break;
- default:
- _isReversed = false;
- break;
- }
- _animationDirection = _targetAnimation.PlaybackDirection;
- _fillMode = _targetAnimation.FillMode;
- if (_durationTotalFrameCount > 0)
- _currentState = KeyFramesStates.DO_DELAY;
- else
- _currentState = KeyFramesStates.DO_RUN;
- }
- public void Step(PlayState _playState, Func<double, T> Interpolator)
- {
- try
- {
- InternalStep(_playState, Interpolator);
- }
- catch (Exception e)
- {
- _targetObserver?.OnError(e);
- }
- }
- private void InternalStep(PlayState _playState, Func<double, 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;
- checkstate:
- switch (_currentState)
- {
- case KeyFramesStates.DO_DELAY:
- if (_fillMode == FillMode.Backward
- || _fillMode == FillMode.Both)
- {
- if (_currentIteration == 0)
- {
- _targetObserver.OnNext(_firstKFValue);
- }
- else
- {
- _targetObserver.OnNext(_lastInterpValue);
- }
- }
- if (_delayFrameCount > _delayTotalFrameCount)
- {
- _currentState = KeyFramesStates.DO_RUN;
- goto checkstate;
- }
- _delayFrameCount++;
- break;
- case KeyFramesStates.DO_RUN:
- if (_isReversed)
- _currentState = KeyFramesStates.RUN_BACKWARDS;
- else
- _currentState = KeyFramesStates.RUN_FORWARDS;
- goto checkstate;
- case KeyFramesStates.RUN_FORWARDS:
- if (_durationFrameCount > _durationTotalFrameCount)
- {
- _currentState = KeyFramesStates.RUN_COMPLETE;
- goto checkstate;
- }
- _tempDuration = (double)_durationFrameCount / _durationTotalFrameCount;
- _currentState = KeyFramesStates.RUN_APPLYVALUE;
- goto checkstate;
- case KeyFramesStates.RUN_BACKWARDS:
- if (_durationFrameCount > _durationTotalFrameCount)
- {
- _currentState = KeyFramesStates.RUN_COMPLETE;
- goto checkstate;
- }
- _tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount;
- _currentState = KeyFramesStates.RUN_APPLYVALUE;
- goto checkstate;
- case KeyFramesStates.RUN_APPLYVALUE:
- _easedTime = _targetAnimation.Easing.Ease(_tempDuration);
- _durationFrameCount++;
- _lastInterpValue = Interpolator(_easedTime);
- _targetObserver.OnNext(_lastInterpValue);
- _currentState = KeyFramesStates.DO_RUN;
- break;
- case KeyFramesStates.RUN_COMPLETE:
- if (_checkLoopAndRepeat)
- {
- _delayFrameCount = 0;
- _durationFrameCount = 0;
- if (_isLooping)
- {
- _currentState = KeyFramesStates.DO_RUN;
- }
- else if (_isRepeating)
- {
- if (_currentIteration >= _repeatCount)
- {
- _currentState = KeyFramesStates.STOP;
- }
- else
- {
- _currentState = KeyFramesStates.DO_RUN;
- }
- _currentIteration++;
- }
- if (_animationDirection == PlaybackDirection.Alternate
- || _animationDirection == PlaybackDirection.AlternateReverse)
- _isReversed = !_isReversed;
- break;
- }
- _currentState = KeyFramesStates.STOP;
- goto checkstate;
- case KeyFramesStates.STOP:
- if (_fillMode == FillMode.Forward
- || _fillMode == FillMode.Both)
- {
- _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.Animation);
- }
- _targetObserver.OnCompleted();
- break;
- }
- }
- public IDisposable Subscribe(IObserver<object> observer)
- {
- _targetObserver = observer;
- return this;
- }
- public void Dispose()
- {
- _unsubscribe = true;
- _currentState = KeyFramesStates.DISPOSED;
- }
- }
- }
|