Quellcode durchsuchen

Initial Implementation of KeyFrame state machines for handling different stages of an animation.

Jumar Macato vor 7 Jahren
Ursprung
Commit
0ed34541a2

+ 11 - 41
src/Avalonia.Animation/Animatable.cs

@@ -24,54 +24,24 @@ namespace Avalonia.Animation
         public Animatable()
         {
             Transitions = new Transitions.Transitions();
-            _animationStates = new Dictionary<ulong, AnimationStatus>();
             AnimatableTimer = Timing.AnimationStateTimer
-                         .Select(p =>
-                         {
-                             if (p == PlayState.Run
-                              && this._playState == PlayState.Run)
-                                 return _animationTime++;
-                             else
-                                 return _animationTime;
-                         })
-                         .Publish()
-                         .RefCount();
+                                .Select(p =>
+                                {
+                                    if (this._playState == PlayState.Pause)
+                                    {
+                                        return PlayState.Pause;
+                                    }
+                                    else return p;
+                                })
+                                .Publish()
+                                .RefCount();
         }
 
-
         /// <summary>
         /// The specific animations timer for this control.
         /// </summary>
         /// <returns></returns>
-        public IObservable<ulong> AnimatableTimer;
-
-        internal class AnimationStatus
-        {
-            public int RepeatCount { get; set; }
-            public int IterationDirection { get; set; }
-            public int CurrentIteration { get; set; }
-            public int TotalIteration { get; set; }
-        }
-
-        internal Dictionary<ulong, AnimationStatus> _animationStates;
-
-        internal ulong PrepareAnimatableForAnimation()
-        {
-            var iterToken = GetIterationToken();
-            _animationStates.Add(iterToken, new AnimationStatus());
-            return iterToken;
-        }
-
-        bool _animationsInitialized = false;
-
-        internal ulong _animationTime;
-
-        internal ulong _iterationTokenCounter;
-
-        internal ulong GetIterationToken()
-        {
-            return _iterationTokenCounter++;
-        }
+        public IObservable<PlayState> AnimatableTimer;
 
         /// <summary>
         /// Defines the <see cref="PlayState"/> property.

+ 3 - 5
src/Avalonia.Animation/Animation.cs

@@ -35,12 +35,12 @@ namespace Avalonia.Animation
         /// <summary>
         /// The playback direction for this animation.
         /// </summary>
-        public Direction Direction { get; set; }
+        public Direction PlaybackDirection { get; set; }
 
         /// <summary>
         /// Number of repeat iteration for this animation.
         /// </summary>
-        public int? RepeatCount { get; set; }
+        public ulong? RepeatCount { get; set; }
 
         /// <summary>
         /// Easing function to be used.
@@ -64,11 +64,9 @@ namespace Avalonia.Animation
         /// <inheritdocs/>
         public IDisposable Apply(Animatable control, IObservable<bool> matchObs)
         {
-            var iterToken = control.PrepareAnimatableForAnimation();
-
             foreach (IKeyFrames keyframes in Children)
             {
-                _subscription.Add(keyframes.Apply(this, control, iterToken, matchObs));
+                _subscription.Add(keyframes.Apply(this, control, matchObs));
             }
             return this;
         }

+ 1 - 1
src/Avalonia.Animation/Keyframes/IKeyFrames.cs

@@ -12,6 +12,6 @@ namespace Avalonia.Animation.Keyframes
         /// <summary>
         /// Applies the current KeyFrame group to the specified control.
         /// </summary>
-        IDisposable Apply(Animation animation, Animatable control, ulong IterationToken, IObservable<bool> obsMatch);
+        IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch);
     }
 }

+ 11 - 3
src/Avalonia.Animation/Keyframes/KeyFrames.cs

@@ -31,7 +31,7 @@ namespace Avalonia.Animation.Keyframes
 
 
         /// <inheritdoc/>
-        public virtual IDisposable Apply(Animation animation, Animatable control, ulong IterationToken, IObservable<bool> obsMatch)
+        public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch)
         {
             if (!IsVerfifiedAndConverted)
                 VerifyConvertKeyFrames(animation, typeof(T));
@@ -91,8 +91,16 @@ namespace Avalonia.Animation.Keyframes
         /// </summary>
         public IObservable<double> SetupAnimation(Animation animation, Animatable control)
         {
-            var newTimer = Timing.GetAnimationsTimer(control, animation.Duration, animation.Delay);
-            return newTimer.Select(t => animation.Easing.Ease(t));
+            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));
         }
 
 

