Browse Source

Commented out System.Numerics.Vectors and the corresponding Vector4 to Color conversion code.

Completed the Repeat Behaviors logic (Loop, Repeat)

State Machine can now respond to PlayStates properly.

Combined the Intra-frame normalized time and Keyframe Pair selection into a single function for flexibility and customizable custom KeyFrames objects on the end-user.

Initial FillMode implementation.
Jumar Macato 7 years ago
parent
commit
beee57d146

+ 1 - 1
build/Base.props

@@ -1,6 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
     <PackageReference Include="System.ValueTuple" Version="4.3.1" />
-    <PackageReference Include="System.Numerics.Vectors" Version="4.5.0-preview2-26406-04"/>
+    <!-- <PackageReference Include="System.Numerics.Vectors" Version="4.5.0-preview2-26406-04"/> -->
   </ItemGroup>
 </Project>

+ 14 - 0
src/Avalonia.Animation/FillMode.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Animation
+{
+    public enum FillMode
+    {
+        None,
+        Forward,
+        Backward,
+        Both
+    }
+}

+ 4 - 7
src/Avalonia.Animation/Keyframes/DoubleKeyFrames.cs

@@ -18,15 +18,12 @@ namespace Avalonia.Animation.Keyframes
         /// <inheritdocs/>
         public override double DoInterpolation(double t)
         {
-            var pair = GetKeyFramePairByTime(t);
-
-            double y0 = pair.FirstKeyFrame.Value;
-            double t0 = pair.FirstKeyFrame.Key;
-            double y1 = pair.SecondKeyFrame.Value;
-            double t1 = pair.SecondKeyFrame.Key;
+            var pair = GetKFPairAndIntraKFTime(t);
+            double y0 = pair.KFPair.FirstKeyFrame.Value;
+            double y1 = pair.KFPair.SecondKeyFrame.Value;
 
             // Do linear parametric interpolation 
-            return y0 + ((t - t0) / (t1 - t0)) * (y1 - y0);
+            return y0 + (pair.IntraKFTime) * (y1 - y0);
         }
     }
 }

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

@@ -48,9 +48,13 @@ namespace Avalonia.Animation.Keyframes
 
         /// <summary>
         /// Get the nearest pair of cue-time ordered keyframes 
-        /// according to the given time parameter.  
+        /// according to the given time parameter that is relative to
+        /// total animation time and the normalized intra-keyframe pair time 
+        /// (i.e., the normalized time between the selected keyframes, relative to the
+        /// give time parameter).
         /// </summary>
-        public KeyFramePair<T> GetKeyFramePairByTime(double t)
+        /// <param name="t">The time parameter, relative to the total animation time</param>
+        public (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t)
         {
             KeyValuePair<double, T> firstCue, lastCue;
             int kvCount = ConvertedKeyframes.Count();
@@ -77,7 +81,11 @@ namespace Avalonia.Animation.Keyframes
                 firstCue = ConvertedKeyframes.First();
                 lastCue = ConvertedKeyframes.Last();
             }
-            return new KeyFramePair<T>(firstCue, lastCue);
+
+            double t0 = firstCue.Key;
+            double t1 = lastCue.Key;
+            var intraframeTime = (t - t0) / (t1 - t0);
+            return (intraframeTime, new KeyFramePair<T>(firstCue, lastCue));
         }
 
 
@@ -87,14 +95,14 @@ namespace Avalonia.Animation.Keyframes
         public IDisposable RunKeyFrames(Animation animation, Animatable control)
         {
             var _kfStateMach = new KeyFramesStateMachine<T>();
-            _kfStateMach.Start(animation);
+            _kfStateMach.Initialize(animation, control);
 
             Timing.AnimationStateTimer
-                         .TakeWhile(_ => !_kfStateMach._unsubscribe)
-                         .Subscribe(p =>
-                         {
-                             _kfStateMach.Step(p, DoInterpolation);
-                         });
+                        .TakeWhile(_ => !_kfStateMach._unsubscribe)
+                        .Subscribe(p =>
+                        {
+                            _kfStateMach.Step(p, DoInterpolation);
+                        });
 
             return control.Bind(Property, _kfStateMach, BindingPriority.Animation);
         }
