From 2345818aab00359a51375157bbad978e66e1b079 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 27 Jul 2018 16:40:45 -0500 Subject: [PATCH] Change completion to be notified via a callback. Only notify that an animation is complete if all animators used in the animation are complete. --- src/Avalonia.Animation/Animation.cs | 57 ++++++++----------- .../AnimatorStateMachine`1.cs | 6 +- src/Avalonia.Animation/Animator`1.cs | 9 ++- src/Avalonia.Animation/IAnimation.cs | 4 +- src/Avalonia.Animation/IAnimator.cs | 2 +- .../Animation/TransformAnimator.cs | 6 +- 6 files changed, 39 insertions(+), 45 deletions(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 98fb652c4a..c228a49ec7 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -43,7 +43,6 @@ namespace Avalonia.Animation return null; } - private bool _isChildrenChanged = false; private List _subscription = new List(); public AvaloniaList _animators { get; set; } = new AvaloniaList(); @@ -77,16 +76,6 @@ namespace Avalonia.Animation /// public Easing Easing { get; set; } = new LinearEasing(); - /// - /// Triggers when the animation is completed. - /// - public event EventHandler Done; - - public Animation() - { - this.CollectionChanged += delegate { _isChildrenChanged = true; }; - } - private IList InterpretKeyframes(Animatable control) { var handlerList = new List<(Type type, AvaloniaProperty property)>(); @@ -152,11 +141,32 @@ namespace Avalonia.Animation } /// - public IDisposable Apply(Animatable control, IObservable matchObs) + public IDisposable Apply(Animatable control, IObservable match, Action onComplete) { - foreach (IAnimator animator in InterpretKeyframes(control)) + var animators = InterpretKeyframes(control); + if (animators.Count == 1) { - _subscription.Add(animator.Apply(this, control, matchObs)); + _subscription.Add(animators[0].Apply(this, control, match, onComplete)); + } + else + { + var completionTasks = onComplete != null ? new List() : null; + foreach (IAnimator animator in InterpretKeyframes(control)) + { + Action animatorOnComplete = null; + if (onComplete != null) + { + var tcs = new TaskCompletionSource(); + animatorOnComplete = () => tcs.SetResult(null); + completionTasks.Add(tcs.Task); + } + _subscription.Add(animator.Apply(this, control, match, animatorOnComplete)); + } + + if (onComplete != null) + { + Task.WhenAll(completionTasks).ContinueWith(_ => onComplete()); + } } return this; } @@ -169,26 +179,9 @@ namespace Avalonia.Animation if (this.RepeatCount == RepeatCount.Loop) run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); - EventHandler doneCallback = null; - doneCallback = (sender, args) => - { - if (sender == control) - { - run.SetResult(null); - this.Done -= doneCallback; - } - }; - - this.Done += doneCallback; - - this.Apply(control, Observable.Return(true)); + this.Apply(control, Observable.Return(true), () => run.SetResult(null)); return run.Task; } - - internal void SetDone(Animatable control) - { - Done?.Invoke(control, null); - } } } diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 4bffd5c145..87e189c997 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -35,6 +35,7 @@ namespace Avalonia.Animation private T _neutralValue; internal bool _unsubscribe = false; private IObserver _targetObserver; + private readonly Action _onComplete; [Flags] private enum KeyFramesStates @@ -51,7 +52,7 @@ namespace Avalonia.Animation Disposed } - public void Initialize(Animation animation, Animatable control, Animator animator) + public AnimatorStateMachine(Animation animation, Animatable control, Animator animator, Action onComplete) { _parent = animator; _targetAnimation = animation; @@ -82,6 +83,7 @@ namespace Avalonia.Animation _currentState = KeyFramesStates.DoDelay; else _currentState = KeyFramesStates.DoRun; + _onComplete = onComplete; } public void Step(PlayState _playState, Func Interpolator) @@ -245,7 +247,7 @@ namespace Avalonia.Animation } _targetObserver.OnCompleted(); - _targetAnimation.SetDone(_targetControl); + _onComplete?.Invoke(); Dispose(); handled = true; break; diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index a1eef87e1e..eb8b40647d 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -35,7 +35,7 @@ namespace Avalonia.Animation } /// - public virtual IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch) + public virtual IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete) { if (!_isVerfifiedAndConverted) VerifyConvertKeyFrames(); @@ -45,7 +45,7 @@ namespace Avalonia.Animation .Where(p => p && Timing.GetGlobalPlayState() != PlayState.Pause) .Subscribe(_ => { - var timerObs = RunKeyFrames(animation, control); + var timerObs = RunKeyFrames(animation, control, onComplete); }); } @@ -97,10 +97,9 @@ namespace Avalonia.Animation /// /// Runs the KeyFrames Animation. /// - private IDisposable RunKeyFrames(Animation animation, Animatable control) + private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete) { - var stateMachine = new AnimatorStateMachine(); - stateMachine.Initialize(animation, control, this); + var stateMachine = new AnimatorStateMachine(animation, control, this, onComplete); Timing.AnimationStateTimer .TakeWhile(_ => !stateMachine._unsubscribe) diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index 734eb3e479..905d90fa52 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -13,11 +13,11 @@ namespace Avalonia.Animation /// /// Apply the animation to the specified control /// - IDisposable Apply(Animatable control, IObservable match); + IDisposable Apply(Animatable control, IObservable match, Action onComplete = null); /// /// Run the animation to the specified control /// Task RunAsync(Animatable control); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Animation/IAnimator.cs b/src/Avalonia.Animation/IAnimator.cs index 6acca4d697..8b763db603 100644 --- a/src/Avalonia.Animation/IAnimator.cs +++ b/src/Avalonia.Animation/IAnimator.cs @@ -17,6 +17,6 @@ namespace Avalonia.Animation /// /// Applies the current KeyFrame group to the specified control. /// - IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch); + IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete); } } diff --git a/src/Avalonia.Visuals/Animation/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/TransformAnimator.cs index 46cefbd061..61cac695b1 100644 --- a/src/Avalonia.Visuals/Animation/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/TransformAnimator.cs @@ -19,7 +19,7 @@ namespace Avalonia.Animation DoubleAnimator childKeyFrames; /// - public override IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch) + public override IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete) { var ctrl = (Visual)control; @@ -51,7 +51,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); + return childKeyFrames.Apply(animation, ctrl.RenderTransform, obsMatch, onComplete); } // It's a TransformGroup and try finding the target there. else if (renderTransformType == typeof(TransformGroup)) @@ -60,7 +60,7 @@ namespace Avalonia.Animation { if (transform.GetType() == Property.OwnerType) { - return childKeyFrames.Apply(animation, transform, obsMatch); + return childKeyFrames.Apply(animation, transform, obsMatch, onComplete); } } }