diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs
index 4176bf01e5..3e030bf765 100644
--- a/src/Avalonia.Animation/Animatable.cs
+++ b/src/Avalonia.Animation/Animatable.cs
@@ -14,15 +14,7 @@ namespace Avalonia.Animation
/// Base class for control which can have property transitions.
///
public class Animatable : AvaloniaObject
- {
- ///
- /// Initializes this object.
- ///
- public Animatable()
- {
- Transitions = new Transitions();
- }
-
+ {
///
/// Defines the property.
///
@@ -42,27 +34,25 @@ namespace Avalonia.Animation
{
get { return _playState; }
set { SetAndRaise(PlayStateProperty, ref _playState, value); }
-
}
-
///
/// Defines the property.
///
- public static readonly DirectProperty> TransitionsProperty =
- AvaloniaProperty.RegisterDirect>(
+ public static readonly DirectProperty TransitionsProperty =
+ AvaloniaProperty.RegisterDirect(
nameof(Transitions),
o => o.Transitions,
(o, v) => o.Transitions = v);
- private IEnumerable _transitions = new AvaloniaList();
+ private Transitions _transitions;
///
/// Gets or sets the property transitions for the control.
///
- public IEnumerable Transitions
+ public Transitions Transitions
{
- get { return _transitions; }
+ get { return _transitions ?? (_transitions = new Transitions()); }
set { SetAndRaise(TransitionsProperty, ref _transitions, value); }
}
@@ -83,6 +73,5 @@ namespace Avalonia.Animation
}
}
}
-
}
-}
+}
\ No newline at end of file
diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs
index e23caf95f1..2c359ecac3 100644
--- a/src/Avalonia.Animation/Animation.cs
+++ b/src/Avalonia.Animation/Animation.cs
@@ -22,8 +22,6 @@ namespace Avalonia.Animation
///
public static PlayState GlobalPlayState { get; set; } = PlayState.Run;
- public AvaloniaList _animators { get; set; } = new AvaloniaList();
-
///
/// Gets or sets the active time of this animation.
///
diff --git a/src/Avalonia.Animation/AnimationsEngine`1.cs b/src/Avalonia.Animation/AnimationsEngine`1.cs
index cccb3098c0..156056d765 100644
--- a/src/Avalonia.Animation/AnimationsEngine`1.cs
+++ b/src/Avalonia.Animation/AnimationsEngine`1.cs
@@ -1,7 +1,9 @@
using System;
using System.Linq;
+using System.Reactive.Linq;
using Avalonia.Animation.Utils;
using Avalonia.Data;
+using Avalonia.Reactive;
namespace Avalonia.Animation
{
@@ -9,182 +11,184 @@ namespace Avalonia.Animation
/// Handles interpolatoin and time-related functions
/// for keyframe animations.
///
- internal class AnimationsEngine : IObservable, IDisposable
+ internal class AnimationsEngine : SingleSubscriberObservableBase
{
- T lastInterpValue;
- T firstKFValue;
-
- private long repeatCount;
- private double currentIteration;
-
- private bool isLooping;
- private bool gotFirstKFValue;
- private bool gotFirstFrameCount;
- private bool iterationDelay;
-
- private FillMode fillMode;
- private PlaybackDirection animationDirection;
- private Animator parent;
- private Animatable targetControl;
- private T neutralValue;
- private double speedRatio;
- internal bool unsubscribe;
- private bool isDisposed;
-
- private TimeSpan delay;
- private TimeSpan duration;
- private TimeSpan firstFrameCount;
- private TimeSpan internalClock;
- private TimeSpan? previousClock;
-
- private Easings.Easing easeFunc;
- private IObserver targetObserver;
- private readonly Action onCompleteAction;
-
- public AnimationsEngine(Animation animation, Animatable control, Animator animator, Action OnComplete)
+ private T _lastInterpValue;
+ private T _firstKFValue;
+ private long _repeatCount;
+ private double _currentIteration;
+ private bool _isLooping;
+ private bool _gotFirstKFValue;
+ private bool _gotFirstFrameCount;
+ private bool _iterationDelay;
+ private FillMode _fillMode;
+ private PlaybackDirection _animationDirection;
+ private Animator _parent;
+ private Animatable _targetControl;
+ private T _neutralValue;
+ private double _speedRatio;
+ private TimeSpan _delay;
+ private TimeSpan _duration;
+ private TimeSpan _firstFrameCount;
+ private TimeSpan _internalClock;
+ private TimeSpan? _previousClock;
+ private Easings.Easing _easeFunc;
+ private Action _onCompleteAction;
+ private Func _interpolator;
+ private IDisposable _timerSubscription;
+
+ public AnimationsEngine(Animation animation, Animatable control, Animator animator, Action OnComplete, Func Interpolator)
{
if (animation.SpeedRatio <= 0 || DoubleUtils.AboutEqual(animation.SpeedRatio, 0))
throw new InvalidOperationException("Speed ratio cannot be negative or zero.");
if (animation.Duration.TotalSeconds <= 0 || DoubleUtils.AboutEqual(animation.Duration.TotalSeconds, 0))
throw new InvalidOperationException("Duration cannot be negative or zero.");
-
- parent = animator;
- easeFunc = animation.Easing;
- targetControl = control;
- neutralValue = (T)targetControl.GetValue(parent.Property);
- speedRatio = animation.SpeedRatio;
+ _parent = animator;
+ _easeFunc = animation.Easing;
+ _targetControl = control;
+ _neutralValue = (T)_targetControl.GetValue(_parent.Property);
+
+ _speedRatio = animation.SpeedRatio;
- delay = animation.Delay;
- duration = animation.Duration;
- iterationDelay = animation.DelayBetweenIterations;
+ _delay = animation.Delay;
+ _duration = animation.Duration;
+ _iterationDelay = animation.DelayBetweenIterations;
switch (animation.RepeatCount.RepeatType)
{
case RepeatType.None:
- repeatCount = 1;
+ _repeatCount = 1;
break;
case RepeatType.Loop:
- isLooping = true;
+ _isLooping = true;
break;
case RepeatType.Repeat:
- repeatCount = (long)animation.RepeatCount.Value;
+ _repeatCount = (long)animation.RepeatCount.Value;
break;
}
- animationDirection = animation.PlaybackDirection;
- fillMode = animation.FillMode;
- onCompleteAction = OnComplete;
+ _animationDirection = animation.PlaybackDirection;
+ _fillMode = animation.FillMode;
+ _onCompleteAction = OnComplete;
+ _interpolator = Interpolator;
+ }
+
+ protected override void Unsubscribed()
+ {
+ _timerSubscription?.Dispose();
}
- public void Step(TimeSpan frameTick, Func Interpolator)
+ protected override void Subscribed()
+ {
+ _timerSubscription = Timing.AnimationsTimer
+ .Subscribe(p => this.Step(p));
+ }
+
+ public void Step(TimeSpan frameTick)
{
try
{
- InternalStep(frameTick, Interpolator);
+ InternalStep(frameTick);
}
catch (Exception e)
{
- targetObserver?.OnError(e);
+ PublishError(e);
}
}
private void DoComplete()
{
- if (fillMode == FillMode.Forward || fillMode == FillMode.Both)
- targetControl.SetValue(parent.Property, lastInterpValue, BindingPriority.LocalValue);
+ if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both)
+ _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
- targetObserver.OnCompleted();
- onCompleteAction?.Invoke();
- Dispose();
+ _onCompleteAction?.Invoke();
+ PublishCompleted();
}
private void DoDelay()
{
- if (fillMode == FillMode.Backward || fillMode == FillMode.Both)
- if (currentIteration == 0)
- targetObserver.OnNext(firstKFValue);
+ if (_fillMode == FillMode.Backward || _fillMode == FillMode.Both)
+ if (_currentIteration == 0)
+ PublishNext(_firstKFValue);
else
- targetObserver.OnNext(lastInterpValue);
+ PublishNext(_lastInterpValue);
}
private void DoPlayStatesAndTime(TimeSpan systemTime)
{
- if (Animation.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop)
+ if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop)
DoComplete();
- if (!previousClock.HasValue)
+ if (!_previousClock.HasValue)
{
- previousClock = systemTime;
- internalClock = TimeSpan.Zero;
+ _previousClock = systemTime;
+ _internalClock = TimeSpan.Zero;
}
else
{
- if (Animation.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause)
+ if (Animation.GlobalPlayState == PlayState.Pause || _targetControl.PlayState == PlayState.Pause)
{
- previousClock = systemTime;
+ _previousClock = systemTime;
return;
}
- var delta = systemTime - previousClock;
- internalClock += delta.Value;
- previousClock = systemTime;
+ var delta = systemTime - _previousClock;
+ _internalClock += delta.Value;
+ _previousClock = systemTime;
}
- if (!gotFirstKFValue)
+ if (!_gotFirstKFValue)
{
- firstKFValue = (T)parent.First().Value;
- gotFirstKFValue = true;
+ _firstKFValue = (T)_parent.First().Value;
+ _gotFirstKFValue = true;
}
- if (!gotFirstFrameCount)
+ if (!_gotFirstFrameCount)
{
- firstFrameCount = internalClock;
- gotFirstFrameCount = true;
+ _firstFrameCount = _internalClock;
+ _gotFirstFrameCount = true;
}
}
- private void InternalStep(TimeSpan systemTime, Func Interpolator)
+ private void InternalStep(TimeSpan systemTime)
{
DoPlayStatesAndTime(systemTime);
-
- if (isDisposed)
- throw new InvalidProgramException("This KeyFrames Animation is already disposed.");
-
- var time = internalClock - firstFrameCount;
- var delayEndpoint = delay;
- var iterationEndpoint = delayEndpoint + duration;
+
+ var time = _internalClock - _firstFrameCount;
+ var delayEndpoint = _delay;
+ var iterationEndpoint = delayEndpoint + _duration;
//determine if time is currently in the first iteration.
if (time >= TimeSpan.Zero & time <= iterationEndpoint)
{
- currentIteration = 1;
+ _currentIteration = 1;
}
else if (time > iterationEndpoint)
{
//Subtract first iteration to properly get the subsequent iteration time
time -= iterationEndpoint;
- if (!iterationDelay & delayEndpoint > TimeSpan.Zero)
+ if (!_iterationDelay & delayEndpoint > TimeSpan.Zero)
{
delayEndpoint = TimeSpan.Zero;
- iterationEndpoint = duration;
+ iterationEndpoint = _duration;
}
//Calculate the current iteration number
- currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2;
+ _currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2;
}
else
{
- previousClock = systemTime;
+ _previousClock = systemTime;
return;
}
time = TimeSpan.FromTicks(time.Ticks % iterationEndpoint.Ticks);
- if (!isLooping)
+ if (!_isLooping)
{
- if (currentIteration > repeatCount)
+ if (_currentIteration > _repeatCount)
DoComplete();
if (time > iterationEndpoint)
@@ -192,10 +196,10 @@ namespace Avalonia.Animation
}
// Determine if the current iteration should have its normalized time inverted.
- bool isCurIterReverse = animationDirection == PlaybackDirection.Normal ? false :
- animationDirection == PlaybackDirection.Alternate ? (currentIteration % 2 == 0) ? false : true :
- animationDirection == PlaybackDirection.AlternateReverse ? (currentIteration % 2 == 0) ? true : false :
- animationDirection == PlaybackDirection.Reverse ? true : false;
+ bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false :
+ _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true :
+ _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false :
+ _animationDirection == PlaybackDirection.Reverse ? true : false;
if (delayEndpoint > TimeSpan.Zero & time < delayEndpoint)
{
@@ -214,23 +218,11 @@ namespace Avalonia.Animation
interpVal = 1 - interpVal;
// Ease and interpolate
- var easedTime = easeFunc.Ease(interpVal);
- lastInterpValue = Interpolator(easedTime, neutralValue);
+ var easedTime = _easeFunc.Ease(interpVal);
+ _lastInterpValue = _interpolator(easedTime, _neutralValue);
- targetObserver.OnNext(lastInterpValue);
+ PublishNext(_lastInterpValue);
}
}
-
- public IDisposable Subscribe(IObserver observer)
- {
- targetObserver = observer;
- return this;
- }
-
- public void Dispose()
- {
- unsubscribe = true;
- isDisposed = true;
- }
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs
index 051b1e50cf..77d761ce4c 100644
--- a/src/Avalonia.Animation/Animator`1.cs
+++ b/src/Avalonia.Animation/Animator`1.cs
@@ -32,12 +32,12 @@ namespace Avalonia.Animation
}
///
- public virtual IDisposable Apply(Animation animation, Animatable control, IObservable Match, Action onComplete)
+ public virtual IDisposable Apply(Animation animation, Animatable control, IObservable match, Action onComplete)
{
if (!_isVerifiedAndConverted)
VerifyConvertKeyFrames();
- return Match
+ return match
.Where(p => p)
.Subscribe(_ =>
{
@@ -103,12 +103,7 @@ namespace Avalonia.Animation
///
private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete)
{
- var stateMachine = new AnimationsEngine(animation, control, this, onComplete);
-
- Timing.AnimationsTimer
- .TakeWhile(_ => !stateMachine.unsubscribe)
- .Subscribe(p => stateMachine.Step(p, DoInterpolation));
-
+ var stateMachine = new AnimationsEngine(animation, control, this, onComplete, DoInterpolation);
return control.Bind((AvaloniaProperty)Property, stateMachine, BindingPriority.Animation);
}
diff --git a/src/Avalonia.Animation/TransitionsEngine.cs b/src/Avalonia.Animation/TransitionsEngine.cs
index 4d52b2bd48..34863e4146 100644
--- a/src/Avalonia.Animation/TransitionsEngine.cs
+++ b/src/Avalonia.Animation/TransitionsEngine.cs
@@ -6,12 +6,15 @@ using System;
using System.Reactive.Linq;
using Avalonia.Animation.Easings;
using Avalonia.Animation.Utils;
+using Avalonia.Reactive;
namespace Avalonia.Animation
{
- public class TransitionsEngine : IObservable, IDisposable
+ ///
+ /// Handles the timing and lifetime of a .
+ ///
+ public class TransitionsEngine : SingleSubscriberObservableBase
{
- private IObserver observer;
private IDisposable timerSubscription;
private readonly TimeSpan startTime;
private readonly TimeSpan duration;
@@ -20,10 +23,6 @@ namespace Avalonia.Animation
{
startTime = Timing.GetTickCount();
duration = Duration;
-
- timerSubscription = Timing
- .AnimationsTimer
- .Subscribe(t => TimerTick(t));
}
private void TimerTick(TimeSpan t)
@@ -33,26 +32,23 @@ namespace Avalonia.Animation
if (interpVal > 1d
|| interpVal < 0d)
{
- this.Dispose();
+ PublishCompleted();
return;
}
- observer?.OnNext(interpVal);
+ PublishNext(interpVal);
}
-
- public void Dispose()
+
+ protected override void Unsubscribed()
{
timerSubscription?.Dispose();
- observer?.OnCompleted();
}
- public IDisposable Subscribe(IObserver Observer)
+ protected override void Subscribed()
{
- if (Observer is null)
- throw new InvalidProgramException("Can only set the subscription once.");
-
- observer = Observer;
- return this;
+ timerSubscription = Timing
+ .AnimationsTimer
+ .Subscribe(t => TimerTick(t));
}
}
}
\ No newline at end of file