Browse Source

Fix introduced bug in neutral keyframe values.

pull/1461/head
Jumar Macato 8 years ago
parent
commit
de4ffbec3b
  1. 40
      samples/RenderTest/Pages/AnimationsPage.xaml
  2. 18
      src/Avalonia.Animation/DoubleKeyFrames.cs
  3. 6
      src/Avalonia.Animation/KeyFramePair`1.cs
  4. 10
      src/Avalonia.Animation/KeyFramesStateMachine`1.cs
  5. 43
      src/Avalonia.Animation/KeyFrames`1.cs
  6. 2
      src/Avalonia.Visuals/Animation/TransformKeyFrames.cs

40
samples/RenderTest/Pages/AnimationsPage.xaml

@ -38,7 +38,6 @@
<Setter Property="Margin" Value="15"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Background" Value="Cyan"/>
<Setter Property="Child" Value="{StaticResource Acorn}"/>
</Style>
<Style Selector="Border.Rect1:pointerover">
@ -76,7 +75,7 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect2a">
<Style Selector="Border.Rect3">
<Setter Property="Child" Value="{StaticResource Heart}"/>
<Style.Animations>
<Animation Duration="0:0:0.5" Easing="QuadraticEaseInOut" RepeatBehavior="Loop">
@ -93,7 +92,7 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect3:pointerover">
<Style Selector="Border.Rect4:pointerover">
<Style.Animations>
<Animation Duration="0:0:3" Easing="BounceEaseInOut">
<TransformKeyFrames Property="TranslateTransform.Y">
@ -104,7 +103,7 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect4:pointerover">
<Style Selector="Border.Rect5:pointerover">
<Style.Animations>
<Animation Duration="0:0:3" Easing="CircularEaseInOut">
<TransformKeyFrames Property="SkewTransform.AngleX">
@ -125,34 +124,11 @@
<Button Content="{Binding PlayStateText}" Command="{Binding ToggleGlobalPlayState}"/>
</StackPanel>
<WrapPanel ClipToBounds="False">
<Border Classes="Test Rect1" Background="DarkRed">
<Border.RenderTransform>
<TransformGroup>
<RotateTransform/>
<ScaleTransform/>
</TransformGroup>
</Border.RenderTransform>
</Border>
<Border Classes="Test Rect2a" Background="Transparent">
<Border.RenderTransform>
<ScaleTransform/>
</Border.RenderTransform>
</Border>
<Border Classes="Test Rect2" Background="DarkMagenta">
<Border.RenderTransform>
<ScaleTransform/>
</Border.RenderTransform>
</Border>
<Border Classes="Test Rect3" Background="Navy">
<Border.RenderTransform>
<TranslateTransform/>
</Border.RenderTransform>
</Border>
<Border Classes="Test Rect4" Background="SeaGreen">
<Border.RenderTransform>
<SkewTransform/>
</Border.RenderTransform>
</Border>
<Border Classes="Test Rect1" Background="DarkRed"/>
<Border Classes="Test Rect2" Background="Magenta"/>
<Border Classes="Test Rect3"/>
<Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/>
</WrapPanel>
</StackPanel>
</Grid>

18
src/Avalonia.Animation/DoubleKeyFrames.cs

@ -16,11 +16,23 @@ namespace Avalonia.Animation
{
/// <inheritdocs/>
protected override double DoInterpolation(double t)
protected override double DoInterpolation(double t, double neutralValue)
{
var pair = GetKFPairAndIntraKFTime(t);
double y0 = pair.KFPair.FirstKeyFrame.Value;
double y1 = pair.KFPair.SecondKeyFrame.Value;
double y0, y1;
var firstKF = pair.KFPair.FirstKeyFrame;
var secondKF = pair.KFPair.SecondKeyFrame;
if (firstKF.Value.isNeutral)
y0 = neutralValue;
else
y0 = firstKF.Value.TargetValue;
if (secondKF.Value.isNeutral)
y1 = neutralValue;
else
y1 = secondKF.Value.TargetValue;
// Do linear parametric interpolation
return y0 + (pair.IntraKFTime) * (y1 - y0);

6
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> FirstKeyFrame, KeyValuePair<double, T> LastKeyFrame) : this()
public KeyFramePair(KeyValuePair<double, (T, bool)> FirstKeyFrame, KeyValuePair<double, (T, bool)> 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> FirstKeyFrame { get; private set; }
public KeyValuePair<double, (T TargetValue, bool isNeutral)> FirstKeyFrame { get; private set; }
/// <summary>
/// Second <see cref="KeyFrame"/> object.
/// </summary>
public KeyValuePair<double, T> SecondKeyFrame { get; private set; }
public KeyValuePair<double, (T TargetValue, bool isNeutral)> SecondKeyFrame { get; private set; }
}
}

10
src/Avalonia.Animation/KeyFramesStateMachine`1.cs

