Browse Source

Add PlayState support.
Redoing the main algorithm yet again.

Jumar Macato 7 years ago
parent
commit
714606b2ad

+ 5 - 5
samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs

@@ -10,21 +10,21 @@ namespace RenderDemo.ViewModels
 
         public AnimationsPageViewModel()
         {
-            ToggleGlobalPlayState = ReactiveCommand.Create(()=>TogglePlayState());
+            ToggleGlobalPlayState = ReactiveCommand.Create(() => TogglePlayState());
         }
 
         void TogglePlayState()
         {
-            switch (Timing.GetGlobalPlayState())
+            switch (Timing.GlobalPlayState)
             {
                 case PlayState.Run:
                     PlayStateText = "Resume all animations";
-                    Timing.SetGlobalPlayState(PlayState.Pause);
+                    Timing.GlobalPlayState = PlayState.Pause;
                     break;
 
                 case PlayState.Pause:
                     PlayStateText = "Pause all animations";
-                    Timing.SetGlobalPlayState(PlayState.Run);
+                    Timing.GlobalPlayState = PlayState.Run;
                     break;
             }
         }
@@ -36,5 +36,5 @@ namespace RenderDemo.ViewModels
         }
 
         public ReactiveCommand ToggleGlobalPlayState { get; }
-     }
+    }
 }

+ 2 - 19
src/Avalonia.Animation/Animatable.cs

@@ -23,25 +23,8 @@ namespace Avalonia.Animation
         /// </summary>
         public Animatable()
         {
-            Transitions = new Transitions();
-            AnimatableTimer = Timing.AnimationStateTimer
-                                .Select(p =>
-                                {
-                                    if (this._playState == PlayState.Pause)
-                                    {
-                                        return (PlayState.Pause, p.Item2);
-                                    }
-                                    else return p;
-                                })
-                                .Publish()
-                                .RefCount();
-        }
-
-        /// <summary>
-        /// The specific animations timer for this control.
-        /// </summary>
-        /// <returns></returns>
-        public IObservable<(PlayState, long)> AnimatableTimer;
+            Transitions = new Transitions(); 
+        } 
 
         /// <summary>
         /// Defines the <see cref="PlayState"/> property.

+ 49 - 38
src/Avalonia.Animation/Animation.cs

