using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using Avalonia.Animation.Animators; using Avalonia.Animation.Easings; using Avalonia.Data; using Avalonia.Metadata; namespace Avalonia.Animation { /// /// Tracks the progress of an animation. /// public class Animation : AvaloniaObject, IAnimation { /// /// Defines the property. /// public static readonly DirectProperty DurationProperty = AvaloniaProperty.RegisterDirect( nameof(Duration), o => o._duration, (o, v) => o._duration = v); /// /// Defines the property. /// public static readonly DirectProperty IterationCountProperty = AvaloniaProperty.RegisterDirect( nameof(IterationCount), o => o._iterationCount, (o, v) => o._iterationCount = v); /// /// Defines the property. /// public static readonly DirectProperty PlaybackDirectionProperty = AvaloniaProperty.RegisterDirect( nameof(PlaybackDirection), o => o._playbackDirection, (o, v) => o._playbackDirection = v); /// /// Defines the property. /// public static readonly DirectProperty FillModeProperty = AvaloniaProperty.RegisterDirect( nameof(FillMode), o => o._fillMode, (o, v) => o._fillMode = v); /// /// Defines the property. /// public static readonly DirectProperty EasingProperty = AvaloniaProperty.RegisterDirect( nameof(Easing), o => o._easing, (o, v) => o._easing = v); /// /// Defines the property. /// public static readonly DirectProperty DelayProperty = AvaloniaProperty.RegisterDirect( nameof(Delay), o => o._delay, (o, v) => o._delay = v); /// /// Defines the property. /// public static readonly DirectProperty DelayBetweenIterationsProperty = AvaloniaProperty.RegisterDirect( nameof(DelayBetweenIterations), o => o._delayBetweenIterations, (o, v) => o._delayBetweenIterations = v); /// /// Defines the property. /// public static readonly DirectProperty SpeedRatioProperty = AvaloniaProperty.RegisterDirect( nameof(SpeedRatio), o => o._speedRatio, (o, v) => o._speedRatio = v, defaultBindingMode: BindingMode.TwoWay); private TimeSpan _duration; private IterationCount _iterationCount = new IterationCount(1); private PlaybackDirection _playbackDirection; private FillMode _fillMode; private Easing _easing = new LinearEasing(); private TimeSpan _delay = TimeSpan.Zero; private TimeSpan _delayBetweenIterations = TimeSpan.Zero; private double _speedRatio = 1d; /// /// Gets or sets the active time of this animation. /// public TimeSpan Duration { get { return _duration; } set { SetAndRaise(DurationProperty, ref _duration, value); } } /// /// Gets or sets the repeat count for this animation. /// public IterationCount IterationCount { get { return _iterationCount; } set { SetAndRaise(IterationCountProperty, ref _iterationCount, value); } } /// /// Gets or sets the playback direction for this animation. /// public PlaybackDirection PlaybackDirection { get { return _playbackDirection; } set { SetAndRaise(PlaybackDirectionProperty, ref _playbackDirection, value); } } /// /// Gets or sets the value fill mode for this animation. /// public FillMode FillMode { get { return _fillMode; } set { SetAndRaise(FillModeProperty, ref _fillMode, value); } } /// /// Gets or sets the easing function to be used for this animation. /// public Easing Easing { get { return _easing; } set { SetAndRaise(EasingProperty, ref _easing, value); } } /// /// Gets or sets the initial delay time for this animation. /// public TimeSpan Delay { get { return _delay; } set { SetAndRaise(DelayProperty, ref _delay, value); } } /// /// Gets or sets the delay time in between iterations. /// public TimeSpan DelayBetweenIterations { get { return _delayBetweenIterations; } set { SetAndRaise(DelayBetweenIterationsProperty, ref _delayBetweenIterations, value); } } /// /// Gets or sets the speed multiple for this animation. /// public double SpeedRatio { get { return _speedRatio; } set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); } } /// /// Gets the children of the . /// [Content] public KeyFrames Children { get; } = new KeyFrames(); // Store values for the Animator attached properties for IAnimationSetter objects. private static readonly Dictionary Factory)> s_animators = new(); /// /// Gets the value of the Animator attached property for a setter. /// /// The animation setter. /// The property animator type. public static (Type Type, Func Factory)? GetAnimator(IAnimationSetter setter) { if (s_animators.TryGetValue(setter, out var type)) { return type; } return null; } /// /// Sets the value of the Animator attached property for a setter. /// /// The animation setter. /// The property animator value. public static void SetAnimator(IAnimationSetter setter, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)] Type value) { s_animators[setter] = (value, () => (IAnimator)Activator.CreateInstance(value)!); } private readonly static List<(Func Condition, Type Animator, Func Factory)> Animators = new() { ( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator() ), ( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator() ), ( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator() ), ( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator() ), ( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator() ), ( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator() ), ( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator() ), ( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator() ), ( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator() ), ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator() ), ( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator() ), }; /// /// Registers a that can handle /// a value type that matches the specified condition. /// /// /// The condition to which the /// is to be activated and used. /// /// /// The type of the animator to instantiate. /// public static void RegisterAnimator(Func condition) where TAnimator : IAnimator, new() { Animators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator())); } private static (Type Type, Func Factory)? GetAnimatorType(AvaloniaProperty property) { foreach (var (condition, type, factory) in Animators) { if (condition(property)) { return (type, factory); } } return null; } private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable control) { var handlerList = new Dictionary<(Type type, AvaloniaProperty Property), Func>(); var animatorKeyFrames = new List(); var subscriptions = new List(); foreach (var keyframe in Children) { foreach (var setter in keyframe.Setters) { if (setter.Property is null) { throw new InvalidOperationException("No Setter property assigned."); } var handler = Animation.GetAnimator(setter) ?? GetAnimatorType(setter.Property); if (handler == null) { throw new InvalidOperationException($"No animator registered for the property {setter.Property}. Add an animator to the Animation.Animators collection that matches this property to animate it."); } var (type, factory) = handler.Value; if (!handlerList.ContainsKey((type, setter.Property))) handlerList[(type, setter.Property)] = factory; var cue = keyframe.Cue; if (keyframe.TimingMode == KeyFrameTimingMode.TimeSpan) { cue = new Cue(keyframe.KeyTime.TotalSeconds / Duration.TotalSeconds); } var newKF = new AnimatorKeyFrame(type, factory, cue, keyframe.KeySpline); subscriptions.Add(newKF.BindSetter(setter, control)); animatorKeyFrames.Add(newKF); } } var newAnimatorInstances = new List(); foreach (var handler in handlerList) { var newInstance = handler.Value(); newInstance.Property = handler.Key.Property; newAnimatorInstances.Add(newInstance); } foreach (var keyframe in animatorKeyFrames) { var animator = newAnimatorInstances.First(a => a.GetType() == keyframe.AnimatorType && a.Property == keyframe.Property); animator.Add(keyframe); } return (newAnimatorInstances, subscriptions); } /// public IDisposable Apply(Animatable control, IClock? clock, IObservable match, Action? onComplete) { var (animators, subscriptions) = InterpretKeyframes(control); if (animators.Count == 1) { var subscription = animators[0].Apply(this, control, clock, match, onComplete); if (subscription is not null) { subscriptions.Add(subscription); } } else { var completionTasks = onComplete != null ? new List() : null; foreach (IAnimator animator in animators) { Action? animatorOnComplete = null; if (onComplete != null) { var tcs = new TaskCompletionSource(); animatorOnComplete = () => tcs.SetResult(null); completionTasks!.Add(tcs.Task); } var subscription = animator.Apply(this, control, clock, match, animatorOnComplete); if (subscription is not null) { subscriptions.Add(subscription); } } if (onComplete != null) { Task.WhenAll(completionTasks!).ContinueWith( (_, state) => ((Action)state!).Invoke(), onComplete); } } return new CompositeDisposable(subscriptions); } /// public Task RunAsync(Animatable control, IClock? clock = null) { return RunAsync(control, clock, default); } /// public Task RunAsync(Animatable control, IClock? clock = null, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { return Task.CompletedTask; } var run = new TaskCompletionSource(); if (this.IterationCount == IterationCount.Infinite) run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); IDisposable? subscriptions = null, cancellation = null; subscriptions = this.Apply(control, clock, Observable.Return(true), () => { run.TrySetResult(null); subscriptions?.Dispose(); cancellation?.Dispose(); }); cancellation = cancellationToken.Register(() => { run.TrySetResult(null); subscriptions?.Dispose(); cancellation?.Dispose(); }); return run.Task; } } }