@ -32,6 +32,7 @@ namespace Avalonia.Animation
private KeyFrames<T> _parent;
private Animation _targetAnimation;
private Animatable _targetControl;
private T _neutralValue;
internal bool _unsubscribe = false;
private IObserver<object> _targetObserver;
@ -55,6 +56,7 @@ namespace Avalonia.Animation
_parent = keyframes;
_targetAnimation = animation;
_targetControl = control;
_neutralValue = (T)_targetControl.GetValue(_parent.Property);
_delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks);
_durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks);
@ -95,7 +97,7 @@ namespace Avalonia.Animation
_currentState = KeyFramesStates.DoRun;
}
public void Step(PlayState _playState, Func<double, T> Interpolator)
public void Step(PlayState _playState, Func<double, T, T> Interpolator)
{
try
{
@ -107,7 +109,7 @@ namespace Avalonia.Animation
}
}
private void InternalStep(PlayState _playState, Func<double, T> Interpolator)
private void InternalStep(PlayState _playState, Func<double, T, T> Interpolator)
{
if (!_gotFirstKFValue)
{
@ -200,7 +202,7 @@ namespace Avalonia.Animation
_easedTime = _targetAnimation.Easing.Ease(_tempDuration);
_durationFrameCount++;
_lastInterpValue = Interpolator(_easedTime);
_lastInterpValue = Interpolator(_easedTime, _neutralValue);
_targetObserver.OnNext(_lastInterpValue);
_currentState = KeyFramesStates.DoRun;
@ -262,6 +264,6 @@ namespace Avalonia.Animation
{
_unsubscribe = true;
_currentState = KeyFramesStates.Disposed;
}
}
}
}

