Browse Source

Addressed all review notes by sir @grokys .

pull/1461/head
Jumar Macato 9 years ago
parent
commit
517622d1f7
  1. 1
      build/Base.props
  2. 2
      src/Avalonia.Animation/Animatable.cs
  3. 2
      src/Avalonia.Animation/IKeyFrames.cs
  4. 3
      src/Avalonia.Animation/KeyFrame.cs
  5. 94
      src/Avalonia.Animation/KeyFramesStateMachine`1.cs
  6. 56
      src/Avalonia.Animation/KeyFrames`1.cs
  7. 8
      src/Avalonia.Styling/Styling/Style.cs
  8. 11
      src/Avalonia.Visuals/Animation/TransformKeyFrames.cs
  9. 26
      src/Avalonia.Visuals/Media/Color.cs
  10. 22
      src/Avalonia.Visuals/Media/TransformGroup.cs

1
build/Base.props

@ -1,6 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.3.1" /> <PackageReference Include="System.ValueTuple" Version="4.3.1" />
<!-- <PackageReference Include="System.Numerics.Vectors" Version="4.5.0-preview2-26406-04"/> -->
</ItemGroup> </ItemGroup>
</Project> </Project>

2
src/Avalonia.Animation/Animatable.cs

@ -23,7 +23,7 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public Animatable() public Animatable()
{ {
Transitions = new Transitions.Transitions(); Transitions = new Transitions();
AnimatableTimer = Timing.AnimationStateTimer AnimatableTimer = Timing.AnimationStateTimer
.Select(p => .Select(p =>
{ {

2
src/Avalonia.Animation/IKeyFrames.cs

@ -7,7 +7,7 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Interface for Keyframe group object /// Interface for Keyframe group object
/// </summary> /// </summary>
public interface IKeyFrames internal interface IKeyFrames
{ {
/// <summary> /// <summary>
/// Applies the current KeyFrame group to the specified control. /// Applies the current KeyFrame group to the specified control.

3
src/Avalonia.Animation/KeyFrame.cs

@ -64,7 +64,4 @@ namespace Avalonia.Animation
public object Value { get; set; } public object Value { get; set; }
} }
} }

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

@ -11,7 +11,7 @@ namespace Avalonia.Animation
{ {
object _lastInterpValue; object _lastInterpValue;
object _firstKFValue; object _firstKFValue;
private ulong _delayTotalFrameCount, private ulong _delayTotalFrameCount,
_durationTotalFrameCount, _durationTotalFrameCount,
_delayFrameCount, _delayFrameCount,
@ -33,18 +33,19 @@ namespace Avalonia.Animation
internal bool _unsubscribe = false; internal bool _unsubscribe = false;
private IObserver<object> _targetObserver; private IObserver<object> _targetObserver;
[Flags]
private enum KeyFramesStates private enum KeyFramesStates
{ {
INITIALIZE, Initialize,
DO_DELAY, DoDelay,
DO_RUN, DoRun,
RUN_FORWARDS, RunForwards,
RUN_BACKWARDS, RunBackwards,
RUN_APPLYVALUE, RunApplyValue,
RUN_COMPLETE, RunComplete,
PAUSE, Pause,
STOP, Stop,
DISPOSED Disposed
} }
public void Initialize(Animation animation, Animatable control, KeyFrames<T> keyframes) public void Initialize(Animation animation, Animatable control, KeyFrames<T> keyframes)
@ -82,24 +83,14 @@ namespace Avalonia.Animation
break; break;
} }
switch (animation.PlaybackDirection) _isReversed = (animation.PlaybackDirection & PlaybackDirection.Reverse) != 0;
{
case PlaybackDirection.Reverse:
case PlaybackDirection.AlternateReverse:
_isReversed = true;
break;
default:
_isReversed = false;
break;
}
_animationDirection = _targetAnimation.PlaybackDirection; _animationDirection = _targetAnimation.PlaybackDirection;
_fillMode = _targetAnimation.FillMode; _fillMode = _targetAnimation.FillMode;
if (_durationTotalFrameCount > 0) if (_durationTotalFrameCount > 0)
_currentState = KeyFramesStates.DO_DELAY; _currentState = KeyFramesStates.DoDelay;
else else
_currentState = KeyFramesStates.DO_RUN; _currentState = KeyFramesStates.DoRun;
} }
@ -124,21 +115,21 @@ namespace Avalonia.Animation
_gotFirstKFValue = true; _gotFirstKFValue = true;
} }
if (_currentState == KeyFramesStates.DISPOSED) if (_currentState == KeyFramesStates.Disposed)
throw new InvalidProgramException("This KeyFrames Animation is already disposed."); throw new InvalidProgramException("This KeyFrames Animation is already disposed.");
if (_playState == PlayState.Stop) if (_playState == PlayState.Stop)
_currentState = KeyFramesStates.STOP; _currentState = KeyFramesStates.Stop;
// Save state and pause the machine // Save state and pause the machine
if (_playState == PlayState.Pause && _currentState != KeyFramesStates.PAUSE) if (_playState == PlayState.Pause && _currentState != KeyFramesStates.Pause)
{ {
_savedState = _currentState; _savedState = _currentState;
_currentState = KeyFramesStates.PAUSE; _currentState = KeyFramesStates.Pause;
} }
// Resume the previous state // Resume the previous state
if (_playState != PlayState.Pause && _currentState == KeyFramesStates.PAUSE) if (_playState != PlayState.Pause && _currentState == KeyFramesStates.Pause)
_currentState = _savedState; _currentState = _savedState;
double _tempDuration = 0d, _easedTime; double _tempDuration = 0d, _easedTime;
@ -146,7 +137,7 @@ namespace Avalonia.Animation
checkstate: checkstate:
switch (_currentState) switch (_currentState)
{ {
case KeyFramesStates.DO_DELAY: case KeyFramesStates.DoDelay:
if (_fillMode == FillMode.Backward if (_fillMode == FillMode.Backward
|| _fillMode == FillMode.Both) || _fillMode == FillMode.Both)
@ -163,60 +154,60 @@ namespace Avalonia.Animation
if (_delayFrameCount > _delayTotalFrameCount) if (_delayFrameCount > _delayTotalFrameCount)
{ {
_currentState = KeyFramesStates.DO_RUN; _currentState = KeyFramesStates.DoRun;
goto checkstate; goto checkstate;
} }
_delayFrameCount++; _delayFrameCount++;
break; break;
case KeyFramesStates.DO_RUN: case KeyFramesStates.DoRun:
if (_isReversed) if (_isReversed)
_currentState = KeyFramesStates.RUN_BACKWARDS; _currentState = KeyFramesStates.RunBackwards;
else else
_currentState = KeyFramesStates.RUN_FORWARDS; _currentState = KeyFramesStates.RunForwards;
goto checkstate; goto checkstate;
case KeyFramesStates.RUN_FORWARDS: case KeyFramesStates.RunForwards:
if (_durationFrameCount > _durationTotalFrameCount) if (_durationFrameCount > _durationTotalFrameCount)
{ {
_currentState = KeyFramesStates.RUN_COMPLETE; _currentState = KeyFramesStates.RunComplete;
goto checkstate; goto checkstate;
} }
_tempDuration = (double)_durationFrameCount / _durationTotalFrameCount; _tempDuration = (double)_durationFrameCount / _durationTotalFrameCount;
_currentState = KeyFramesStates.RUN_APPLYVALUE; _currentState = KeyFramesStates.RunApplyValue;
goto checkstate; goto checkstate;
case KeyFramesStates.RUN_BACKWARDS: case KeyFramesStates.RunBackwards:
if (_durationFrameCount > _durationTotalFrameCount) if (_durationFrameCount > _durationTotalFrameCount)
{ {
_currentState = KeyFramesStates.RUN_COMPLETE; _currentState = KeyFramesStates.RunComplete;
goto checkstate; goto checkstate;
} }
_tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount; _tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount;
_currentState = KeyFramesStates.RUN_APPLYVALUE; _currentState = KeyFramesStates.RunApplyValue;
goto checkstate; goto checkstate;
case KeyFramesStates.RUN_APPLYVALUE: case KeyFramesStates.RunApplyValue:
_easedTime = _targetAnimation.Easing.Ease(_tempDuration); _easedTime = _targetAnimation.Easing.Ease(_tempDuration);
_durationFrameCount++; _durationFrameCount++;
_lastInterpValue = Interpolator(_easedTime); _lastInterpValue = Interpolator(_easedTime);
_targetObserver.OnNext(_lastInterpValue); _targetObserver.OnNext(_lastInterpValue);
_currentState = KeyFramesStates.DO_RUN; _currentState = KeyFramesStates.DoRun;
break; break;
case KeyFramesStates.RUN_COMPLETE: case KeyFramesStates.RunComplete:
if (_checkLoopAndRepeat) if (_checkLoopAndRepeat)
{ {
@ -225,17 +216,17 @@ namespace Avalonia.Animation
if (_isLooping) if (_isLooping)
{ {
_currentState = KeyFramesStates.DO_RUN; _currentState = KeyFramesStates.DoRun;
} }
else if (_isRepeating) else if (_isRepeating)
{ {
if (_currentIteration >= _repeatCount) if (_currentIteration >= _repeatCount)
{ {
_currentState = KeyFramesStates.STOP; _currentState = KeyFramesStates.Stop;
} }
else else
{ {
_currentState = KeyFramesStates.DO_RUN; _currentState = KeyFramesStates.DoRun;
} }
_currentIteration++; _currentIteration++;
} }
@ -247,14 +238,15 @@ namespace Avalonia.Animation
break; break;
} }
_currentState = KeyFramesStates.STOP; _currentState = KeyFramesStates.Stop;
goto checkstate; goto checkstate;
case KeyFramesStates.STOP: case KeyFramesStates.Stop:
if (_fillMode == FillMode.Forward if (_fillMode == FillMode.Forward
|| _fillMode == FillMode.Both) || _fillMode == FillMode.Both)
{ {
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.Animation); _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
} }
_targetObserver.OnCompleted(); _targetObserver.OnCompleted();
break; break;
@ -267,10 +259,12 @@ namespace Avalonia.Animation
_targetObserver = observer; _targetObserver = observer;
return this; return this;
} }
public void Dispose() public void Dispose()
{ {
_unsubscribe = true; _unsubscribe = true;
_currentState = KeyFramesStates.DISPOSED; _currentState = KeyFramesStates.Disposed;
} }
} }
} }

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

