Browse Source

Cleanup and refactor leftover transition types.

Dariusz Komosinski 4 years ago
parent
commit
21b7b88b0f

+ 5 - 0
src/Avalonia.Animation/AnimatorDrivenTransition.cs

@@ -3,6 +3,11 @@ using Avalonia.Animation.Animators;
 
 namespace Avalonia.Animation
 {
+    /// <summary>
+    /// <see cref="Transition{T}"/> using an <see cref="Animator{T}"/> to transition between values.
+    /// </summary>
+    /// <typeparam name="T">Type of the transitioned value.</typeparam>
+    /// <typeparam name="TAnimator">Type of the animator.</typeparam>
     public abstract class AnimatorDrivenTransition<T, TAnimator> : Transition<T> where TAnimator : Animator<T>, new()
     {
         private static readonly TAnimator s_animator = new TAnimator();

+ 6 - 3
src/Avalonia.Animation/AnimatorTransitionObservable.cs

@@ -4,6 +4,11 @@ using Avalonia.Animation.Easings;
 
 namespace Avalonia.Animation
 {
+    /// <summary>
+    /// Transition observable based on an <see cref="Animator{T}"/> producing a value.
+    /// </summary>
+    /// <typeparam name="T">Type of the transitioned value.</typeparam>
+    /// <typeparam name="TAnimator">Type of the animator.</typeparam>
     public class AnimatorTransitionObservable<T, TAnimator> : TransitionObservableBase<T> where TAnimator : Animator<T>
     {
         private readonly TAnimator _animator;
@@ -11,7 +16,7 @@ namespace Avalonia.Animation
         private readonly T _oldValue;
         private readonly T _newValue;
 
-        public AnimatorTransitionObservable(TAnimator animator, IObservable<double> progress, Easing easing, T oldValue, T newValue) : base(progress)
+        public AnimatorTransitionObservable(TAnimator animator, IObservable<double> progress, Easing easing, T oldValue, T newValue) : base(progress, easing)
         {
             _animator = animator;
             _easing = easing;
@@ -21,8 +26,6 @@ namespace Avalonia.Animation
 
         protected override T ProduceValue(double progress)
         {
-            progress = _easing.Ease(progress);
-
             return _animator.Interpolate(progress, _oldValue, _newValue);
         }
     }

+ 0 - 4
src/Avalonia.Animation/Clock.cs

@@ -1,8 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Reactive.Linq;
-using System.Text;
-using Avalonia.Reactive;
 
 namespace Avalonia.Animation
 {

+ 1 - 4
src/Avalonia.Animation/ClockBase.cs

@@ -1,7 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Reactive.Linq;
-using System.Text;
 using Avalonia.Reactive;
 
 namespace Avalonia.Animation
@@ -58,7 +55,7 @@ namespace Avalonia.Animation
             return _observable.Subscribe(observer);
         }
 
-        private class ClockObservable : LightweightObservableBase<TimeSpan>
+        private sealed class ClockObservable : LightweightObservableBase<TimeSpan>
         {
             public bool HasSubscriptions { get; private set; }
             public void Pulse(TimeSpan time) => PublishNext(time);

+ 35 - 2
src/Avalonia.Animation/TransitionInstance.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Runtime.ExceptionServices;
 using Avalonia.Reactive;
 using Avalonia.Utilities;
 
@@ -13,7 +14,7 @@ namespace Avalonia.Animation
         private TimeSpan _delay;
         private TimeSpan _duration;
         private readonly IClock _baseClock;
-        private IClock _clock;
+        private TransitionClock _clock;
 
         public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration)
         {
@@ -71,7 +72,7 @@ namespace Avalonia.Animation
 
         protected override void Subscribed()
         {
-            _clock = new Clock(_baseClock);
+            _clock = new TransitionClock(_baseClock);
             _timerSubscription = _clock.Subscribe(this);
             PublishNext(0.0d);
         }
@@ -90,5 +91,37 @@ namespace Avalonia.Animation
         {
             TimerTick(value);
         }
+
+        /// <summary>
+        /// TODO: This clock is still fairly expensive due to <see cref="ClockBase"/> implementation.
+        /// </summary>
+        private sealed class TransitionClock : ClockBase, IObserver<TimeSpan>
+        {
+            private readonly IDisposable _parentSubscription;
+
+            public TransitionClock(IClock parent)
+            {
+                _parentSubscription = parent.Subscribe(this);
+            }
+
+            protected override void Stop()
+            {
+                _parentSubscription.Dispose();
+            }
+
+            void IObserver<TimeSpan>.OnNext(TimeSpan value)
+            {
+                Pulse(value);
+            }
+
+            void IObserver<TimeSpan>.OnCompleted()
+            {
+            }
+
+            void IObserver<TimeSpan>.OnError(Exception error)
+            {
+                ExceptionDispatchInfo.Capture(error).Throw();
+            }
+        }
     }
 }

+ 26 - 13
src/Avalonia.Animation/TransitionObservableBase.cs

@@ -1,45 +1,58 @@
 using System;
+using Avalonia.Animation.Easings;
 using Avalonia.Reactive;
 
 #nullable enable
 
 namespace Avalonia.Animation
 {
+    /// <summary>
+    /// Provides base for observables implementing transitions.
+    /// </summary>
+    /// <typeparam name="T">Type of the transitioned value.</typeparam>
     public abstract class TransitionObservableBase<T> : SingleSubscriberObservableBase<T>, IObserver<double>
     {
+        private readonly Easing _easing;
         private readonly IObservable<double> _progress;
         private IDisposable? _progressSubscription;
 
-        protected TransitionObservableBase(IObservable<double> progress)
+        protected TransitionObservableBase(IObservable<double> progress, Easing easing)
         {
             _progress = progress;
+            _easing = easing;
         }
 
-        void IObserver<double>.OnCompleted()
+        /// <summary>
+        /// Produces value at given progress time point.
+        /// </summary>
+        /// <param name="progress">Transition progress.</param>
+        protected abstract T ProduceValue(double progress);
+
+        protected override void Subscribed()
         {
-            PublishCompleted();
+            _progressSubscription = _progress.Subscribe(this);
         }
 
-        void IObserver<double>.OnError(Exception error)
+        protected override void Unsubscribed()
         {
-            PublishError(error);
+            _progressSubscription?.Dispose();
         }
 
-        void IObserver<double>.OnNext(double value)
+        void IObserver<double>.OnCompleted()
         {
-            PublishNext(ProduceValue(value));
+            PublishCompleted();
         }
 
-        protected override void Unsubscribed()
+        void IObserver<double>.OnError(Exception error)
         {
-            _progressSubscription?.Dispose();
+            PublishError(error);
         }
 
-        protected override void Subscribed()
+        void IObserver<double>.OnNext(double value)
         {
-            _progressSubscription = _progress.Subscribe(this);
-        }
+            double progress = _easing.Ease(value);
 
-        protected abstract T ProduceValue(double progress);
+            PublishNext(ProduceValue(progress));
+        }
     }
 }

+ 0 - 14
src/Avalonia.Animation/Transitions/DoubleTransition.cs

@@ -1,5 +1,3 @@
-using System;
-using System.Reactive.Linq;
 using Avalonia.Animation.Animators;
 
 namespace Avalonia.Animation
@@ -10,16 +8,4 @@ namespace Avalonia.Animation
     public class DoubleTransition : AnimatorDrivenTransition<double, DoubleAnimator>
     {
     }
-
-    public class DoubleTransitionOld : Transition<double>
-    {
-        private static readonly DoubleAnimator s_animator = new DoubleAnimator();
-
-        /// <inheritdocs/>
-        public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue)
-        {
-            return progress
-                .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
-        }
-    }
 }

