Browse Source

Enable setter bindings for animations.

pull/1768/head
Jeremy Koritzinsky 8 years ago
parent
commit
4765596e22
  1. 12
      src/Avalonia.Animation/Animation.cs
  2. 64
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  3. 37
      src/Avalonia.Animation/Animator`1.cs
  4. 8
      src/Avalonia.Animation/DoubleAnimator.cs
  5. 2
      src/Avalonia.Animation/IAnimationSetter.cs
  6. 8
      src/Avalonia.Animation/KeyFramePair`1.cs

12
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);
}

64
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));
}
}
}

37
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));
}
}
}

8
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);

2
src/Avalonia.Animation/IAnimationSetter.cs

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

8
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; }
}
}
}

Loading…
Cancel
Save