Browse Source

Merge pull request #1774 from AvaloniaUI/awaitablerun-animations

Code-behind Animation Trigger
pull/1796/head
Jeremy Koritzinsky 8 years ago
committed by GitHub
parent
commit
03444597cb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 73
      src/Avalonia.Animation/Animation.cs
  2. 2
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  3. 7
      src/Avalonia.Animation/AnimatorStateMachine`1.cs
  4. 9
      src/Avalonia.Animation/Animator`1.cs
  5. 10
      src/Avalonia.Animation/IAnimation.cs
  6. 2
      src/Avalonia.Animation/IAnimator.cs
  7. 6
      src/Avalonia.Visuals/Animation/TransformAnimator.cs

73
src/Avalonia.Animation/Animation.cs

@ -10,13 +10,16 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Reflection; using System.Reflection;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Reactive.Linq;
using System.Reactive.Disposables;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
/// Tracks the progress of an animation. /// Tracks the progress of an animation.
/// </summary> /// </summary>
public class Animation : AvaloniaList<KeyFrame>, IDisposable, IAnimation public class Animation : AvaloniaList<KeyFrame>, IAnimation
{ {
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)> private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
{ {
@ -24,7 +27,7 @@ namespace Avalonia.Animation
}; };
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition) public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
where TAnimator: IAnimator where TAnimator : IAnimator
{ {
Animators.Insert(0, (condition, typeof(TAnimator))); Animators.Insert(0, (condition, typeof(TAnimator)));
} }
@ -41,8 +44,6 @@ namespace Avalonia.Animation
return null; return null;
} }
private bool _isChildrenChanged = false;
private List<IDisposable> _subscription = new List<IDisposable>();
public AvaloniaList<IAnimator> _animators { get; set; } = new AvaloniaList<IAnimator>(); public AvaloniaList<IAnimator> _animators { get; set; } = new AvaloniaList<IAnimator>();
/// <summary> /// <summary>
@ -72,18 +73,14 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Easing function to be used. /// Easing function to be used.
/// </summary> /// </summary>
public Easing Easing { get; set; } = new LinearEasing(); public Easing Easing { get; set; } = new LinearEasing();
public Animation() private (IList<IAnimator> Animators, IList<IDisposable> subscriptions) InterpretKeyframes(Animatable control)
{
this.CollectionChanged += delegate { _isChildrenChanged = true; };
}
private IList<IAnimator> InterpretKeyframes(Animatable control)
{ {
var handlerList = new List<(Type type, AvaloniaProperty property)>(); var handlerList = new List<(Type type, AvaloniaProperty property)>();
var animatorKeyFrames = new List<AnimatorKeyFrame>(); var animatorKeyFrames = new List<AnimatorKeyFrame>();
var subscriptions = new List<IDisposable>();
foreach (var keyframe in this) foreach (var keyframe in this)
{ {
@ -108,7 +105,7 @@ namespace Avalonia.Animation
var newKF = new AnimatorKeyFrame(handler, cue); var newKF = new AnimatorKeyFrame(handler, cue);
_subscription.Add(newKF.BindSetter(setter, control)); subscriptions.Add(newKF.BindSetter(setter, control));
animatorKeyFrames.Add(newKF); animatorKeyFrames.Add(newKF);
} }
@ -130,28 +127,56 @@ namespace Avalonia.Animation
animator.Add(keyframe); animator.Add(keyframe);
} }
return newAnimatorInstances; return (newAnimatorInstances, subscriptions);
} }
/// <summary> /// <inheritdocs/>
/// Cancels the animation. public IDisposable Apply(Animatable control, IObservable<bool> match, Action onComplete)
/// </summary>
public void Dispose()
{ {
foreach (var sub in _subscription) var (animators, subscriptions) = InterpretKeyframes(control);
if (animators.Count == 1)
{ {
sub.Dispose(); subscriptions.Add(animators[0].Apply(this, control, match, onComplete));
} }
else
{
var completionTasks = onComplete != null ? new List<Task>() : null;
foreach (IAnimator animator in animators)
{
Action animatorOnComplete = null;
if (onComplete != null)
{
var tcs = new TaskCompletionSource<object>();
animatorOnComplete = () => tcs.SetResult(null);
completionTasks.Add(tcs.Task);
}
subscriptions.Add(animator.Apply(this, control, match, animatorOnComplete));
}
if (onComplete != null)
{
Task.WhenAll(completionTasks).ContinueWith(_ => onComplete());
}
}
return new CompositeDisposable(subscriptions);
} }
/// <inheritdocs/> /// <inheritdocs/>
public IDisposable Apply(Animatable control, IObservable<bool> matchObs) public Task RunAsync(Animatable control)
{ {
foreach (IAnimator animator in InterpretKeyframes(control)) var run = new TaskCompletionSource<object>();
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), () =>
{ {
_subscription.Add(animator.Apply(this, control, matchObs)); run.SetResult(null);
} subscriptions?.Dispose();
return this; });
return run.Task;
} }
} }
} }

2
src/Avalonia.Animation/AnimatorKeyFrame.cs