+ 1 - 12
src/Avalonia.Animation/Transitions/FloatTransition.cs

@@ -1,6 +1,3 @@
-using System;
-using System.Reactive.Linq;
-
 using Avalonia.Animation.Animators;
 
 namespace Avalonia.Animation
@@ -8,15 +5,7 @@ namespace Avalonia.Animation
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="float"/> types.
     /// </summary>  
-    public class FloatTransition : Transition<float>
+    public class FloatTransition : AnimatorDrivenTransition<float, FloatAnimator>
     {
-        private static readonly FloatAnimator s_animator = new FloatAnimator();
-
-        /// <inheritdocs/>
-        public override IObservable<float> DoTransition(IObservable<double> progress, float oldValue, float newValue)
-        {
-            return progress
-                .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
-        }
     }
 }

+ 1 - 12
src/Avalonia.Animation/Transitions/IntegerTransition.cs

@@ -1,6 +1,3 @@
-using System;
-using System.Reactive.Linq;
-
 using Avalonia.Animation.Animators;
 
 namespace Avalonia.Animation
@@ -8,15 +5,7 @@ namespace Avalonia.Animation
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="int"/> types.
     /// </summary>  
-    public class IntegerTransition : Transition<int>
+    public class IntegerTransition : AnimatorDrivenTransition<int, Int32Animator>
     {
-        private static readonly Int32Animator s_animator = new Int32Animator();
-
-        /// <inheritdocs/>
-        public override IObservable<int> DoTransition(IObservable<double> progress, int oldValue, int newValue)
-        {
-            return progress
-                .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
-        }
     }
 }

