diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 685f160745..dcfeb7f88f 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -68,7 +68,7 @@ namespace Avalonia.Animation /// /// The value fill mode for this animation. /// - public FillMode FillMode { get; set; } + public FillMode FillMode { get; set; } /// /// Easing function to be used. @@ -106,13 +106,9 @@ namespace Avalonia.Animation cue = new Cue(keyframe.KeyTime.Ticks / Duration.Ticks); } - var newKF = new AnimatorKeyFrame() - { - AnimatorType = handler, - Property = setter.Property, - Cue = cue, - Value = setter.Value - }; + var newKF = new AnimatorKeyFrame(handler, cue); + + _subscription.Add(newKF.BindSetter(setter)); animatorKeyFrames.Add(newKF); } diff --git a/src/Avalonia.Animation/AnimatorKeyFrame.cs b/src/Avalonia.Animation/AnimatorKeyFrame.cs index 1b0c38e0fc..a975f5028f 100644 --- a/src/Avalonia.Animation/AnimatorKeyFrame.cs +++ b/src/Avalonia.Animation/AnimatorKeyFrame.cs @@ -4,6 +4,8 @@ using System.Text; using System.ComponentModel; using Avalonia.Metadata; using Avalonia.Collections; +using Avalonia.Data; +using Avalonia.Reactive; namespace Avalonia.Animation { @@ -11,11 +13,63 @@ namespace Avalonia.Animation /// Defines a KeyFrame that is used for /// objects. /// - public class AnimatorKeyFrame + public class AnimatorKeyFrame : AvaloniaObject { - public Type AnimatorType; - public Cue Cue; - public AvaloniaProperty Property; - public object Value; + public static readonly DirectProperty ValueProperty = + AvaloniaProperty.RegisterDirect(nameof(Value), k => k._value, (k, v) => k._value = v); + + public AnimatorKeyFrame() + { + + } + + public AnimatorKeyFrame(Type animatorType, Cue cue) + { + AnimatorType = animatorType; + Cue = cue; + } + + public Type AnimatorType { get; } + public Cue Cue { get; } + public AvaloniaProperty Property { get; private set; } + + private object _value; + + public object Value + { + get => _value; + set => SetAndRaise(ValueProperty, ref _value, value); + } + + public IDisposable BindSetter(IAnimationSetter setter) + { + Property = setter.Property; + var value = setter.Value; + + if (value is IBinding binding) + { + return this.Bind(ValueProperty, binding); + } + else + { + return this.Bind(ValueProperty, ObservableEx.SingleValue(value)); + } + } + + public T GetTypedValue() + { + var typeConv = TypeDescriptor.GetConverter(typeof(T)); + + if (Value == null) + { + throw new ArgumentNullException($"KeyFrame value can't be null."); + } + if (!typeConv.CanConvertTo(Value.GetType())) + { + throw new InvalidCastException($"KeyFrame value doesnt match property type."); + } + + return (T)typeConv.ConvertTo(Value, typeof(T)); + } } } diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 327a2ebd62..a1eef87e1e 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -19,7 +19,7 @@ namespace Avalonia.Animation /// /// List of type-converted keyframes. /// - private readonly SortedList _convertedKeyframes = new SortedList(); + private readonly SortedList _convertedKeyframes = new SortedList(); private bool _isVerfifiedAndConverted; @@ -38,7 +38,7 @@ namespace Avalonia.Animation public virtual IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch) { if (!_isVerfifiedAndConverted) - VerifyConvertKeyFrames(animation); + VerifyConvertKeyFrames(); return obsMatch // Ignore triggers when global timers are paused. @@ -59,7 +59,7 @@ namespace Avalonia.Animation /// The time parameter, relative to the total animation time protected (double IntraKFTime, KeyFramePair KFPair) GetKFPairAndIntraKFTime(double t) { - KeyValuePair firstCue, lastCue; + KeyValuePair firstCue, lastCue; int kvCount = _convertedKeyframes.Count; if (kvCount > 2) { @@ -88,7 +88,9 @@ namespace Avalonia.Animation double t0 = firstCue.Key; double t1 = lastCue.Key; var intraframeTime = (t - t0) / (t1 - t0); - return (intraframeTime, new KeyFramePair(firstCue, lastCue)); + var firstFrameData = (firstCue.Value.frame.GetTypedValue(), firstCue.Value.isNeutral); + var lastFrameData = (lastCue.Value.frame.GetTypedValue(), lastCue.Value.isNeutral); + return (intraframeTime, new KeyFramePair(firstFrameData, lastFrameData)); } @@ -115,24 +117,11 @@ namespace Avalonia.Animation /// /// Verifies and converts keyframe values according to this class's target type. /// - private void VerifyConvertKeyFrames(Animation animation) + private void VerifyConvertKeyFrames() { - var typeConv = TypeDescriptor.GetConverter(typeof(T)); - - foreach (AnimatorKeyFrame k in this) + foreach (AnimatorKeyFrame keyframe in this) { - if (k.Value == null) - { - throw new ArgumentNullException($"KeyFrame value can't be null."); - } - if (!typeConv.CanConvertTo(k.Value.GetType())) - { - throw new InvalidCastException($"KeyFrame value doesnt match property type."); - } - - T convertedValue = (T)typeConv.ConvertTo(k.Value, typeof(T)); - - _convertedKeyframes.Add(k.Cue.CueValue, (convertedValue, false)); + _convertedKeyframes.Add(keyframe.Cue.CueValue, (keyframe, false)); } AddNeutralKeyFramesIfNeeded(); @@ -159,19 +148,19 @@ namespace Avalonia.Animation } if (!hasStartKey || !hasEndKey) - AddNeutralKeyFrames(hasStartKey, hasEndKey, _convertedKeyframes); + AddNeutralKeyFrames(hasStartKey, hasEndKey); } - private void AddNeutralKeyFrames(bool hasStartKey, bool hasEndKey, IDictionary convertedKeyframes) + private void AddNeutralKeyFrames(bool hasStartKey, bool hasEndKey) { if (!hasStartKey) { - convertedKeyframes.Add(0.0d, (default(T), true)); + _convertedKeyframes.Add(0.0d, (new AnimatorKeyFrame { Value = default(T) }, true)); } if (!hasEndKey) { - convertedKeyframes.Add(1.0d, (default(T), true)); + _convertedKeyframes.Add(1.0d, (new AnimatorKeyFrame { Value = default(T) }, true)); } } } diff --git a/src/Avalonia.Animation/DoubleAnimator.cs b/src/Avalonia.Animation/DoubleAnimator.cs index 5b994377f1..154f37360c 100644 --- a/src/Avalonia.Animation/DoubleAnimator.cs +++ b/src/Avalonia.Animation/DoubleAnimator.cs @@ -24,15 +24,15 @@ namespace Avalonia.Animation var firstKF = pair.KFPair.FirstKeyFrame; var secondKF = pair.KFPair.SecondKeyFrame; - if (firstKF.Value.isNeutral) + if (firstKF.isNeutral) y0 = neutralValue; else - y0 = firstKF.Value.TargetValue; + y0 = firstKF.TargetValue; - if (secondKF.Value.isNeutral) + if (secondKF.isNeutral) y1 = neutralValue; else - y1 = secondKF.Value.TargetValue; + y1 = secondKF.TargetValue; // Do linear parametric interpolation return y0 + (pair.IntraKFTime) * (y1 - y0); diff --git a/src/Avalonia.Animation/IAnimationSetter.cs b/src/Avalonia.Animation/IAnimationSetter.cs index f2a94c9ed6..2d22377286 100644 --- a/src/Avalonia.Animation/IAnimationSetter.cs +++ b/src/Avalonia.Animation/IAnimationSetter.cs @@ -5,4 +5,4 @@ namespace Avalonia.Animation AvaloniaProperty Property { get; set; } object Value { get; set; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Animation/KeyFramePair`1.cs b/src/Avalonia.Animation/KeyFramePair`1.cs index c192479a1d..408b13e0d8 100644 --- a/src/Avalonia.Animation/KeyFramePair`1.cs +++ b/src/Avalonia.Animation/KeyFramePair`1.cs @@ -22,7 +22,7 @@ namespace Avalonia.Animation /// /// /// - public KeyFramePair(KeyValuePair FirstKeyFrame, KeyValuePair LastKeyFrame) : this() + public KeyFramePair((T TargetValue, bool isNeutral) FirstKeyFrame, (T TargetValue, bool isNeutral) LastKeyFrame) : this() { this.FirstKeyFrame = FirstKeyFrame; this.SecondKeyFrame = LastKeyFrame; @@ -31,11 +31,11 @@ namespace Avalonia.Animation /// /// First object. /// - public KeyValuePair FirstKeyFrame { get; private set; } + public (T TargetValue, bool isNeutral) FirstKeyFrame { get; } /// /// Second object. /// - public KeyValuePair SecondKeyFrame { get; private set; } + public (T TargetValue, bool isNeutral) SecondKeyFrame { get; } } -} \ No newline at end of file +}