@@ -21,70 +21,81 @@ namespace Avalonia.Animation
     /// </summary>
     public class Animation : AvaloniaList<KeyFrame>, IAnimation
     {
-        private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
-        {
-            ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
-        };
-
-        public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
-            where TAnimator : IAnimator
-        {
-            Animators.Insert(0, (condition, typeof(TAnimator)));
-        }
-
-        private static Type GetAnimatorType(AvaloniaProperty property)
-        {
-            foreach (var (condition, type) in Animators)
-            {
-                if (condition(property))
-                {
-                    return type;
-                }
-            }
-            return null;
-        }
 
         public AvaloniaList<IAnimator> _animators { get; set; } = new AvaloniaList<IAnimator>();
 
         /// <summary>
-        /// Run time of this animation.
+        /// Gets or sets the active time of this animation.
         /// </summary>
         public TimeSpan Duration { get; set; }
 
         /// <summary>
-        /// Delay time for this animation.
-        /// </summary>
-        public TimeSpan Delay { get; set; }
-
-        /// <summary>
-        /// The repeat count for this animation.
+        /// Gets or sets the repeat count for this animation.
         /// </summary>
         public RepeatCount RepeatCount { get; set; }
 
         /// <summary>
-        /// The playback direction for this animation.
+        /// Gets or sets the playback direction for this animation.
         /// </summary>
         public PlaybackDirection PlaybackDirection { get; set; }
 
         /// <summary>
-        /// The value fill mode for this animation.
+        /// Gets or sets the value fill mode for this animation.
         /// </summary>
         public FillMode FillMode { get; set; }
 
         /// <summary>
-        /// Easing function to be used.
+        /// Gets or sets the easing function to be used for this animation.
         /// </summary>
         public Easing Easing { get; set; } = new LinearEasing();
- 
+
         /// <summary>
-        /// Sets the speed multiple for this animation.
+        /// Gets or sets the speed multiple for this animation.
         /// </summary>
         public double SpeedRatio { get; set; } = 1d;
 
-        /// <summary>
-        /// Sets the behavior for having a delay between repeats for this animation.
-        /// </summary>
-        public bool DelayBetweenRepeats { get; set; }
+        /// <summary> 
+        /// Gets or sets the delay time for this animation. 
+        /// </summary> 
+        /// <remarks>
+        /// Describes a delay to be added before the animation starts, and optionally between 
+        /// repeats of the animation if <see cref="DelayBetweenIterations"/> is set. 
+        /// </remarks> 
+        public TimeSpan Delay { get; set; }
+
+        /// <summary> 
+        /// Gets or sets a value indicating whether <see cref="Delay"/> will be applied between 
+        /// iterations of the animation.
+        /// </summary> 
+        /// <remarks>
+        /// If this property is not set, then <see cref="Delay"/> will only be applied to the first 
+        /// iteration of the animation. 
+        /// </remarks> 
+        public bool DelayBetweenIterations { get; set; }
+
+
+        private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
+        {
+            ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
+        };
+
+        public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
+            where TAnimator : IAnimator
+        {
+            Animators.Insert(0, (condition, typeof(TAnimator)));
+        }
+
+        private static Type GetAnimatorType(AvaloniaProperty property)
+        {
+            foreach (var (condition, type) in Animators)
+            {
+                if (condition(property))
+                {
+                    return type;
+                }
+            }
+            return null;
+        }
 
         private (IList<IAnimator> Animators, IList<IDisposable> subscriptions) InterpretKeyframes(Animatable control)
         {

+ 49 - 43
src/Avalonia.Animation/AnimatorStateMachine`1.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Animation
     /// Provides statefulness for an iteration of a keyframe animation.
     /// </summary>
     internal class AnimatorStateMachine<T> : IObservable<T>, IDisposable
-    { 
+    {
         T lastInterpValue;
         T firstKFValue;
 
@@ -34,6 +34,11 @@ namespace Avalonia.Animation
         internal bool unsubscribe;
         private bool isDisposed;
 
+        private long? internalClock;
+
+        private long? previousClock = null;
+        private long currentDiscreteTime;
+
         private Easings.Easing EaseFunc;
         private IObserver<T> targetObserver;
         private readonly Action onComplete;
@@ -55,7 +60,7 @@ namespace Avalonia.Animation
             speedRatio = animation.SpeedRatio;
             delayFC = ((animation.Delay.Ticks / Timing.FrameTick.Ticks) * speedRatio);
             durationFC = ((animation.Duration.Ticks / Timing.FrameTick.Ticks) * speedRatio);
-            delayBetweenIterations = animation.DelayBetweenRepeats;
+            delayBetweenIterations = animation.DelayBetweenIterations;
 
             switch (animation.RepeatCount.RepeatType)
             {
@@ -72,15 +77,15 @@ namespace Avalonia.Animation
             }
 
             animationDirection = animation.PlaybackDirection;
-            fillMode = animation.FillMode; 
+            fillMode = animation.FillMode;
             this.onComplete = onComplete;
         }
 
-        public void Step(PlayState playState, long frameTick, Func<double, T, T> Interpolator)
+        public void Step(long frameTick, Func<double, T, T> Interpolator)
         {
             try
             {
-                InternalStep(playState, frameTick, Interpolator);
+                InternalStep(frameTick, Interpolator);
             }
             catch (Exception e)
             {
@@ -107,8 +112,31 @@ namespace Avalonia.Animation
                     targetObserver.OnNext(lastInterpValue);
         }
 
-        private void InternalStep(PlayState playState, long frameTick, Func<double, T, T> Interpolator)
+        private void InternalStep(long time, Func<double, T, T> Interpolator)
         {
+            if (Timing.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop)
+                DoComplete();
+
+            if (!previousClock.HasValue)
+            {
+                previousClock = time;
+                internalClock = 0;
+            }
+            else
+            {
+                if (Timing.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause)
+                {
+                    previousClock = time;
+                    return;
+                }
+                var delta = time - previousClock;
+                internalClock += delta;
+                previousClock = time;
+            }
+
+            // currentDiscreteTime = internalClock.Value;
+            currentDiscreteTime++;
+
             if (!gotFirstKFValue)
             {
                 firstKFValue = (T)parent.First().Value;
@@ -117,42 +145,23 @@ namespace Avalonia.Animation
 
             if (!gotFirstFrameCount)
             {
-                firstFrameCount = frameTick;
+                firstFrameCount = currentDiscreteTime;
                 gotFirstFrameCount = true;
             }
 
             if (isDisposed)
                 throw new InvalidProgramException("This KeyFrames Animation is already disposed.");
 
-            if (playState == PlayState.Stop)
-                DoComplete();
-
-            // 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;
-
             // get the time with the initial fc as point of origin.
-            double t = (frameTick - firstFrameCount);
+            double t = (currentDiscreteTime - firstFrameCount);
 
             // check if t is within the zeroth iteration
-            if (t <= (delayFC + durationFC))
-            {
-                currentIteration = 0;
-                t = t % (delayFC + durationFC + 1);
-            }
-            else
-            {
-                var totalDur = (double)((delayBetweenIterations ? delayFC : 0) + durationFC + 1);
-                currentIteration = Math.Floor(t / totalDur);
-                t = t % totalDur;
-            }
+
+            double delayEndpoint = delayFC;
+            double iterationEndpoint = delayEndpoint + durationFC;
+
+            currentIteration = Math.Floor(t / iterationEndpoint);
+            t = t % iterationEndpoint;
 
             // check if it's over the repeat count
             if (currentIteration > (repeatCount - 1) && !isLooping)
@@ -166,19 +175,17 @@ namespace Avalonia.Animation
                                     animationDirection == PlaybackDirection.AlternateReverse ? (currentIteration % 2 == 0) ? true : false :
                                     animationDirection == PlaybackDirection.Reverse ? true : false;
 
-            double delayEndpoint = delayFC;
-            double iterationEndpoint = delayEndpoint + durationFC;
 
-            if (delayFC > 0 & t >= 0 & t <= delayEndpoint)
+            if (delayFC > 0 & t <= delayEndpoint)
             {
-                if (currentIteration == 0 || delayBetweenIterations)
+                if (currentIteration == 0)
                     DoDelay();
-
             }
-            else if (t >= delayEndpoint & t <= iterationEndpoint)
+            else if (t > delayEndpoint & t < iterationEndpoint)
             {
-                var interpVal = t / durationFC;
-
+                double k = t - delayFC;
+                var interpVal = k / (double)durationFC;
+ 
                 if (isCurIterReverse)
                     interpVal = 1 - interpVal;
 
@@ -187,7 +194,7 @@ namespace Avalonia.Animation
                 lastInterpValue = Interpolator(easedTime, neutralValue);
                 targetObserver.OnNext(lastInterpValue);
             }
-            else if (t > iterationEndpoint & (currentIteration + 1 > repeatCount & !isLooping))
+            else if (t > iterationEndpoint && !isLooping)
             {
                 DoComplete();
             }
@@ -198,7 +205,6 @@ namespace Avalonia.Animation
             targetObserver = observer;
             return this;
         }
-
         public void Dispose()
         {
             unsubscribe = true;

+ 10 - 11
src/Avalonia.Animation/Animator`1.cs

@@ -21,7 +21,7 @@ namespace Avalonia.Animation
         /// </summary>
         private readonly SortedList<double, (AnimatorKeyFrame, bool isNeutral)> _convertedKeyframes = new SortedList<double, (AnimatorKeyFrame, bool)>();
 
-        private bool _isVerfifiedAndConverted;
+        private bool isVerfifiedAndConverted;
 
         /// <summary>
         /// Gets or sets the target property for the keyframe.
@@ -31,18 +31,17 @@ namespace Avalonia.Animation
         public Animator()
         {
             // Invalidate keyframes when changed.
-            this.CollectionChanged += delegate { _isVerfifiedAndConverted = false; };
+            this.CollectionChanged += delegate { isVerfifiedAndConverted = false; };
         }
 
         /// <inheritdoc/>
-        public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch, Action onComplete)
+        public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> Match, Action onComplete)
         {
-            if (!_isVerfifiedAndConverted)
+            if (!isVerfifiedAndConverted)
                 VerifyConvertKeyFrames();
 
-            return obsMatch
-                // Ignore triggers when global timers are paused.
-                .Where(p => p && Timing.GetGlobalPlayState() != PlayState.Pause)
+            return Match
+                .Where(p => p)
                 .Subscribe(_ =>
                 {
                     var timerObs = RunKeyFrames(animation, control, onComplete);
@@ -101,9 +100,9 @@ namespace Avalonia.Animation
         {
             var stateMachine = new AnimatorStateMachine<T>(animation, control, this, onComplete);
 
-            Timing.AnimationStateTimer
+            Timing.AnimationsTimer
                         .TakeWhile(_ => !stateMachine.unsubscribe)
-                        .Subscribe(p => stateMachine.Step(p.Item1, p.Item2, DoInterpolation));
+                        .Subscribe(p => stateMachine.Step(p, DoInterpolation));
 
             return control.Bind<T>((AvaloniaProperty<T>)Property, stateMachine, BindingPriority.Animation);
         }
@@ -124,7 +123,7 @@ namespace Avalonia.Animation
             }
 
             AddNeutralKeyFramesIfNeeded();
-            _isVerfifiedAndConverted = true;
+            isVerfifiedAndConverted = true;
 
         }
 
@@ -133,7 +132,7 @@ namespace Avalonia.Animation
             bool hasStartKey, hasEndKey;
             hasStartKey = hasEndKey = false;
 
-            // Make start and end keyframe mandatory.
+            // Check if there's start and end keyframes.
             foreach (var converted in _convertedKeyframes.Keys)
             {
                 if (DoubleUtils.AboutEqual(converted, 0.0))

+ 12 - 47
src/Avalonia.Animation/Timing.cs

@@ -16,16 +16,18 @@ namespace Avalonia.Animation
     public static class Timing
     {
         static long _tickStartTimeStamp;
-        static PlayState _globalState = PlayState.Run;
         static long TicksPerFrame = Stopwatch.Frequency / FramesPerSecond;
 
+        /// <summary>
+        /// Gets or sets the animation play state for all animations
+        /// </summary> 
+        public static PlayState GlobalPlayState { get; set; } = PlayState.Run;
 
         /// <summary>
         /// The number of frames per second.
         /// </summary>
         public const int FramesPerSecond = 60;
 
-
         /// <summary>
         /// The time span of each frame.
         /// </summary>
@@ -36,45 +38,20 @@ namespace Avalonia.Animation
         /// </summary>
         static Timing()
         {
-
             _tickStartTimeStamp = Stopwatch.GetTimestamp();
 
             var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance);
 
-
-            AnimationStateTimer = globalTimer
+            AnimationsTimer = globalTimer
                 .Select(_ =>
                 {
-                    return (_globalState, (Stopwatch.GetTimestamp() - _tickStartTimeStamp)
-                      / TicksPerFrame);
+                    return (Stopwatch.GetTimestamp() - _tickStartTimeStamp)
+                      / TicksPerFrame * 2;
                 })
                 .Publish()
                 .RefCount();
-
-            TransitionsTimer = globalTimer
-                               .Select(p => p)
-                               .Publish()
-                               .RefCount();
-        }
-
-
-        /// <summary>
-        /// Sets the animation play state for all animations
-        /// </summary>
-        public static void SetGlobalPlayState(PlayState playState)
-        {
-            Dispatcher.UIThread.VerifyAccess();
-            _globalState = playState;
         }
 
-        /// <summary>
-        /// Gets the animation play state for all animations
-        /// </summary>
-        public static PlayState GetGlobalPlayState()
-        {
-            Dispatcher.UIThread.VerifyAccess();
-            return _globalState;
-        }
 
         /// <summary>
         /// Gets the animation timer.
@@ -84,21 +61,7 @@ namespace Avalonia.Animation
         /// defined in <see cref="FramesPerSecond"/>.
         /// The parameter passed to a subsciber is the current playstate of the animation.
         /// </remarks>
-        internal static IObservable<(PlayState, long)> AnimationStateTimer
-        {
-            get;
-        }
-
-        /// <summary>
-        /// Gets the transitions timer.
-        /// </summary>
-        /// <remarks>
-        /// The transitions timer increments usually 60 times per second as
-        /// defined in <see cref="FramesPerSecond"/>.
-        /// The parameter passed to a subsciber is the number of frames since the animation system was
-        /// initialized.
-        /// </remarks>
-        public static IObservable<long> TransitionsTimer
+        internal static IObservable<long> AnimationsTimer
         {
             get;
         }
@@ -116,10 +79,12 @@ namespace Avalonia.Animation
         /// </remarks>
         public static IObservable<double> GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan))
         {
+            // TODO: Fix this mess.
             var _duration = (duration.Ticks / FrameTick.Ticks);
-            var endTime = _duration;
+            long? endTime = ((Stopwatch.GetTimestamp() - _tickStartTimeStamp)
+                      / TicksPerFrame) + _duration;
 
-            return TransitionsTimer
+            return AnimationsTimer
                 .TakeWhile(x => x < endTime)
                 .Select(x => (double)x / _duration)
                 .StartWith(0.0)