+ 1 - 12
src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs

@@ -1,6 +1,3 @@
-using System;
-using System.Reactive.Linq;
-
 using Avalonia.Animation.Animators;
 using Avalonia.Media;
 
@@ -9,15 +6,7 @@ namespace Avalonia.Animation
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="BoxShadows"/> type.
     /// </summary>  
-    public class BoxShadowsTransition : Transition<BoxShadows>
+    public class BoxShadowsTransition : AnimatorDrivenTransition<BoxShadows, BoxShadowsAnimator>
     {
-        private static readonly BoxShadowsAnimator s_animator = new BoxShadowsAnimator();
-
-        /// <inheritdocs/>
-        public override IObservable<BoxShadows> DoTransition(IObservable<double> progress, BoxShadows oldValue, BoxShadows newValue)
-        {
-            return progress
-                .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
-        }
     }
 }

+ 12 - 0
src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs

@@ -0,0 +1,12 @@
+using Avalonia.Animation.Animators;
+using Avalonia.Media;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Color"/> type.
+    /// </summary>
+    public class ColorTransition : AnimatorDrivenTransition<Color, ColorAnimator>
+    {
+    }
+}

+ 1 - 12
src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs

@@ -1,6 +1,3 @@
-using System;
-using System.Reactive.Linq;
-
 using Avalonia.Animation.Animators;
 
 namespace Avalonia.Animation
@@ -8,15 +5,7 @@ namespace Avalonia.Animation
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="CornerRadius"/> type.
     /// </summary>  
-    public class CornerRadiusTransition : Transition<CornerRadius>
+    public class CornerRadiusTransition : AnimatorDrivenTransition<CornerRadius, CornerRadiusAnimator>
     {
-        private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator();
-
-        /// <inheritdocs/>
-        public override IObservable<CornerRadius> DoTransition(IObservable<double> progress, CornerRadius oldValue, CornerRadius newValue)
-        {
-            return progress
-                .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
-        }
     }
 }

+ 1 - 12
src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs

@@ -1,6 +1,3 @@
-using System;
-using System.Reactive.Linq;
-
 using Avalonia.Animation.Animators;
 
 namespace Avalonia.Animation
@@ -8,15 +5,7 @@ namespace Avalonia.Animation
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Point"/> type.
     /// </summary>  
-    public class PointTransition : Transition<Point>
+    public class PointTransition : AnimatorDrivenTransition<Point, PointAnimator>
     {
-        private static readonly PointAnimator s_animator = new PointAnimator();
-
-        /// <inheritdocs/>
-        public override IObservable<Point> DoTransition(IObservable<double> progress, Point oldValue, Point newValue)
-        {
-            return progress
-                .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
-        }
     }
 }

+ 1 - 12
src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs

@@ -1,6 +1,3 @@
-using System;
-using System.Reactive.Linq;
-
 using Avalonia.Animation.Animators;
 
 namespace Avalonia.Animation
@@ -8,15 +5,7 @@ namespace Avalonia.Animation
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Size"/> type.
     /// </summary>  
-    public class SizeTransition : Transition<Size>
+    public class SizeTransition : AnimatorDrivenTransition<Size, SizeAnimator>
     {
-        private static readonly SizeAnimator s_animator = new SizeAnimator();
-
-        /// <inheritdocs/>
-        public override IObservable<Size> DoTransition(IObservable<double> progress, Size oldValue, Size newValue)
-        {
-            return progress
-                .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
-        }
     }
 }

