Browse Source

Enable setter bindings for animations.

Jeremy Koritzinsky 7 years ago
parent
commit
4765596e22

+ 4 - 8
src/Avalonia.Animation/Animation.cs

@@ -68,7 +68,7 @@ namespace Avalonia.Animation
         /// <summary>
         /// The value fill mode for this animation.
         /// </summary>
-        public FillMode FillMode { get; set; } 
+        public FillMode FillMode { get; set; }
 
         /// <summary>
         /// 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);
                 }

+ 59 - 5
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
     /// <see cref="Animator{T}"/> objects.
     /// </summary>
-    public class AnimatorKeyFrame
+    public class AnimatorKeyFrame : AvaloniaObject
     {
-        public Type AnimatorType;
-        public Cue Cue;
-        public AvaloniaProperty Property;
-        public object Value;
+        public static readonly DirectProperty<AnimatorKeyFrame, object> ValueProperty =
+            AvaloniaProperty.RegisterDirect<AnimatorKeyFrame, object>(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<T>()
+        {
+            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));
+        }
     }
 }

+ 13 - 24
src/Avalonia.Animation/Animator`1.cs

@@ -19,7 +19,7 @@ namespace Avalonia.Animation
         /// <summary>
         /// List of type-converted keyframes.
         /// </summary>
-        private readonly SortedList<double, (T, bool isNeutral)> _convertedKeyframes = new SortedList<double, (T, bool)>();
+        private readonly SortedList<double, (AnimatorKeyFrame, bool isNeutral)> _convertedKeyframes = new SortedList<double, (AnimatorKeyFrame, bool)>();
 
         private bool _isVerfifiedAndConverted;
 
@@ -38,7 +38,7 @@ namespace Avalonia.Animation
         public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch)
         {
             if (!_isVerfifiedAndConverted)
-                VerifyConvertKeyFrames(animation);
+                VerifyConvertKeyFrames();
 
             return obsMatch
                 // Ignore triggers when global timers are paused.
@@ -59,7 +59,7 @@ namespace Avalonia.Animation
         /// <param name="t">The time parameter, relative to the total animation time</param>
         protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t)
         {
-            KeyValuePair<double, (T, bool)> firstCue, lastCue;
+            KeyValuePair<double, (AnimatorKeyFrame frame, bool isNeutral)> 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<T>(firstCue, lastCue));
+            var firstFrameData = (firstCue.Value.frame.GetTypedValue<T>(), firstCue.Value.isNeutral);
+            var lastFrameData = (lastCue.Value.frame.GetTypedValue<T>(), lastCue.Value.isNeutral);
+            return (intraframeTime, new KeyFramePair<T>(firstFrameData, lastFrameData));
         }
 
 
@@ -115,24 +117,11 @@ namespace Avalonia.Animation
         /// <summary>
         /// Verifies and converts keyframe values according to this class's target type.
         /// </summary>
-        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<double, (T, bool)> 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));
             }
         }
     }

+ 4 - 4
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);

+ 1 - 1
src/Avalonia.Animation/IAnimationSetter.cs

@@ -5,4 +5,4 @@ namespace Avalonia.Animation
         AvaloniaProperty Property { get; set; }
         object Value { get; set; }
     }
-}
+}

+ 4 - 4
src/Avalonia.Animation/KeyFramePair`1.cs

@@ -22,7 +22,7 @@ namespace Avalonia.Animation
         /// </summary>
         /// <param name="FirstKeyFrame"></param>
         /// <param name="LastKeyFrame"></param>
-        public KeyFramePair(KeyValuePair<double, (T, bool)> FirstKeyFrame, KeyValuePair<double, (T, bool)> 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
         /// <summary>
         /// First <see cref="KeyFrame"/> object.
         /// </summary>
-        public KeyValuePair<double, (T TargetValue, bool isNeutral)> FirstKeyFrame { get; private set; }
+        public (T TargetValue, bool isNeutral) FirstKeyFrame { get; }
 
         /// <summary>
         /// Second <see cref="KeyFrame"/> object.
         /// </summary>
-        public KeyValuePair<double, (T TargetValue, bool isNeutral)> SecondKeyFrame { get; private set; }
+        public (T TargetValue, bool isNeutral) SecondKeyFrame { get; }
     }
-}
+}