@ -17,17 +17,17 @@ namespace Avalonia.Animation
public abstract class KeyFrames<T> : AvaloniaList<KeyFrame>, IKeyFrames public abstract class KeyFrames<T> : AvaloniaList<KeyFrame>, IKeyFrames
{ {
private bool _isVerfifiedAndConverted;
/// <summary> /// <summary>
/// Target property. /// List of type-converted keyframes.
/// </summary> /// </summary>
public AvaloniaProperty Property { get; set; } private Dictionary<double, T> _convertedKeyframes = new Dictionary<double, T>();
private bool _isVerfifiedAndConverted;
/// <summary> /// <summary>
/// List of type-converted keyframes. /// Gets or sets the target property for the keyframe.
/// </summary> /// </summary>
public Dictionary<double, T> ConvertedKeyframes = new Dictionary<double, T>(); public AvaloniaProperty Property { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch) public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch)
@ -47,38 +47,38 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Get the nearest pair of cue-time ordered keyframes /// Get the nearest pair of cue-time ordered keyframes
/// according to the given time parameter that is relative to /// according to the given time parameter that is relative to the
/// total animation time and the normalized intra-keyframe pair time /// total animation time and the normalized intra-keyframe pair time
/// (i.e., the normalized time between the selected keyframes, relative to the /// (i.e., the normalized time between the selected keyframes, relative to the
/// time parameter). /// time parameter).
/// </summary> /// </summary>
/// <param name="t">The time parameter, relative to the total animation time</param> /// <param name="t">The time parameter, relative to the total animation time</param>
public (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t) protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t)
{ {
KeyValuePair<double, T> firstCue, lastCue; KeyValuePair<double, T> firstCue, lastCue;
int kvCount = ConvertedKeyframes.Count(); int kvCount = _convertedKeyframes.Count();
if (kvCount > 2) if (kvCount > 2)
{ {
if (DoubleUtils.AboutEqual(t, 0.0) || t < 0.0) if (DoubleUtils.AboutEqual(t, 0.0) || t < 0.0)
{ {
firstCue = ConvertedKeyframes.First(); firstCue = _convertedKeyframes.First();
lastCue = ConvertedKeyframes.Skip(1).First(); lastCue = _convertedKeyframes.Skip(1).First();
} }
else if (DoubleUtils.AboutEqual(t, 1.0) || t > 1.0) else if (DoubleUtils.AboutEqual(t, 1.0) || t > 1.0)
{ {
firstCue = ConvertedKeyframes.Skip(kvCount - 2).First(); firstCue = _convertedKeyframes.Skip(kvCount - 2).First();
lastCue = ConvertedKeyframes.Last(); lastCue = _convertedKeyframes.Last();
} }
else else
{ {
firstCue = ConvertedKeyframes.Where(j => j.Key <= t).Last(); firstCue = _convertedKeyframes.Where(j => j.Key <= t).Last();
lastCue = ConvertedKeyframes.Where(j => j.Key >= t).First(); lastCue = _convertedKeyframes.Where(j => j.Key >= t).First();
} }
} }
else else
{ {
firstCue = ConvertedKeyframes.First(); firstCue = _convertedKeyframes.First();
lastCue = ConvertedKeyframes.Last(); lastCue = _convertedKeyframes.Last();
} }
double t0 = firstCue.Key; double t0 = firstCue.Key;
@ -91,7 +91,7 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Runs the KeyFrames Animation. /// Runs the KeyFrames Animation.
/// </summary> /// </summary>
public IDisposable RunKeyFrames(Animation animation, Animatable control) private IDisposable RunKeyFrames(Animation animation, Animatable control)
{ {
var _kfStateMach = new KeyFramesStateMachine<T>(); var _kfStateMach = new KeyFramesStateMachine<T>();
_kfStateMach.Initialize(animation, control, this); _kfStateMach.Initialize(animation, control, this);
@ -109,12 +109,7 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Interpolates a value given the desired time. /// Interpolates a value given the desired time.
/// </summary> /// </summary>
public abstract T DoInterpolation(double time); protected abstract T DoInterpolation(double time);
// public abstract IObservable<T> DoInterpolation(IObservable<double> timer, Animation animation,
// Animatable control);
/// <summary> /// <summary>
/// Verifies and converts keyframe values according to this class's target type. /// Verifies and converts keyframe values according to this class's target type.
@ -144,11 +139,11 @@ namespace Avalonia.Animation
_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);
} }
SortKeyFrameCues(ConvertedKeyframes); SortKeyFrameCues(_convertedKeyframes);
_isVerfifiedAndConverted = true; _isVerfifiedAndConverted = true;
} }
@ -158,9 +153,8 @@ namespace Avalonia.Animation
bool hasStartKey, hasEndKey; bool hasStartKey, hasEndKey;
hasStartKey = hasEndKey = false; hasStartKey = hasEndKey = false;
// this can be optional later, by making the default start/end keyframes // Make start and end keyframe mandatory.
// to have a neutral value (a.k.a. the value prior to the animation). foreach (var converted in _convertedKeyframes.Keys)
foreach (var converted in ConvertedKeyframes.Keys)
{ {
if (DoubleUtils.AboutEqual(converted, 0.0)) if (DoubleUtils.AboutEqual(converted, 0.0))
{ {
@ -177,8 +171,8 @@ namespace Avalonia.Animation
($"{this.GetType().Name} must have a starting (0% cue) and ending (100% cue) keyframe."); ($"{this.GetType().Name} must have a starting (0% cue) and ending (100% cue) keyframe.");
// Sort Cues, in case users don't order it by themselves. // Sort Cues, in case users don't order it by themselves.
ConvertedKeyframes = ConvertedKeyframes.OrderBy(p => p.Key) _convertedKeyframes = _convertedKeyframes.OrderBy(p => p.Key)
.ToDictionary((k) => k.Key, (v) => v.Value); .ToDictionary((k) => k.Key, (v) => v.Value);
} }
} }