+ 1 - 12
src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs

@@ -1,6 +1,3 @@
-using System;
-using System.Reactive.Linq;
-
 using Avalonia.Animation.Animators;
 
 namespace Avalonia.Animation
@@ -8,15 +5,7 @@ namespace Avalonia.Animation
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Thickness"/> type.
     /// </summary>  
-    public class ThicknessTransition : Transition<Thickness>
+    public class ThicknessTransition : AnimatorDrivenTransition<Thickness, ThicknessAnimator>
     {
-        private static readonly ThicknessAnimator s_animator = new ThicknessAnimator();
-
-        /// <inheritdocs/>
-        public override IObservable<Thickness> DoTransition(IObservable<double> progress, Thickness oldValue, Thickness newValue)
-        {
-            return progress
-                .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
-        }
     }
 }

+ 1 - 12
src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs

@@ -1,6 +1,3 @@
-using System;
-using System.Reactive.Linq;
-
 using Avalonia.Animation.Animators;
 
 namespace Avalonia.Animation
@@ -8,15 +5,7 @@ namespace Avalonia.Animation
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Vector"/> type.
     /// </summary>  
-    public class VectorTransition : Transition<Vector>
+    public class VectorTransition : AnimatorDrivenTransition<Vector, VectorAnimator>
     {
-        private static readonly VectorAnimator s_animator = new VectorAnimator();
-
-        /// <inheritdocs/>
-        public override IObservable<Vector> DoTransition(IObservable<double> progress, Vector oldValue, Vector newValue)
-        {
-            return progress
-                .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
-        }
     }
 }

+ 27 - 10
tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs

@@ -1,9 +1,11 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Reactive.Linq;
 using System.Reactive.Subjects;
 using System.Runtime.CompilerServices;
 using Avalonia.Animation;
+using Avalonia.Animation.Animators;
 using Avalonia.Layout;
 using BenchmarkDotNet.Attributes;
 
@@ -14,26 +16,29 @@ namespace Avalonia.Benchmarks.Animations
     {
         private readonly DoubleTransition _transition;
         private readonly DoubleTransitionOld _oldTransition;
-        private readonly int _frameCount;
         private readonly Subject<double> _timeProducer;
         private readonly List<double> _producedValues;
+        private readonly AddValueObserver _observer;
+
+        [Params(10, 100)]
+        public int FrameCount { get; set; }
 
         public TransitionBenchmark()
         {
-            _frameCount = 100;
-
             _oldTransition = new DoubleTransitionOld
             {
-                Duration = TimeSpan.FromMilliseconds(_frameCount), Property = Layoutable.WidthProperty
+                Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty
             };
 
             _transition = new DoubleTransition
             {
-                Duration = TimeSpan.FromMilliseconds(_frameCount), Property = Layoutable.WidthProperty
+                Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty
             };
 
             _timeProducer = new Subject<double>();
-            _producedValues = new List<double>(_frameCount);
+            _producedValues = new List<double>(FrameCount);
+
+            _observer = new AddValueObserver(_producedValues);
         }
 
         [Benchmark(Baseline = true)]
@@ -56,14 +61,26 @@ namespace Avalonia.Benchmarks.Animations
 
             _producedValues.Clear();
 
-            using var transitionSub = transitionObs.Subscribe(new AddValueObserver(_producedValues));
+            using var transitionSub = transitionObs.Subscribe(_observer);
 
-            for (int i = 0; i < _frameCount; i++)
+            for (int i = 0; i < FrameCount; i++)
             {
-                _timeProducer.OnNext(TimeSpan.FromMilliseconds(i).TotalSeconds);
+                _timeProducer.OnNext(i/1000d);
             }
 
-            Debug.Assert(_producedValues.Count == _frameCount);
+            Debug.Assert(_producedValues.Count == FrameCount);
+        }
+
+        private class DoubleTransitionOld : Transition<double>
+        {
+            private static readonly DoubleAnimator s_animator = new DoubleAnimator();
+
+            /// <inheritdocs/>
+            public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue)
+            {
+                return progress
+                    .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
+            }
         }
 
         private class AddValueObserver : IObserver<double>