43
src/Avalonia.Animation/KeyFrames`1.cs

@ -19,11 +19,9 @@ namespace Avalonia.Animation
/// <summary>
/// List of type-converted keyframes.
/// </summary>
private Dictionary<double, T> _convertedKeyframes = new Dictionary<double, T>();
private Dictionary<double, (T, bool isNeutral)> _convertedKeyframes = new Dictionary<double, (T, bool)>();
private bool _isVerfifiedAndConverted;
private Animation _animation;
private Animatable _target;
/// <summary>
/// Gets or sets the target property for the keyframe.
@ -34,11 +32,7 @@ namespace Avalonia.Animation
public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch)
{
if (!_isVerfifiedAndConverted)
{
this._animation = animation;
this._target = control;
VerifyConvertKeyFrames(typeof(T));
}
VerifyConvertKeyFrames(animation, typeof(T));
return obsMatch
.Where(p => p == true)
@ -46,7 +40,7 @@ namespace Avalonia.Animation
.Where(p => Timing.GetGlobalPlayState() != PlayState.Pause)
.Subscribe(_ =>
{
var timerObs = RunKeyFrames();
var timerObs = RunKeyFrames(animation, control);
});
}
@ -60,9 +54,8 @@ 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> firstCue, lastCue;
KeyValuePair<double, (T, bool)> firstCue, lastCue;
int kvCount = _convertedKeyframes.Count();
if (kvCount > 2)
{
if (DoubleUtils.AboutEqual(t, 0.0) || t < 0.0)
@ -97,10 +90,10 @@ namespace Avalonia.Animation
/// <summary>
/// Runs the KeyFrames Animation.
/// </summary>
private IDisposable RunKeyFrames()
private IDisposable RunKeyFrames(Animation animation, Animatable control)
{
var _kfStateMach = new KeyFramesStateMachine<T>();
_kfStateMach.Initialize(_animation, _target, this);
_kfStateMach.Initialize(animation, control, this);
Timing.AnimationStateTimer
.TakeWhile(_ => !_kfStateMach._unsubscribe)
@ -109,18 +102,18 @@ namespace Avalonia.Animation
_kfStateMach.Step(p, DoInterpolation);
});
return _target.Bind(Property, _kfStateMach, BindingPriority.Animation);
return control.Bind(Property, _kfStateMach, BindingPriority.Animation);
}
/// <summary>
/// Interpolates a value given the desired time.
/// </summary>
protected abstract T DoInterpolation(double time);
protected abstract T DoInterpolation(double time, T neutralValue);
/// <summary>
/// Verifies and converts keyframe values according to this class's target type.
/// </summary>
private void VerifyConvertKeyFrames(Type type)
private void VerifyConvertKeyFrames(Animation animation, Type type)
{
var typeConv = TypeDescriptor.GetConverter(type);
@ -141,10 +134,10 @@ namespace Avalonia.Animation
if (k.timeSpanSet)
{
_normalizedCue = new Cue(k.KeyTime.Ticks / _animation.Duration.Ticks);
_normalizedCue = new Cue(k.KeyTime.Ticks / animation.Duration.Ticks);
}
_convertedKeyframes.Add(_normalizedCue.CueValue, convertedValue);
_convertedKeyframes.Add(_normalizedCue.CueValue, (convertedValue, false));
}
SortKeyFrameCues(_convertedKeyframes);
@ -152,11 +145,12 @@ namespace Avalonia.Animation
}
private void SortKeyFrameCues(Dictionary<double, T> convertedValues)
private void SortKeyFrameCues(Dictionary<double, (T, bool)> convertedValues)
{
bool hasStartKey, hasEndKey;
hasStartKey = hasEndKey = false;
// Make start and end keyframe mandatory.
foreach (var converted in _convertedKeyframes.Keys)
{
if (DoubleUtils.AboutEqual(converted, 0.0))
@ -170,25 +164,24 @@ namespace Avalonia.Animation
}
if (!hasStartKey || !hasEndKey)
AddNeutralKeyFrames(hasStartKey, hasEndKey);
AddNeutralKeyFrames(hasStartKey, hasEndKey, _convertedKeyframes);
_convertedKeyframes = _convertedKeyframes.OrderBy(p => p.Key)
.ToDictionary((k) => k.Key, (v) => v.Value);
}
private void AddNeutralKeyFrames(bool hasStartKey, bool hasEndKey)
private void AddNeutralKeyFrames(bool hasStartKey, bool hasEndKey, Dictionary<double, (T, bool)> convertedKeyframes)
{
var neutralValue = (T)_target.GetValue(Property);
if (!hasStartKey)
{
_convertedKeyframes.Add(0.0d, neutralValue);
convertedKeyframes.Add(0.0d, (default(T), true));
}
if (!hasEndKey)
{
_convertedKeyframes.Add(1.0d, neutralValue);
convertedKeyframes.Add(1.0d, (default(T), true));
}
}
}
}

2
src/Avalonia.Visuals/Animation/TransformKeyFrames.cs

@ -93,6 +93,6 @@ namespace Avalonia.Animation
}
/// <inheritdocs/>
protected override double DoInterpolation(double time) => 0;
protected override double DoInterpolation(double time, double neutralValue) => 0;
}
}

Loading…
Cancel
Save