diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 7b3aa06ea0..e787143b59 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -154,12 +154,12 @@ namespace Avalonia.Animation } /// - public IDisposable Apply(Animatable control, IObservable match, Action onComplete) + public IDisposable Apply(Animatable control, Clock clock, IObservable match, Action onComplete) { var (animators, subscriptions) = InterpretKeyframes(control); if (animators.Count == 1) { - subscriptions.Add(animators[0].Apply(this, control, match, onComplete)); + subscriptions.Add(animators[0].Apply(this, control, clock, match, onComplete)); } else { @@ -173,7 +173,7 @@ namespace Avalonia.Animation animatorOnComplete = () => tcs.SetResult(null); completionTasks.Add(tcs.Task); } - subscriptions.Add(animator.Apply(this, control, match, animatorOnComplete)); + subscriptions.Add(animator.Apply(this, control, clock, match, animatorOnComplete)); } if (onComplete != null) @@ -185,15 +185,20 @@ namespace Avalonia.Animation } /// - public Task RunAsync(Animatable control) + public Task RunAsync(Animatable control, Clock clock = null) { + if (clock == null) + { + clock = Clock.GlobalClock; + } + var run = new TaskCompletionSource(); if (this.RepeatCount == RepeatCount.Loop) run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); IDisposable subscriptions = null; - subscriptions = this.Apply(control, Observable.Return(true), () => + subscriptions = this.Apply(control, clock, Observable.Return(true), () => { run.SetResult(null); subscriptions?.Dispose(); diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index a2d25e4524..fe84dd879a 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -34,8 +34,9 @@ namespace Avalonia.Animation private Action _onCompleteAction; private Func _interpolator; private IDisposable _timerSubscription; + private readonly Clock _clock; - public AnimationInstance(Animation animation, Animatable control, Animator animator, Action OnComplete, Func Interpolator) + public AnimationInstance(Animation animation, Animatable control, Animator animator, Clock clock, Action OnComplete, Func Interpolator) { if (animation.SpeedRatio <= 0) throw new InvalidOperationException("Speed ratio cannot be negative or zero."); @@ -71,6 +72,7 @@ namespace Avalonia.Animation _fillMode = animation.FillMode; _onCompleteAction = OnComplete; _interpolator = Interpolator; + _clock = clock; } protected override void Unsubscribed() @@ -80,7 +82,7 @@ namespace Avalonia.Animation protected override void Subscribed() { - _timerSubscription = Clock.GlobalClock.Subscribe(Step); + _timerSubscription = _clock.Subscribe(Step); } public void Step(TimeSpan frameTick) @@ -115,7 +117,7 @@ namespace Avalonia.Animation private void DoPlayStatesAndTime(TimeSpan systemTime) { - if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop) + if (_clock.PlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop) DoComplete(); if (!_gotFirstKFValue) diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index f0ef55aa9e..c699ff635a 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -32,7 +32,7 @@ namespace Avalonia.Animation } /// - public virtual IDisposable Apply(Animation animation, Animatable control, IObservable match, Action onComplete) + public virtual IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable match, Action onComplete) { if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); @@ -41,7 +41,7 @@ namespace Avalonia.Animation .Where(p => p) .Subscribe(_ => { - var timerObs = RunKeyFrames(animation, control, onComplete); + var timerObs = RunKeyFrames(animation, control, clock, onComplete); }); } @@ -101,9 +101,9 @@ namespace Avalonia.Animation /// /// Runs the KeyFrames Animation. /// - private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete) + private IDisposable RunKeyFrames(Animation animation, Animatable control, Clock clock, Action onComplete) { - var instance = new AnimationInstance(animation, control, this, onComplete, DoInterpolation); + var instance = new AnimationInstance(animation, control, this, clock, onComplete, DoInterpolation); return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index 2de781ea57..e8616c9694 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -58,6 +58,16 @@ namespace Avalonia.Animation _observable.Pulse(_internalTime); CurrentTime = _internalTime; + + if (PlayState == PlayState.Stop) + { + Stop(); + } + } + + protected virtual void Stop() + { + _parentSubscription?.Dispose(); } public IDisposable Subscribe(IObserver observer) diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index 1d545a322a..f726cf43dc 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -11,11 +11,11 @@ namespace Avalonia.Animation /// /// Apply the animation to the specified control /// - IDisposable Apply(Animatable control, IObservable match, Action onComplete = null); + IDisposable Apply(Animatable control, Clock clock, IObservable match, Action onComplete = null); /// /// Run the animation to the specified control /// - Task RunAsync(Animatable control); + Task RunAsync(Animatable control, Clock clock); } } diff --git a/src/Avalonia.Animation/IAnimator.cs b/src/Avalonia.Animation/IAnimator.cs index 9a4da35a02..134b30a555 100644 --- a/src/Avalonia.Animation/IAnimator.cs +++ b/src/Avalonia.Animation/IAnimator.cs @@ -16,6 +16,6 @@ namespace Avalonia.Animation /// /// Applies the current KeyFrame group to the specified control. /// - IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete); + IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable obsMatch, Action onComplete); } } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 399be5470d..a033184588 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -120,7 +120,7 @@ namespace Avalonia.Styling obsMatch = Observable.Return(true); } - var sub = animation.Apply((Animatable)control, obsMatch); + var sub = animation.Apply((Animatable)control, Clock.GlobalClock, obsMatch); subs.Add(sub); } diff --git a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs index d60d366ad4..d9ee269739 100644 --- a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs +++ b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs @@ -7,6 +7,11 @@ namespace Avalonia.Animation { public class RenderLoopClock : Clock, IRenderLoopTask { + protected override void Stop() + { + AvaloniaLocator.Current.GetService().Remove(this); + } + bool IRenderLoopTask.NeedsUpdate => HasSubscriptions; void IRenderLoopTask.Render() diff --git a/src/Avalonia.Visuals/Animation/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/TransformAnimator.cs index 2be1226abe..4476058bfe 100644 --- a/src/Avalonia.Visuals/Animation/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/TransformAnimator.cs @@ -12,7 +12,7 @@ namespace Avalonia.Animation DoubleAnimator childKeyFrames; /// - public override IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete) + public override IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable obsMatch, Action onComplete) { var ctrl = (Visual)control; @@ -44,7 +44,7 @@ namespace Avalonia.Animation // It's a transform object so let's target that. if (renderTransformType == Property.OwnerType) { - return childKeyFrames.Apply(animation, ctrl.RenderTransform, obsMatch, onComplete); + return childKeyFrames.Apply(animation, ctrl.RenderTransform, clock, obsMatch, onComplete); } // It's a TransformGroup and try finding the target there. else if (renderTransformType == typeof(TransformGroup)) @@ -53,7 +53,7 @@ namespace Avalonia.Animation { if (transform.GetType() == Property.OwnerType) { - return childKeyFrames.Apply(animation, transform, obsMatch, onComplete); + return childKeyFrames.Apply(animation, transform, clock, obsMatch, onComplete); } } }