@@ -110,7 +118,7 @@ namespace Avalonia.Animation.Keyframes
 
 
         /// <summary>
-        /// Verifies and converts keyframe values according to this class's type parameter.
+        /// Verifies and converts keyframe values according to this class's target type.
         /// </summary>
         private void VerifyConvertKeyFrames(Animation animation, Type type)
         {
@@ -169,7 +177,7 @@ namespace Avalonia.Animation.Keyframes
                 throw new InvalidOperationException
                     ($"{this.GetType().Name} must have a starting (0% cue) and ending (100% cue) keyframe.");
 
-            // Sort Cues, in case they don't order it by themselves.
+            // Sort Cues, in case users don't order it by themselves.
             ConvertedKeyframes = ConvertedKeyframes.OrderBy(p => p.Key)
                                                    .ToDictionary((k) => k.Key, (v) => v.Value);
         }

+ 101 - 28
src/Avalonia.Animation/Keyframes/KeyFramesStateMachine.cs

@@ -7,21 +7,24 @@ namespace Avalonia.Animation.Keyframes
     /// </summary>
     internal class KeyFramesStateMachine<T> : IObservable<object>, IDisposable
     {
-        ulong _delayTotalFrameCount,
+        
+        T _lastInterpValue = default(T);
+
+        private ulong _delayTotalFrameCount,
             _durationTotalFrameCount,
             _delayFrameCount,
             _durationFrameCount,
             _repeatCount,
-            _currentIteration,
-            _totalIteration;
+            _currentIteration;
 
-        bool _isLooping, _isRepeating, _isReversed;
+        private bool _isLooping, _isRepeating, _isReversed, _checkLoopAndRepeat;
         private PlaybackDirection _animationDirection;
         KeyFramesStates _currentState, _savedState;
-        private Animation parentAnimation;
+        private Animation _parentAnimation;
+        private Animatable _targetControl;
         internal bool _unsubscribe = false;
         double _outputTime = 0d;
-        private IObserver<object> targetObserver;
+        private IObserver<object> _targetObserver;
 
         private enum KeyFramesStates
         {
@@ -30,26 +33,28 @@ namespace Avalonia.Animation.Keyframes
             DO_RUN,
             RUN_FORWARDS,
             RUN_BACKWARDS,
+            RUN_APPLYVALUE,
             RUN_COMPLETE,
             PAUSE,
             STOP,
             DISPOSED
         }
 
-        public void Start(Animation animation)
+        public void Initialize(Animation animation, Animatable control)
         {
-            parentAnimation = animation;
+            _parentAnimation = 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:
-                    _isRepeating = true;
                     if (animation.RepeatCount != null)
                     {
                         if (animation.RepeatCount == 0)
@@ -57,6 +62,8 @@ namespace Avalonia.Animation.Keyframes
                             throw new InvalidOperationException
                                 ($"RepeatCount should be greater than zero when RepeatBehavior is set to Repeat.");
                         }
+                        _isRepeating = true;
+                        _checkLoopAndRepeat = true;
                         _repeatCount = (ulong)animation.RepeatCount;
                     }
                     else
@@ -78,13 +85,29 @@ namespace Avalonia.Animation.Keyframes
                     break;
             }
 
+            _animationDirection = _parentAnimation.PlaybackDirection;
+
             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 (_currentState == KeyFramesStates.DISPOSED)
                 throw new InvalidProgramException("This KeyFrames Animation is already disposed.");
@@ -103,7 +126,9 @@ namespace Avalonia.Animation.Keyframes
             if (_playState != PlayState.Pause && _currentState == KeyFramesStates.PAUSE)
                 _currentState = _savedState;
 
