// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Animation.Easings; using Avalonia.Animation; using Avalonia.Collections; using Avalonia.Metadata; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Reflection; using System.Linq; namespace Avalonia.Animation { /// /// Tracks the progress of an animation. /// public class Animation : AvaloniaList, IDisposable, IAnimation { private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> { ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ) }; public static void RegisterAnimator(Func condition) where TAnimator: IAnimator { Animators.Insert(0, (condition, typeof(TAnimator))); } private static Type GetAnimatorType(AvaloniaProperty property) { foreach (var (condition, type) in Animators) { if (condition(property)) { return type; } } return null; } private bool _isChildrenChanged = false; private List _subscription = new List(); public AvaloniaList _animators { get; set; } = new AvaloniaList(); /// /// Run time of this animation. /// public TimeSpan Duration { get; set; } /// /// Delay time for this animation. /// public TimeSpan Delay { get; set; } /// /// The repeat count for this animation. /// public RepeatCount RepeatCount { get; set; } /// /// The playback direction for this animation. /// public PlaybackDirection PlaybackDirection { get; set; } /// /// The value fill mode for this animation. /// public FillMode FillMode { get; set; } /// /// Easing function to be used. /// public Easing Easing { get; set; } = new LinearEasing(); public Animation() { this.CollectionChanged += delegate { _isChildrenChanged = true; }; } private void InterpretKeyframes() { var handlerList = new List<(Type type, AvaloniaProperty property)>(); var kfList = new List(); foreach (var keyframe in this) { foreach (var setter in keyframe) { var handler = 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."); } if (!handlerList.Contains((handler, setter.Property))) handlerList.Add((handler, setter.Property)); var newKF = new AnimatorKeyFrame() { Handler = handler, Property = setter.Property, Cue = keyframe.Cue, KeyTime = keyframe.KeyTime, TimingMode = keyframe.TimingMode, Value = setter.Value }; kfList.Add(newKF); } } var newAnimatorInstances = new List<(Type handler, AvaloniaProperty prop, IAnimator inst)>(); foreach (var (handlerType, property) in handlerList) { var newInstance = (IAnimator)Activator.CreateInstance(handlerType); newInstance.Property = property; newAnimatorInstances.Add((handlerType, property, newInstance)); } foreach (var kf in kfList) { var parent = newAnimatorInstances.First(p => p.handler == kf.Handler && p.prop == kf.Property); parent.inst.Add(kf); } foreach(var instance in newAnimatorInstances) _animators.Add(instance.inst); } /// /// Cancels the animation. /// public void Dispose() { foreach (var sub in _subscription) { sub.Dispose(); } } /// public IDisposable Apply(Animatable control, IObservable matchObs) { if (_isChildrenChanged) { InterpretKeyframes(); _isChildrenChanged = false; } foreach (IAnimator keyframes in _animators) { _subscription.Add(keyframes.Apply(this, control, matchObs)); } return this; } } }