8
src/Avalonia.Styling/Styling/Style.cs

@ -88,11 +88,8 @@ namespace Avalonia.Styling
{ {
return _animations ?? (_animations = new List<IAnimation>()); return _animations ?? (_animations = new List<IAnimation>());
} }
set
{
_animations = value;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => _parent; IResourceNode IResourceNode.ResourceParent => _parent;
@ -118,8 +115,7 @@ namespace Avalonia.Styling
foreach (var animation in Animations) foreach (var animation in Animations)
{ {
// TODO: Needs more work in passing the appropriate
// observable.
IObservable<bool> obsMatch = match.ObservableResult; IObservable<bool> obsMatch = match.ObservableResult;
if (match.ImmediateResult == true) if (match.ImmediateResult == true)

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

@ -11,7 +11,7 @@ using Avalonia.Media;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
/// Key frames that handles <see cref="double"/> properties. /// Keyframes that handles <see cref="Transform"/> properties.
/// </summary> /// </summary>
public class TransformKeyFrames : KeyFrames<double> public class TransformKeyFrames : KeyFrames<double>
{ {
@ -29,7 +29,7 @@ namespace Avalonia.Animation
if (childKeyFrames == null) if (childKeyFrames == null)
{ {
InitializeInternalDoubleKeyFrames(); InitializeChildKeyFrames();
} }
// It's a transform object so let's target that. // It's a transform object so let's target that.
@ -49,6 +49,7 @@ namespace Avalonia.Animation
} }
} }
//TODO: determine correctly when to throw this error.
//throw new InvalidOperationException($"TransformKeyFrame hasn't found an appropriate Transform object with type {Property.OwnerType} in target {control}."); //throw new InvalidOperationException($"TransformKeyFrame hasn't found an appropriate Transform object with type {Property.OwnerType} in target {control}.");
return null; return null;
@ -56,11 +57,11 @@ namespace Avalonia.Animation
} }
else else
{ {
throw new Exception($"Unsupported property {Property}"); throw new Exception($"Cannot apply keyframe: property {Property} is not a Transform");
} }
} }
void InitializeInternalDoubleKeyFrames() void InitializeChildKeyFrames()
{ {
childKeyFrames = new DoubleKeyFrames(); childKeyFrames = new DoubleKeyFrames();
@ -73,6 +74,6 @@ namespace Avalonia.Animation
} }
/// <inheritdocs/> /// <inheritdocs/>
public override double DoInterpolation(double time) => 0; protected override double DoInterpolation(double time) => 0;
} }
} }