-            checkstate:
+            double _tempDuration = 0d, _easedTime;
+
+        checkstate:
             switch (_currentState)
             {
                 case KeyFramesStates.DO_DELAY:
@@ -116,32 +141,83 @@ namespace Avalonia.Animation.Keyframes
                     break;
 
                 case KeyFramesStates.DO_RUN:
-                    // temporary stuff.
-                    _currentState = KeyFramesStates.RUN_FORWARDS;
-
+                    if (_isReversed)
+                        _currentState = KeyFramesStates.RUN_BACKWARDS;
+                    else
+                        _currentState = KeyFramesStates.RUN_FORWARDS;
                     goto checkstate;
 
-
                 case KeyFramesStates.RUN_FORWARDS:
-                    // temporary stuff.
                     if (_durationFrameCount > _durationTotalFrameCount)
+                    {
                         _currentState = KeyFramesStates.RUN_COMPLETE;
+                        goto checkstate;
+                    }
 
-                    var tmp1 = (double)_durationFrameCount / _durationTotalFrameCount;
-                    var easedTime = parentAnimation.Easing.Ease(tmp1);
-                    _durationFrameCount++;
+                    _tempDuration = (double)_durationFrameCount / _durationTotalFrameCount;
+                    _currentState = KeyFramesStates.RUN_APPLYVALUE;
 
-                    targetObserver.OnNext(Interpolator(easedTime));
-                    break;
+                    goto checkstate;
 
                 case KeyFramesStates.RUN_BACKWARDS:
-                // break;
+                    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 = _parentAnimation.Easing.Ease(_tempDuration);
+
+                    _durationFrameCount++;
+                    _lastInterpValue = Interpolator(_easedTime);
+                    _targetObserver.OnNext(_lastInterpValue);
+                    _currentState = KeyFramesStates.DO_RUN;
+
+                    break;
 
                 case KeyFramesStates.RUN_COMPLETE:
-                // break;
+
+                    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:
-                    this.Dispose();
+                    _targetObserver.OnCompleted();
                     break;
             }
 
@@ -149,16 +225,13 @@ namespace Avalonia.Animation.Keyframes
 
         public IDisposable Subscribe(IObserver<object> observer)
         {
-            this.targetObserver = observer;
+            _targetObserver = observer;
             return this;
         }
-
         public void Dispose()
         {
-            targetObserver.OnCompleted();
             _unsubscribe = true;
             _currentState = KeyFramesStates.DISPOSED;
-
         }
     }
 }

+ 26 - 26
src/Avalonia.Visuals/Media/Color.cs

@@ -82,32 +82,32 @@ namespace Avalonia.Media
             );
         }
 
-        /// <summary>
-        /// Creates a <see cref="Color"/> from a <see cref="Vector4"/>.
-        /// </summary>
-        /// <param name="value">The <see cref="Vector4"/> value.</param>
-        /// <param name="normalized">True if the color vector is normalized floats, false if it's bytes (0-255)</param>
-        /// <returns>The color.</returns>
-        public static Color FromVector4(Vector4 value, bool normalized)
-        {
-            if (normalized)
-            {
-                return new Color(
-                    (byte)(value.W * 255),
-                    (byte)(value.X * 255),
-                    (byte)(value.Y * 255),
-                    (byte)(value.Z * 255)
-                );
-            } else {
-                return new Color(
-                    (byte)(value.W),
-                    (byte)(value.X),
-                    (byte)(value.Y),
-                    (byte)(value.Z)
-                );
-            }
-
-        }
+        // /// <summary>
+        // /// Creates a <see cref="Color"/> from a <see cref="Vector4"/>.
+        // /// </summary>
+        // /// <param name="value">The <see cref="Vector4"/> value.</param>
+        // /// <param name="normalized">True if the color vector is normalized floats, false if it's bytes (0-255)</param>
+        // /// <returns>The color.</returns>
+        // public static Color FromVector4(Vector4 value, bool normalized)
+        // {
+        //     if (normalized)
+        //     {
+        //         return new Color(
+        //             (byte)(value.W * 255),
+        //             (byte)(value.X * 255),
+        //             (byte)(value.Y * 255),
+        //             (byte)(value.Z * 255)
+        //         );
+        //     } else {
+        //         return new Color(
+        //             (byte)(value.W),
+        //             (byte)(value.X),
+        //             (byte)(value.Y),
+        //             (byte)(value.Z)
+        //         );
+        //     }
+        // }
+        
         /// <summary>
         /// Parses a color string.
         /// </summary>