+ 131 - 0
src/Avalonia.Animation/Keyframes/KeyFramesStateMachine.cs

@@ -0,0 +1,131 @@
+using System;
+
+namespace Avalonia.Animation.Keyframes
+{
+    /// <summary>
+    /// Provides statefulness for an iteration of a keyframe group animation.
+    /// </summary>
+    internal class KeyFramesStateMachine
+    {
+
+        ulong _delayTotalFrameCount,
+            _durationTotalFrameCount,
+            _delayFrameCount,
+            _durationFrameCount,
+            _repeatCount,
+            _currentIteration,
+            _totalIteration;
+
+        bool _isLooping, _isRepeating, _isReversed;
+        private Direction _animationDirection;
+        KeyFramesStates _currentState;
+
+        internal bool _unsubscribe = false;
+        private Animation _parentAnimation;
+        private Animatable _targetAnimatable;
+
+        private enum KeyFramesStates
+        {
+            INITIALIZE,
+            DO_DELAY,
+            DO_RUN,
+            RUN_FORWARDS,
+            RUN_BACKWARDS,
+            RUN_COMPLETE,
+            STOP
+        }
+
+        public void Start(Animation animation, Animatable control)
+        {
+            this._parentAnimation = animation;
+            this._targetAnimatable = control;
+
+            // int _delayFrameCount,
+            //     _durationFrameCount,
+            //     _repeatCount,
+            //     _iterationDirection,
+            //     _currentIteration,
+            //     _totalIteration;
+
+            _delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks);
+            _durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks);
+
+            if (_delayTotalFrameCount > 0)
+            {
+                _currentState = KeyFramesStates.DO_DELAY;
+            }
+
+            switch (animation.RepeatBehavior)
+            {
+                case RepeatBehavior.Loop:
+                    _isLooping = true;
+                    break;
+                case RepeatBehavior.Repeat:
+                    _isRepeating = true;
+                    if (animation.RepeatCount != null)
+                    {
+                        if (animation.RepeatCount == 0)
+                        {
+                            throw new InvalidOperationException
+                                ($"RepeatCount should be greater than zero when RepeatBehavior is set to Repeat.");
+                        }
+                        _repeatCount = (ulong)animation.RepeatCount;
+                    }
+                    else
+                    {
+                        throw new InvalidOperationException
+                            ($"RepeatCount should be defined when RepeatBehavior is set to Repeat.");
+                    }
+                    break;
+            }
+            this._animationDirection = animation.PlaybackDirection;
+            _currentState = KeyFramesStates.DO_RUN;
+        }
+
+        private void SetInitialPlaybackDirection(bool isReversed)
+        {
+            _isReversed = isReversed;
+        }
+
+        private bool GetPlaybackDirection()
+        {
+            _isReversed = !_isReversed;
+            return _isReversed;
+        }
+
+        public double Step(PlayState _playState)
+        {
+            if (_playState == PlayState.Stop) _currentState = KeyFramesStates.STOP;
+
+            switch (_currentState)
+            {
+                case KeyFramesStates.DO_DELAY:
+                    if (_delayFrameCount >= _delayTotalFrameCount)
+                    {
+                        _currentState = KeyFramesStates.DO_RUN;
+                    }
+                    _delayFrameCount++;
+                    return 0d;
+
+                case KeyFramesStates.DO_RUN:
+                    
+                    break;
+
+                case KeyFramesStates.RUN_FORWARDS:
+                // break;
+
+                case KeyFramesStates.RUN_BACKWARDS:
+                // break;
+
+                case KeyFramesStates.RUN_COMPLETE:
+                // break;
+
+                case KeyFramesStates.STOP:
+                    _unsubscribe = true;
+                    break;
+            }
+            return 0;
+        }
+
+    }
+}

+ 2 - 1
src/Avalonia.Animation/PlayState.cs

@@ -7,6 +7,7 @@ namespace Avalonia.Animation
     public enum PlayState
     {
         Run,
-        Pause
+        Pause,
+        Stop
     }
 }

+ 5 - 30
src/Avalonia.Animation/Timing.cs

