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.Reflection;
using System.Linq;
using System.Threading.Tasks;
using System.Reactive.Linq;
using System.Reactive.Disposables;
namespace Avalonia.Animation
{
/// <summary>
/// Tracks the progress of an animation.
/// </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)>
{
@ -24,7 +27,7 @@ namespace Avalonia.Animation
};
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
where TAnimator: IAnimator
where TAnimator : IAnimator
{
Animators.Insert(0, (condition, typeof(TAnimator)));
}
@ -41,8 +44,6 @@ namespace Avalonia.Animation
return null;
}
private bool _isChildrenChanged = false;
private List<IDisposable> _subscription = new List<IDisposable>();
public AvaloniaList<IAnimator> _animators { get; set; } = new AvaloniaList<IAnimator>();
/// <summary>
@ -72,18 +73,14 @@ namespace Avalonia.Animation
/// <summary>
/// Easing function to be used.
/// </summary>
/// </summary>
public Easing Easing { get; set; } = new LinearEasing();
public Animation()
{
this.CollectionChanged += delegate { _isChildrenChanged = true; };
}
private IList<IAnimator> InterpretKeyframes(Animatable control)
private (IList<IAnimator> Animators, IList<IDisposable> subscriptions) InterpretKeyframes(Animatable control)
{
var handlerList = new List<(Type type, AvaloniaProperty property)>();
var animatorKeyFrames = new List<AnimatorKeyFrame>();
var subscriptions = new List<IDisposable>();
foreach (var keyframe in this)
{
@ -108,7 +105,7 @@ namespace Avalonia.Animation
var newKF = new AnimatorKeyFrame(handler, cue);
_subscription.Add(newKF.BindSetter(setter, control));
subscriptions.Add(newKF.BindSetter(setter, control));
animatorKeyFrames.Add(newKF);
}
@ -130,28 +127,56 @@ namespace Avalonia.Animation
animator.Add(keyframe);
}
return newAnimatorInstances;
return (newAnimatorInstances, subscriptions);
}
/// <summary>
/// Cancels the animation.
/// </summary>
public void Dispose()
/// <inheritdocs/>
public IDisposable Apply(Animatable control, IObservable<bool> match, Action onComplete)
{
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/>
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));
}
return this;
run.SetResult(null);
subscriptions?.Dispose();
});
return run.Task;
}
}
}

2
src/Avalonia.Animation/AnimatorKeyFrame.cs

@ -16,7 +16,7 @@ namespace Avalonia.Animation
public class AnimatorKeyFrame : AvaloniaObject
{
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()
{

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

@ -35,6 +35,7 @@ namespace Avalonia.Animation
private T _neutralValue;
internal bool _unsubscribe = false;
private IObserver<object> _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<T> animator)
public AnimatorStateMachine(Animation animation, Animatable control, Animator<T> 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<double, T, T> Interpolator)
@ -243,7 +245,10 @@ namespace Avalonia.Animation
{
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
}
_targetObserver.OnCompleted();
_onComplete?.Invoke();
Dispose();
handled = true;
break;
default:

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

@ -35,7 +35,7 @@ namespace Avalonia.Animation
}
/// <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)
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
/// <summary>
/// Runs the KeyFrames Animation.
/// </summary>
private IDisposable RunKeyFrames(Animation animation, Animatable control)
private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete)
{
var stateMachine = new AnimatorStateMachine<T>();
stateMachine.Initialize(animation, control, this);
var stateMachine = new AnimatorStateMachine<T>(animation, control, this, onComplete);
Timing.AnimationStateTimer
.TakeWhile(_ => !stateMachine._unsubscribe)

10
src/Avalonia.Animation/IAnimation.cs

@ -1,6 +1,7 @@
using System;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Animation
{
@ -12,6 +13,11 @@ namespace Avalonia.Animation
/// <summary>
/// Apply the animation to the specified control
/// </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>
/// Applies the current KeyFrame group to the specified control.
/// </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;
/// <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;
@ -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);
}
}
}

Loading…
Cancel
Save