From 1acfc74fb1bfbaf35709ccda1c118afc237cc34a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 14 Apr 2018 21:53:53 +0800 Subject: [PATCH] Make KeyFrameStateMachine an IObservable. --- .../Keyframes/DoubleKeyFrames.cs | 24 +++-- src/Avalonia.Animation/Keyframes/KeyFrames.cs | 46 +++++----- .../Keyframes/KeyFramesStateMachine.cs | 90 +++++++++++-------- .../{Direction.cs => PlaybackDirection.cs} | 0 src/Avalonia.DotNetCoreRuntime/AppBuilder.cs | 4 +- .../Animation/Keyframes/TransformKeyFrames.cs | 4 +- 6 files changed, 89 insertions(+), 79 deletions(-) rename src/Avalonia.Animation/{Direction.cs => PlaybackDirection.cs} (100%) diff --git a/src/Avalonia.Animation/Keyframes/DoubleKeyFrames.cs b/src/Avalonia.Animation/Keyframes/DoubleKeyFrames.cs index 2239ac5253..601b277069 100644 --- a/src/Avalonia.Animation/Keyframes/DoubleKeyFrames.cs +++ b/src/Avalonia.Animation/Keyframes/DoubleKeyFrames.cs @@ -14,21 +14,19 @@ namespace Avalonia.Animation.Keyframes /// public class DoubleKeyFrames : KeyFrames { - /// - public override IObservable DoInterpolation(IObservable timer, Animation animation, Animatable control) - => timer.Select(t => - { - // Get a pair of keyframes to make the interpolation. - var pair = GetKeyFramePairByTime(t); - double y0 = pair.FirstKeyFrame.Value; - double t0 = pair.FirstKeyFrame.Key; - double y1 = pair.SecondKeyFrame.Value; - double t1 = pair.SecondKeyFrame.Key; + /// + public override double DoInterpolation(double t) + { + var pair = GetKeyFramePairByTime(t); - // Do linear parametric interpolation - return y0 + ((t - t0) / (t1 - t0)) * (y1 - y0); - }); + double y0 = pair.FirstKeyFrame.Value; + double t0 = pair.FirstKeyFrame.Key; + double y1 = pair.SecondKeyFrame.Value; + double t1 = pair.SecondKeyFrame.Key; + // Do linear parametric interpolation + return y0 + ((t - t0) / (t1 - t0)) * (y1 - y0); + } } } diff --git a/src/Avalonia.Animation/Keyframes/KeyFrames.cs b/src/Avalonia.Animation/Keyframes/KeyFrames.cs index 2e4ed78c11..3e3973f7c7 100644 --- a/src/Avalonia.Animation/Keyframes/KeyFrames.cs +++ b/src/Avalonia.Animation/Keyframes/KeyFrames.cs @@ -39,14 +39,10 @@ namespace Avalonia.Animation.Keyframes return obsMatch .Where(p => p == true) // Ignore triggers when global timers are paused. - .Where(p => Timing.GetGlobalPlayState() == PlayState.Run) + .Where(p => Timing.GetGlobalPlayState() != PlayState.Pause) .Subscribe(_ => { - var timerObs = SetupAnimation(animation, control); - var interp = DoInterpolation(timerObs, animation, control) - .Select(p => (object)p); - - control.Bind(Property, interp, BindingPriority.Animation); + var timerObs = RunKeyFrames(animation, control); }); } @@ -86,33 +82,35 @@ namespace Avalonia.Animation.Keyframes /// - /// Returns an observable timer with the specific Animation - /// duration and delay and applies the Animation's easing function. + /// Runs the KeyFrames Animation. /// - public IObservable SetupAnimation(Animation animation, Animatable control) + public IDisposable RunKeyFrames(Animation animation, Animatable control) { - var newStateMachine = new KeyFramesStateMachine(); - newStateMachine.Start(animation, control); - var playStateObs = Timing.AnimationStateTimer - .TakeWhile(p => !newStateMachine._unsubscribe) - .Select(p => - { - return newStateMachine.Step(p); - }); - // var newTimer = Timing.GetAnimationsTimer(animation, control, animation.Duration, animation.Delay); - return playStateObs.Select(t => animation.Easing.Ease(t)); - } + var _kfStateMach = new KeyFramesStateMachine(); + _kfStateMach.Start(animation); + + Timing.AnimationStateTimer + .TakeWhile(_ => !_kfStateMach._unsubscribe) + .Subscribe(p => + { + _kfStateMach.Step(p, DoInterpolation); + }); + return control.Bind(Property, _kfStateMach, BindingPriority.Animation); + } /// - /// Interpolates the given keyframes to the control. + /// Interpolates a value given the desired time. /// - public abstract IObservable DoInterpolation(IObservable timer, Animation animation, - Animatable control); + public abstract T DoInterpolation(double time); + + + // public abstract IObservable DoInterpolation(IObservable timer, Animation animation, + // Animatable control); /// - /// Verifies and converts keyframe values according to this class type parameter. + /// Verifies and converts keyframe values according to this class's type parameter. /// private void VerifyConvertKeyFrames(Animation animation, Type type) { diff --git a/src/Avalonia.Animation/Keyframes/KeyFramesStateMachine.cs b/src/Avalonia.Animation/Keyframes/KeyFramesStateMachine.cs index 0b1bb26d9b..454974ec02 100644 --- a/src/Avalonia.Animation/Keyframes/KeyFramesStateMachine.cs +++ b/src/Avalonia.Animation/Keyframes/KeyFramesStateMachine.cs @@ -5,9 +5,8 @@ namespace Avalonia.Animation.Keyframes /// /// Provides statefulness for an iteration of a keyframe group animation. /// - internal class KeyFramesStateMachine + internal class KeyFramesStateMachine : IObservable, IDisposable { - ulong _delayTotalFrameCount, _durationTotalFrameCount, _delayFrameCount, @@ -18,11 +17,11 @@ namespace Avalonia.Animation.Keyframes bool _isLooping, _isRepeating, _isReversed; private PlaybackDirection _animationDirection; - KeyFramesStates _currentState; - + KeyFramesStates _currentState, _savedState; + private Animation parentAnimation; internal bool _unsubscribe = false; - private Animation _parentAnimation; - private Animatable _targetAnimatable; + double _outputTime = 0d; + private IObserver targetObserver; private enum KeyFramesStates { @@ -32,21 +31,14 @@ namespace Avalonia.Animation.Keyframes RUN_FORWARDS, RUN_BACKWARDS, RUN_COMPLETE, - STOP + PAUSE, + STOP, + DISPOSED } - public void Start(Animation animation, Animatable control) + public void Start(Animation animation) { - this._parentAnimation = animation; - this._targetAnimatable = control; - - // int _delayFrameCount, - // _durationFrameCount, - // _repeatCount, - // _iterationDirection, - // _currentIteration, - // _totalIteration; - + parentAnimation = animation; _delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks); _durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks); @@ -78,37 +70,39 @@ namespace Avalonia.Animation.Keyframes } break; } - _animationDirection = animation.PlaybackDirection; - - switch (_animationDirection) + + switch (animation.PlaybackDirection) { case PlaybackDirection.Reverse: case PlaybackDirection.AlternateReverse: - SetInitialPlaybackDirection(true); + _isReversed = true; break; default: - SetInitialPlaybackDirection(false); + _isReversed = false; break; } _currentState = KeyFramesStates.DO_RUN; } - private void SetInitialPlaybackDirection(bool isReversed) - { - _isReversed = isReversed; - } - - private bool GetPlaybackDirection() + public double Step(PlayState _playState, Func Interpolator) { - _isReversed = !_isReversed; - return _isReversed; - } + if (_currentState == KeyFramesStates.DISPOSED) throw new InvalidProgramException("This KeyFrames Animation is already disposed."); - public double Step(PlayState _playState) - { 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; + + checkstate: switch (_currentState) { case KeyFramesStates.DO_DELAY: @@ -120,11 +114,22 @@ namespace Avalonia.Animation.Keyframes return 0d; case KeyFramesStates.DO_RUN: + // temporary stuff. + _currentState = KeyFramesStates.RUN_FORWARDS; + + goto checkstate; - break; case KeyFramesStates.RUN_FORWARDS: - // break; + // temporary stuff. + if (_durationFrameCount > _durationTotalFrameCount) + _currentState = KeyFramesStates.RUN_COMPLETE; + + var tmp1 = (double)_durationFrameCount / _durationTotalFrameCount; + var easedTime = parentAnimation.Easing.Ease(tmp1); + _durationFrameCount++; + targetObserver.OnNext(Interpolator(easedTime)); + break; case KeyFramesStates.RUN_BACKWARDS: // break; @@ -133,11 +138,24 @@ namespace Avalonia.Animation.Keyframes // break; case KeyFramesStates.STOP: + targetObserver.OnCompleted(); _unsubscribe = true; break; } return 0; } + public IDisposable Subscribe(IObserver observer) + { + this.targetObserver = observer; + return this; + } + + public void Dispose() + { + _unsubscribe = true; + _currentState = KeyFramesStates.DISPOSED; + targetObserver = null; + } } } \ No newline at end of file diff --git a/src/Avalonia.Animation/Direction.cs b/src/Avalonia.Animation/PlaybackDirection.cs similarity index 100% rename from src/Avalonia.Animation/Direction.cs rename to src/Avalonia.Animation/PlaybackDirection.cs diff --git a/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs b/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs index 5ae836eb24..3a9f00ab52 100644 --- a/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs +++ b/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs @@ -47,8 +47,6 @@ namespace Avalonia //that CLR doesn't try to load dependencies before referencing method is jitted if (os == OperatingSystemType.WinNT) LoadWin32(); - else if(os==OperatingSystemType.OSX) - LoadMonoMac(); else LoadGtk3(); this.UseSkia(); @@ -56,7 +54,7 @@ namespace Avalonia return this; } - void LoadMonoMac() => this.UseMonoMac(); + void LoadWin32() => this.UseWin32(); void LoadGtk3() => this.UseGtk3(); } diff --git a/src/Avalonia.Visuals/Animation/Keyframes/TransformKeyFrames.cs b/src/Avalonia.Visuals/Animation/Keyframes/TransformKeyFrames.cs index 811964d3d6..d317b38347 100644 --- a/src/Avalonia.Visuals/Animation/Keyframes/TransformKeyFrames.cs +++ b/src/Avalonia.Visuals/Animation/Keyframes/TransformKeyFrames.cs @@ -73,8 +73,6 @@ namespace Avalonia.Animation.Keyframes } /// - public override IObservable DoInterpolation(IObservable timer, Animation animation, Animatable control) => null; - - + public override double DoInterpolation(double time) => 0; } }