diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index d6def6406c..d5b476814e 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -12,6 +12,7 @@ using Avalonia.Animation.Easings; using Avalonia.Collections; using Avalonia.Data; using Avalonia.Data.Core; +using Avalonia.Logging; using Avalonia.Metadata; namespace Avalonia.Animation @@ -213,11 +214,6 @@ namespace Avalonia.Animation ( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator) ), }; - - private readonly static List<(Func Condition, Type Animator)> SecondLevelAnimators - = new List<(Func Condition, Type Animator)>(); - - public static void RegisterAnimator(Func condition) where TAnimator : IAnimator { @@ -236,7 +232,7 @@ namespace Avalonia.Animation return null; } - AnimationTarget GetTargetFromSetter(IAnimationSetter setter, Animatable root) + AnimationTarget GetTargetFromSetter(IAnimationSetter setter, Animatable root, ref bool haltProcessing) { var target = new AnimationTarget(root, null); bool traverse = false, start = true; @@ -269,15 +265,33 @@ namespace Avalonia.Animation } break; case CastTypePropertyPathElement ct: - if (tempTarget != null && tempTarget?.GetType() != ct.Type) + var tmpTargetType = tempTarget?.GetType(); + var castType = ct.Type; + if (!castType.IsAssignableFrom(tmpTargetType)) { - tempTarget = Convert.ChangeType(target.TargetAnimatable, ct.Type); + Logger.Error(LogArea.Animations, this, + $"Type cast mismatch: `{tempTarget?.GetType()}` is not assignable to `{ct.Type}`"); + haltProcessing = true; + return null; } break; case EnsureTypePropertyPathElement et: - if (target.TargetAnimatable?.GetType() != et.Type) + var one = tempTarget?.GetType(); + var two = et.Type; + if (!one.IsAssignableFrom(two)) { - + if (et.Type.IsAssignableFrom(target.TargetProperty.PropertyType)) + { + tempTarget = Activator.CreateInstance(et.Type); + } + else + { + Logger.Error(LogArea.Animations, this, + $"Type enforcement mismatch: `{tempTarget?.GetType()}` is not assignable to `{et.Type}`"); + haltProcessing = true; + return null; + } + target.TargetAnimatable.SetValue(target.TargetProperty, tempTarget); } break; case ChildTraversalPropertyPathElement tr: @@ -292,7 +306,7 @@ namespace Avalonia.Animation return target; } - private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable root) + private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable root, ref bool haltProcessing) { var subscriptions = new List(); var animatorInstances = new Dictionary<(Type type, AnimationTarget animTarget), IAnimator>(); @@ -301,12 +315,19 @@ namespace Avalonia.Animation { foreach (var setter in keyframe.Setters) { - var target = GetTargetFromSetter(setter, root); + var target = GetTargetFromSetter(setter, root, ref haltProcessing); + + if (haltProcessing) return (null, null); + var animatorType = GetAnimatorType(target.TargetProperty); if (animatorType == null) { - throw new InvalidOperationException($"No animator registered for the property {target.TargetProperty}. Add an animator to the Animation.Animators collection that matches this property to animate it."); + Logger.Error(LogArea.Control, this, $"No animator registered for the property {target.TargetProperty}. " + + "Add an animator to the Animation.Animators collection that matches " + + "this property to animate it."); + haltProcessing = true; + return (null, null); } IAnimator animator; @@ -328,9 +349,8 @@ namespace Avalonia.Animation } var newKF = new AnimatorKeyFrame(animatorType, cue, target); - newKF.Value = setter.Value; - subscriptions.Add(newKF.BindSetter(setter, target.TargetAnimatable)); + subscriptions.Add(newKF.BindSetter(setter, target.RootAnimatable)); animator.Add(newKF); } @@ -342,10 +362,14 @@ namespace Avalonia.Animation /// public IDisposable Apply(Animatable control, IClock clock, IObservable match, Action onComplete) { - var (animators, subscriptions) = InterpretKeyframes(control); + bool haltProcessing = false; + var (animators, subscriptions) = InterpretKeyframes(control, ref haltProcessing); + if (haltProcessing) + return Disposable.Empty; + if (animators.Count == 1) { - subscriptions.Add(animators[0].Apply(this, control, clock, match, onComplete)); + subscriptions.Add(animators[0].Apply(this, clock, match, onComplete)); } else { @@ -359,7 +383,7 @@ namespace Avalonia.Animation animatorOnComplete = () => tcs.SetResult(null); completionTasks.Add(tcs.Task); } - subscriptions.Add(animator.Apply(this, control, clock, match, animatorOnComplete)); + subscriptions.Add(animator.Apply(this, clock, match, animatorOnComplete)); } if (onComplete != null) diff --git a/src/Avalonia.Animation/AnimationTarget.cs b/src/Avalonia.Animation/AnimationTarget.cs index b388203ad0..a662c2dab2 100644 --- a/src/Avalonia.Animation/AnimationTarget.cs +++ b/src/Avalonia.Animation/AnimationTarget.cs @@ -11,12 +11,15 @@ namespace Avalonia.Animation /// public class AnimationTarget : IEquatable { - public AnimationTarget(Animatable control, AvaloniaProperty property) + public AnimationTarget(Animatable root, AvaloniaProperty property) { - TargetAnimatable = control; + RootAnimatable = root; + TargetAnimatable = root; TargetProperty = property; } + public Animatable RootAnimatable { get; internal set; } + /// /// The target property of the animation. /// diff --git a/src/Avalonia.Animation/Animators/Animator`1.cs b/src/Avalonia.Animation/Animators/Animator`1.cs index a26cf53597..9a5082d54a 100644 --- a/src/Avalonia.Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Animation/Animators/Animator`1.cs @@ -37,12 +37,12 @@ namespace Avalonia.Animation.Animators } /// - public virtual IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) + public virtual IDisposable Apply(Animation animation, IClock clock, IObservable match, Action onComplete) { if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); - var subject = new DisposeAnimationInstanceSubject(this, animation, control, clock, onComplete); + var subject = new DisposeAnimationInstanceSubject(this, animation, clock, onComplete); return match.Subscribe(subject); } @@ -108,17 +108,16 @@ namespace Avalonia.Animation.Animators /// /// Runs the KeyFrames Animation. /// - internal IDisposable Run(Animation animation, Animatable control, IClock clock, Action onComplete) + internal IDisposable Run(Animation animation, IClock clock, Action onComplete) { var instance = new AnimationInstance( animation, Target.TargetAnimatable, this, - clock ?? Target.TargetAnimatable.Clock ?? Clock.GlobalClock, + clock ?? Target.RootAnimatable.Clock ?? Clock.GlobalClock, onComplete, InterpolationHandler); - return Target.TargetAnimatable.Bind((AvaloniaProperty)Target.TargetProperty, instance, BindingPriority.Animation); } diff --git a/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs b/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs index b44f392ce3..69dca8cfc4 100644 --- a/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs +++ b/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs @@ -22,15 +22,13 @@ namespace Avalonia.Animation private bool _lastMatch; private Animator _animator; private Animation _animation; - private Animatable _control; private Action _onComplete; private IClock _clock; - public DisposeAnimationInstanceSubject(Animator animator, Animation animation, Animatable control, IClock clock, Action onComplete) + public DisposeAnimationInstanceSubject(Animator animator, Animation animation, IClock clock, Action onComplete) { this._animator = animator; this._animation = animation; - this._control = control; this._onComplete = onComplete; this._clock = clock; } @@ -55,10 +53,10 @@ namespace Avalonia.Animation _lastInstance?.Dispose(); if (matchVal) { - _lastInstance = _animator.Run(_animation, _control, _clock, _onComplete); + _lastInstance = _animator.Run(_animation, _clock, _onComplete); } _lastMatch = matchVal; } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Animation/IAnimator.cs b/src/Avalonia.Animation/IAnimator.cs index fa0a3638df..1b63d50463 100644 --- a/src/Avalonia.Animation/IAnimator.cs +++ b/src/Avalonia.Animation/IAnimator.cs @@ -19,6 +19,6 @@ namespace Avalonia.Animation /// /// Applies the current KeyFrame group to the specified control. /// - IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete); + IDisposable Apply(Animation animation, IClock clock, IObservable match, Action onComplete); } } diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index a241dd4490..01c783544d 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -25,7 +25,7 @@ namespace Avalonia.Animation.Animators _colorAnimator.Target = new AnimationTarget(target, SolidColorBrush.ColorProperty); } - public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) + public override IDisposable Apply(Animation animation, IClock clock, IObservable match, Action onComplete) { foreach (var keyframe in this) { @@ -52,7 +52,6 @@ namespace Avalonia.Animation.Animators // Continue if target prop is not empty & is a SolidColorBrush derivative. if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) { - SolidColorBrush finalTarget; // If it's ISCB, change it back to SCB. @@ -68,7 +67,7 @@ namespace Avalonia.Animation.Animators if (_colorAnimator == null) InitializeColorAnimator(finalTarget); - return _colorAnimator.Apply(animation, finalTarget, clock ?? targetAnim.Clock, match, onComplete); + return _colorAnimator.Apply(animation, clock, match, onComplete); } return Disposable.Empty;