|
|
|
@ -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; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|