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
+}