@ -16,7 +16,7 @@ namespace Avalonia.Animation
public class AnimatorKeyFrame : AvaloniaObject public class AnimatorKeyFrame : AvaloniaObject
{ {
public static readonly DirectProperty<AnimatorKeyFrame, object> ValueProperty = public static readonly DirectProperty<AnimatorKeyFrame, object> ValueProperty =
AvaloniaProperty.RegisterDirect<AnimatorKeyFrame, object>(nameof(Value), k => k._value, (k, v) => k._value = v); AvaloniaProperty.RegisterDirect<AnimatorKeyFrame, object>(nameof(Value), k => k.Value, (k, v) => k.Value = v);
public AnimatorKeyFrame() public AnimatorKeyFrame()
{ {

7
src/Avalonia.Animation/AnimatorStateMachine`1.cs

@ -35,6 +35,7 @@ namespace Avalonia.Animation
private T _neutralValue; private T _neutralValue;
internal bool _unsubscribe = false; internal bool _unsubscribe = false;
private IObserver<object> _targetObserver; private IObserver<object> _targetObserver;
private readonly Action _onComplete;
[Flags] [Flags]
private enum KeyFramesStates private enum KeyFramesStates
@ -51,7 +52,7 @@ namespace Avalonia.Animation
Disposed Disposed
} }
public void Initialize(Animation animation, Animatable control, Animator<T> animator) public AnimatorStateMachine(Animation animation, Animatable control, Animator<T> animator, Action onComplete)
{ {
_parent = animator; _parent = animator;
_targetAnimation = animation; _targetAnimation = animation;
@ -82,6 +83,7 @@ namespace Avalonia.Animation
_currentState = KeyFramesStates.DoDelay; _currentState = KeyFramesStates.DoDelay;
else else
_currentState = KeyFramesStates.DoRun; _currentState = KeyFramesStates.DoRun;
_onComplete = onComplete;
} }
public void Step(PlayState _playState, Func<double, T, T> Interpolator) public void Step(PlayState _playState, Func<double, T, T> Interpolator)
@ -243,7 +245,10 @@ namespace Avalonia.Animation
{ {
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue); _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
} }
_targetObserver.OnCompleted(); _targetObserver.OnCompleted();
_onComplete?.Invoke();
Dispose();
handled = true; handled = true;
break; break;
default: default:

9
src/Avalonia.Animation/Animator`1.cs

@ -35,7 +35,7 @@ namespace Avalonia.Animation
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch) public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch, Action onComplete)
{ {
if (!_isVerfifiedAndConverted) if (!_isVerfifiedAndConverted)
VerifyConvertKeyFrames(); VerifyConvertKeyFrames();
@ -45,7 +45,7 @@ namespace Avalonia.Animation
.Where(p => p && Timing.GetGlobalPlayState() != PlayState.Pause) .Where(p => p && Timing.GetGlobalPlayState() != PlayState.Pause)
.Subscribe(_ => .Subscribe(_ =>
{ {
var timerObs = RunKeyFrames(animation, control); var timerObs = RunKeyFrames(animation, control, onComplete);
}); });
} }
@ -97,10 +97,9 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Runs the KeyFrames Animation. /// Runs the KeyFrames Animation.
/// </summary> /// </summary>
private IDisposable RunKeyFrames(Animation animation, Animatable control) private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete)
{ {
var stateMachine = new AnimatorStateMachine<T>(); var stateMachine = new AnimatorStateMachine<T>(animation, control, this, onComplete);
stateMachine.Initialize(animation, control, this);
Timing.AnimationStateTimer Timing.AnimationStateTimer
.TakeWhile(_ => !stateMachine._unsubscribe) .TakeWhile(_ => !stateMachine._unsubscribe)

10
src/Avalonia.Animation/IAnimation.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
@ -12,6 +13,11 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Apply the animation to the specified control /// Apply the animation to the specified control
/// </summary> /// </summary>
IDisposable Apply(Animatable control, IObservable<bool> match); IDisposable Apply(Animatable control, IObservable<bool> match, Action onComplete = null);
/// <summary>
/// Run the animation to the specified control
/// </summary>
Task RunAsync(Animatable control);
} }
} }

2
src/Avalonia.Animation/IAnimator.cs

@ -17,6 +17,6 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Applies the current KeyFrame group to the specified control. /// Applies the current KeyFrame group to the specified control.
/// </summary> /// </summary>
IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch); IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch, Action onComplete);
} }
} }

6
src/Avalonia.Visuals/Animation/TransformAnimator.cs

@ -19,7 +19,7 @@ namespace Avalonia.Animation
DoubleAnimator childKeyFrames; DoubleAnimator childKeyFrames;
/// <inheritdoc/> /// <inheritdoc/>
public override IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch) public override IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch, Action onComplete)
{ {
var ctrl = (Visual)control; var ctrl = (Visual)control;
@ -51,7 +51,7 @@ namespace Avalonia.Animation
// It's a transform object so let's target that. // It's a transform object so let's target that.
if (renderTransformType == Property.OwnerType) 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. // It's a TransformGroup and try finding the target there.
else if (renderTransformType == typeof(TransformGroup)) else if (renderTransformType == typeof(TransformGroup))
@ -60,7 +60,7 @@ namespace Avalonia.Animation
{ {
if (transform.GetType() == Property.OwnerType) if (transform.GetType() == Property.OwnerType)
{ {
return childKeyFrames.Apply(animation, transform, obsMatch); return childKeyFrames.Apply(animation, transform, obsMatch, onComplete);
} }
} }
} }

Loading…
Cancel
Save