浏览代码

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 年之前
父节点
当前提交
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>