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