26
src/Avalonia.Visuals/Media/Color.cs

@ -81,32 +81,6 @@ namespace Avalonia.Media
(byte)(value & 0xff) (byte)(value & 0xff)
); );
} }
// /// <summary>
// /// Creates a <see cref="Color"/> from a <see cref="Vector4"/>.
// /// </summary>
// /// <param name="value">The <see cref="Vector4"/> value.</param>
// /// <param name="normalized">True if the color vector is normalized floats, false if it's bytes (0-255)</param>
// /// <returns>The color.</returns>
// public static Color FromVector4(Vector4 value, bool normalized)
// {
// if (normalized)
// {
// return new Color(
// (byte)(value.W * 255),
// (byte)(value.X * 255),
// (byte)(value.Y * 255),
// (byte)(value.Z * 255)
// );
// } else {
// return new Color(
// (byte)(value.W),
// (byte)(value.X),
// (byte)(value.Y),
// (byte)(value.Z)
// );
// }
// }
/// <summary> /// <summary>
/// Parses a color string. /// Parses a color string.

22
src/Avalonia.Visuals/Media/TransformGroup.cs

@ -18,23 +18,13 @@ namespace Avalonia.Media
public TransformGroup() public TransformGroup()
{ {
Children = new Transforms(); Children = new Transforms();
Children.CollectionChanged += (_, e) => Children.ResetBehavior = ResetBehavior.Remove;
Children.CollectionChanged += delegate
{ {
switch (e.Action) Children.ForEachItem(
{ (tr) => tr.Changed += ChildTransform_Changed,
case NotifyCollectionChangedAction.Add: (tr) => tr.Changed -= ChildTransform_Changed,
foreach (Transform tr in e.NewItems) () => { });
{
tr.Changed += ChildTransform_Changed;
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (Transform tr in e.OldItems)
{
tr.Changed -= ChildTransform_Changed;
}
break;
}
}; };
} }

Loading…
Cancel
Save