diff --git a/src/Avalonia.Animation/AnimatorDrivenTransition.cs b/src/Avalonia.Animation/AnimatorDrivenTransition.cs
new file mode 100644
index 0000000000..88c8ec5ec1
--- /dev/null
+++ b/src/Avalonia.Animation/AnimatorDrivenTransition.cs
@@ -0,0 +1,20 @@
+using System;
+using Avalonia.Animation.Animators;
+
+namespace Avalonia.Animation
+{
+ ///
+ /// using an to transition between values.
+ ///
+ /// Type of the transitioned value.
+ /// Type of the animator.
+ public abstract class AnimatorDrivenTransition : Transition where TAnimator : Animator, new()
+ {
+ private static readonly TAnimator s_animator = new TAnimator();
+
+ public override IObservable DoTransition(IObservable progress, T oldValue, T newValue)
+ {
+ return new AnimatorTransitionObservable(s_animator, progress, Easing, oldValue, newValue);
+ }
+ }
+}
diff --git a/src/Avalonia.Animation/AnimatorTransitionObservable.cs b/src/Avalonia.Animation/AnimatorTransitionObservable.cs
new file mode 100644
index 0000000000..3cc185179b
--- /dev/null
+++ b/src/Avalonia.Animation/AnimatorTransitionObservable.cs
@@ -0,0 +1,32 @@
+using System;
+using Avalonia.Animation.Animators;
+using Avalonia.Animation.Easings;
+
+namespace Avalonia.Animation
+{
+ ///
+ /// Transition observable based on an producing a value.
+ ///
+ /// Type of the transitioned value.
+ /// Type of the animator.
+ public class AnimatorTransitionObservable : TransitionObservableBase where TAnimator : Animator
+ {
+ private readonly TAnimator _animator;
+ private readonly Easing _easing;
+ private readonly T _oldValue;
+ private readonly T _newValue;
+
+ public AnimatorTransitionObservable(TAnimator animator, IObservable progress, Easing easing, T oldValue, T newValue) : base(progress, easing)
+ {
+ _animator = animator;
+ _easing = easing;
+ _oldValue = oldValue;
+ _newValue = newValue;
+ }
+
+ protected override T ProduceValue(double progress)
+ {
+ return _animator.Interpolate(progress, _oldValue, _newValue);
+ }
+ }
+}
diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs
index bea6c75982..5c2b7ce0dd 100644
--- a/src/Avalonia.Animation/Clock.cs
+++ b/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
{
@@ -10,10 +6,9 @@ namespace Avalonia.Animation
{
public static IClock GlobalClock => AvaloniaLocator.Current.GetService();
- private IDisposable _parentSubscription;
+ private readonly IDisposable _parentSubscription;
- public Clock()
- :this(GlobalClock)
+ public Clock() : this(GlobalClock)
{
}
diff --git a/src/Avalonia.Animation/ClockBase.cs b/src/Avalonia.Animation/ClockBase.cs
index a2b29e728e..c6e5a363be 100644
--- a/src/Avalonia.Animation/ClockBase.cs
+++ b/src/Avalonia.Animation/ClockBase.cs
@@ -1,16 +1,11 @@
using System;
-using System.Collections.Generic;
-using System.Reactive.Linq;
-using System.Text;
using Avalonia.Reactive;
namespace Avalonia.Animation
{
public class ClockBase : IClock
{
- private ClockObservable _observable;
-
- private IObservable _connectedObservable;
+ private readonly ClockObservable _observable;
private TimeSpan? _previousTime;
private TimeSpan _internalTime;
@@ -18,7 +13,6 @@ namespace Avalonia.Animation
protected ClockBase()
{
_observable = new ClockObservable();
- _connectedObservable = _observable.Publish().RefCount();
}
protected bool HasSubscriptions => _observable.HasSubscriptions;
@@ -58,10 +52,10 @@ namespace Avalonia.Animation
public IDisposable Subscribe(IObserver observer)
{
- return _connectedObservable.Subscribe(observer);
+ return _observable.Subscribe(observer);
}
- private class ClockObservable : LightweightObservableBase
+ private sealed class ClockObservable : LightweightObservableBase
{
public bool HasSubscriptions { get; private set; }
public void Pulse(TimeSpan time) => PublishNext(time);
diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition.cs
similarity index 96%
rename from src/Avalonia.Animation/Transition`1.cs
rename to src/Avalonia.Animation/Transition.cs
index 4542a137e5..4115c95c0f 100644
--- a/src/Avalonia.Animation/Transition`1.cs
+++ b/src/Avalonia.Animation/Transition.cs
@@ -1,7 +1,5 @@
-using System;
-using System.Reactive.Linq;
+using System;
using Avalonia.Animation.Easings;
-using Avalonia.Animation.Utils;
namespace Avalonia.Animation
{
@@ -56,4 +54,4 @@ namespace Avalonia.Animation
return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs
index 5184341324..b522d1961e 100644
--- a/src/Avalonia.Animation/TransitionInstance.cs
+++ b/src/Avalonia.Animation/TransitionInstance.cs
@@ -1,8 +1,5 @@
-using Avalonia.Metadata;
using System;
-using System.Reactive.Linq;
-using Avalonia.Animation.Easings;
-using Avalonia.Animation.Utils;
+using System.Runtime.ExceptionServices;
using Avalonia.Reactive;
using Avalonia.Utilities;
@@ -11,13 +8,13 @@ namespace Avalonia.Animation
///
/// Handles the timing and lifetime of a .
///
- internal class TransitionInstance : SingleSubscriberObservableBase
+ internal class TransitionInstance : SingleSubscriberObservableBase, IObserver
{
private IDisposable _timerSubscription;
private TimeSpan _delay;
private TimeSpan _duration;
private readonly IClock _baseClock;
- private IClock _clock;
+ private TransitionClock _clock;
public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration)
{
@@ -75,9 +72,56 @@ namespace Avalonia.Animation
protected override void Subscribed()
{
- _clock = new Clock(_baseClock);
- _timerSubscription = _clock.Subscribe(TimerTick);
+ _clock = new TransitionClock(_baseClock);
+ _timerSubscription = _clock.Subscribe(this);
PublishNext(0.0d);
}
+
+ void IObserver.OnCompleted()
+ {
+ PublishCompleted();
+ }
+
+ void IObserver.OnError(Exception error)
+ {
+ PublishError(error);
+ }
+
+ void IObserver.OnNext(TimeSpan value)
+ {
+ TimerTick(value);
+ }
+
+ ///
+ /// TODO: This clock is still fairly expensive due to implementation.
+ ///
+ private sealed class TransitionClock : ClockBase, IObserver
+ {
+ private readonly IDisposable _parentSubscription;
+
+ public TransitionClock(IClock parent)
+ {
+ _parentSubscription = parent.Subscribe(this);
+ }
+
+ protected override void Stop()
+ {
+ _parentSubscription.Dispose();
+ }
+
+ void IObserver.OnNext(TimeSpan value)
+ {
+ Pulse(value);
+ }
+
+ void IObserver.OnCompleted()
+ {
+ }
+
+ void IObserver.OnError(Exception error)
+ {
+ ExceptionDispatchInfo.Capture(error).Throw();
+ }
+ }
}
}
diff --git a/src/Avalonia.Animation/TransitionObservableBase.cs b/src/Avalonia.Animation/TransitionObservableBase.cs
new file mode 100644
index 0000000000..c4ac803135
--- /dev/null
+++ b/src/Avalonia.Animation/TransitionObservableBase.cs
@@ -0,0 +1,58 @@
+using System;
+using Avalonia.Animation.Easings;
+using Avalonia.Reactive;
+
+#nullable enable
+
+namespace Avalonia.Animation
+{
+ ///
+ /// Provides base for observables implementing transitions.
+ ///
+ /// Type of the transitioned value.
+ public abstract class TransitionObservableBase : SingleSubscriberObservableBase, IObserver
+ {
+ private readonly Easing _easing;
+ private readonly IObservable _progress;
+ private IDisposable? _progressSubscription;
+
+ protected TransitionObservableBase(IObservable progress, Easing easing)
+ {
+ _progress = progress;
+ _easing = easing;
+ }
+
+ ///
+ /// Produces value at given progress time point.
+ ///
+ /// Transition progress.
+ protected abstract T ProduceValue(double progress);
+
+ protected override void Subscribed()
+ {
+ _progressSubscription = _progress.Subscribe(this);
+ }
+
+ protected override void Unsubscribed()
+ {
+ _progressSubscription?.Dispose();
+ }
+
+ void IObserver.OnCompleted()
+ {
+ PublishCompleted();
+ }
+
+ void IObserver.OnError(Exception error)
+ {
+ PublishError(error);
+ }
+
+ void IObserver.OnNext(double value)
+ {
+ double progress = _easing.Ease(value);
+
+ PublishNext(ProduceValue(progress));
+ }
+ }
+}
diff --git a/src/Avalonia.Animation/Transitions/DoubleTransition.cs b/src/Avalonia.Animation/Transitions/DoubleTransition.cs
index d5bb1aac20..7232d87863 100644
--- a/src/Avalonia.Animation/Transitions/DoubleTransition.cs
+++ b/src/Avalonia.Animation/Transitions/DoubleTransition.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
///
/// Transition class that handles with types.
///
- public class DoubleTransition : Transition
+ public class DoubleTransition : AnimatorDrivenTransition
{
- private static readonly DoubleAnimator s_animator = new DoubleAnimator();
-
- ///
- public override IObservable DoTransition(IObservable progress, double oldValue, double newValue)
- {
- return progress
- .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
- }
}
}
diff --git a/src/Avalonia.Animation/Transitions/FloatTransition.cs b/src/Avalonia.Animation/Transitions/FloatTransition.cs
index 37b644fa96..a96db8ba5b 100644
--- a/src/Avalonia.Animation/Transitions/FloatTransition.cs
+++ b/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
///
/// Transition class that handles with types.
///
- public class FloatTransition : Transition
+ public class FloatTransition : AnimatorDrivenTransition
{
- private static readonly FloatAnimator s_animator = new FloatAnimator();
-
- ///
- public override IObservable DoTransition(IObservable progress, float oldValue, float newValue)
- {
- return progress
- .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
- }
}
}
diff --git a/src/Avalonia.Animation/Transitions/IntegerTransition.cs b/src/Avalonia.Animation/Transitions/IntegerTransition.cs
index 223b2ba531..343da7b689 100644
--- a/src/Avalonia.Animation/Transitions/IntegerTransition.cs
+++ b/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
///
/// Transition class that handles with types.
///
- public class IntegerTransition : Transition
+ public class IntegerTransition : AnimatorDrivenTransition
{
- private static readonly Int32Animator s_animator = new Int32Animator();
-
- ///
- public override IObservable DoTransition(IObservable progress, int oldValue, int newValue)
- {
- return progress
- .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
- }
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs
index 008613fb40..8a070836e9 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs
+++ b/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
///
/// Transition class that handles with type.
///
- public class BoxShadowsTransition : Transition
+ public class BoxShadowsTransition : AnimatorDrivenTransition
{
- private static readonly BoxShadowsAnimator s_animator = new BoxShadowsAnimator();
-
- ///
- public override IObservable DoTransition(IObservable progress, BoxShadows oldValue, BoxShadows newValue)
- {
- return progress
- .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
- }
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs
new file mode 100644
index 0000000000..9b925a3779
--- /dev/null
+++ b/src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs
@@ -0,0 +1,12 @@
+using Avalonia.Animation.Animators;
+using Avalonia.Media;
+
+namespace Avalonia.Animation
+{
+ ///
+ /// Transition class that handles with type.
+ ///
+ public class ColorTransition : AnimatorDrivenTransition
+ {
+ }
+}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
index 9ffdf53694..62b77a64e1 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
+++ b/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
///
/// Transition class that handles with type.
///
- public class CornerRadiusTransition : Transition
+ public class CornerRadiusTransition : AnimatorDrivenTransition
{
- private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator();
-
- ///
- public override IObservable DoTransition(IObservable progress, CornerRadius oldValue, CornerRadius newValue)
- {
- return progress
- .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
- }
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
index fbe24c6d55..0985aaa8f8 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
+++ b/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
///
/// Transition class that handles with type.
///
- public class PointTransition : Transition
+ public class PointTransition : AnimatorDrivenTransition
{
- private static readonly PointAnimator s_animator = new PointAnimator();
-
- ///
- public override IObservable DoTransition(IObservable progress, Point oldValue, Point newValue)
- {
- return progress
- .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
- }
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs
index 464f83bec7..6e59fb3631 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs
+++ b/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
///
/// Transition class that handles with type.
///
- public class SizeTransition : Transition
+ public class SizeTransition : AnimatorDrivenTransition
{
- private static readonly SizeAnimator s_animator = new SizeAnimator();
-
- ///
- public override IObservable DoTransition(IObservable progress, Size oldValue, Size newValue)
- {
- return progress
- .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
- }
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
index 9fb3380780..f50929348f 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
+++ b/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
///
/// Transition class that handles with type.
///
- public class ThicknessTransition : Transition
+ public class ThicknessTransition : AnimatorDrivenTransition
{
- private static readonly ThicknessAnimator s_animator = new ThicknessAnimator();
-
- ///
- public override IObservable DoTransition(IObservable progress, Thickness oldValue, Thickness newValue)
- {
- return progress
- .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
- }
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs
index 104acb71ad..73fc23ad99 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs
+++ b/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs
@@ -1,28 +1,26 @@
using System;
-using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Media;
+using Avalonia.Media.Transformation;
+
+#nullable enable
namespace Avalonia.Animation
{
public class TransformOperationsTransition : Transition
{
- private static readonly TransformOperationsAnimator _operationsAnimator = new TransformOperationsAnimator();
+ private static readonly TransformOperationsAnimator s_operationsAnimator = new TransformOperationsAnimator();
- public override IObservable DoTransition(IObservable progress,
+ public override IObservable DoTransition(
+ IObservable progress,
ITransform oldValue,
ITransform newValue)
{
var oldTransform = TransformOperationsAnimator.EnsureOperations(oldValue);
var newTransform = TransformOperationsAnimator.EnsureOperations(newValue);
- return progress
- .Select(p =>
- {
- var f = Easing.Ease(p);
-
- return _operationsAnimator.Interpolate(f, oldTransform, newTransform);
- });
+ return new AnimatorTransitionObservable(
+ s_operationsAnimator, progress, Easing, oldTransform, newTransform);
}
}
}
diff --git a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs
index 5038117faa..596989fbae 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs
+++ b/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
///
/// Transition class that handles with type.
///
- public class VectorTransition : Transition
+ public class VectorTransition : AnimatorDrivenTransition
{
- private static readonly VectorAnimator s_animator = new VectorAnimator();
-
- ///
- public override IObservable DoTransition(IObservable progress, Vector oldValue, Vector newValue)
- {
- return progress
- .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
- }
}
}
diff --git a/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs
new file mode 100644
index 0000000000..f74457a924
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs
@@ -0,0 +1,78 @@
+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;
+
+namespace Avalonia.Benchmarks.Animations
+{
+ [MemoryDiagnoser]
+ public class TransitionBenchmark
+ {
+ private readonly AddValueObserver _observer;
+ private readonly List _producedValues;
+ private readonly Subject _timeProducer;
+ private readonly DoubleTransition _transition;
+
+ public TransitionBenchmark()
+ {
+ _transition = new DoubleTransition
+ {
+ Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty
+ };
+
+ _timeProducer = new Subject();
+ _producedValues = new List(FrameCount);
+
+ _observer = new AddValueObserver(_producedValues);
+ }
+
+ [Params(10, 100)] public int FrameCount { get; set; }
+
+ [Benchmark]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public void NewTransition()
+ {
+ var transitionObs = _transition.DoTransition(_timeProducer, 0, 1);
+
+ _producedValues.Clear();
+
+ using var transitionSub = transitionObs.Subscribe(_observer);
+
+ for (int i = 0; i < FrameCount; i++)
+ {
+ _timeProducer.OnNext(i / 1000d);
+ }
+
+ Debug.Assert(_producedValues.Count == FrameCount);
+ }
+
+ private class AddValueObserver : IObserver
+ {
+ private readonly List _values;
+
+ public AddValueObserver(List values)
+ {
+ _values = values;
+ }
+
+ public void OnCompleted()
+ {
+ }
+
+ public void OnError(Exception error)
+ {
+ }
+
+ public void OnNext(double value)
+ {
+ _values.Add(value);
+ }
+ }
+ }
+}