@@ -26,14 +26,14 @@ namespace Avalonia.Animation
         /// <summary>
         /// The time span of each frame.
         /// </summary>
-        private static readonly TimeSpan Tick = TimeSpan.FromSeconds(1.0 / FramesPerSecond);
+        internal static readonly TimeSpan FrameTick = TimeSpan.FromSeconds(1.0 / FramesPerSecond);
 
         /// <summary>
         /// Initializes static members of the <see cref="Timing"/> class.
         /// </summary>
         static Timing()
         {
-            var globalTimer = Observable.Interval(Tick, AvaloniaScheduler.Instance);
+            var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance);
 
             AnimationStateTimer = globalTimer
                 .Select(_ =>
@@ -72,10 +72,9 @@ namespace Avalonia.Animation
         /// Gets the animation timer.
         /// </summary>
         /// <remarks>
-        /// The animation timer increments usually 60 times per second as
+        /// The animation timer triggers usually at 60 times per second or as
         /// defined in <see cref="FramesPerSecond"/>.
-        /// The parameter passed to a subsciber is the number of frames since the animation system was
-        /// initialized.
+        /// The parameter passed to a subsciber is the current playstate of the animation.
         /// </remarks>
         internal static IObservable<PlayState> AnimationStateTimer
         {
@@ -96,30 +95,6 @@ namespace Avalonia.Animation
             get;
         }
 
-        /// <summary>
-        /// Gets a timer that fires every frame for the specified duration with delay.
-        /// This timer's running state can be changed via <see cref="SetGlobalPlayState"/> method.
-        /// </summary>
-        /// <returns>
-        /// An observable that notifies the subscriber of the progress along the animation.
-        /// </returns>
-        /// <remarks>
-        /// The parameter passed to the subscriber is the progress along the animation, with
-        /// 0 being the start and 1 being the end. The observable is guaranteed to fire 0
-        /// immediately on subscribe and 1 at the end of the duration.
-        /// </remarks>
-        public static IObservable<double> GetAnimationsTimer(Animatable control, TimeSpan duration, TimeSpan delay)
-        {
-            var startTime = control._animationTime;
-            var _duration = (ulong)(duration.Ticks / Tick.Ticks);
-            var endTime = startTime + _duration;
-            return control.AnimatableTimer
-                .TakeWhile(x => x < endTime)
-                .Select(x => (double)(x - startTime) / _duration)
-                .StartWith(0.0)
-                .Concat(Observable.Return(1.0));
-        }
-
         /// <summary>
         /// Gets a timer that fires every frame for the specified duration with delay.
         /// </summary>
@@ -134,7 +109,7 @@ namespace Avalonia.Animation
         public static IObservable<double> GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan))
         {
             var startTime = _transitionsFrameCount;
-            var _duration = (ulong)(duration.Ticks / Tick.Ticks);
+            var _duration = (ulong)(duration.Ticks / FrameTick.Ticks);
             var endTime = startTime + _duration;
 
             return TransitionsTimer

+ 3 - 3
src/Avalonia.Visuals/Animation/Keyframes/TransformKeyFrames.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Animation.Keyframes
         DoubleKeyFrames childKeyFrames;
 
         /// <inheritdoc/>
-        public override IDisposable Apply(Animation animation, Animatable control, ulong IterationToken, IObservable<bool> obsMatch)
+        public override IDisposable Apply(Animation animation, Animatable control,  IObservable<bool> obsMatch)
         {
             var ctrl = (Visual)control;
 
@@ -35,7 +35,7 @@ namespace Avalonia.Animation.Keyframes
                 // It's a transform object so let's target that.
                 if (renderTransformType == Property.OwnerType)
                 {
-                    return childKeyFrames.Apply(animation, ctrl.RenderTransform, IterationToken, obsMatch);
+                    return childKeyFrames.Apply(animation, ctrl.RenderTransform,  obsMatch);
                 }
                 // It's a TransformGroup and try finding the target there.
                 else if (renderTransformType == typeof(TransformGroup))
@@ -44,7 +44,7 @@ namespace Avalonia.Animation.Keyframes
                     {
                         if (transform.GetType() == Property.OwnerType)
                         {
-                            return childKeyFrames.Apply(animation, transform, IterationToken, obsMatch);
+                            return childKeyFrames.Apply(animation, transform,  obsMatch);
                         }
                     }
                 }