Browse Source

Initial Implementation of KeyFrame state machines for handling different stages of an animation.

pull/1461/head
Jumar Macato 9 years ago
parent
commit
0ed34541a2
  1. 52
      src/Avalonia.Animation/Animatable.cs
  2. 8
      src/Avalonia.Animation/Animation.cs
  3. 2
      src/Avalonia.Animation/Keyframes/IKeyFrames.cs
  4. 14
      src/Avalonia.Animation/Keyframes/KeyFrames.cs
  5. 131
      src/Avalonia.Animation/Keyframes/KeyFramesStateMachine.cs
  6. 3
      src/Avalonia.Animation/PlayState.cs
  7. 35
      src/Avalonia.Animation/Timing.cs
  8. 6
      src/Avalonia.Visuals/Animation/Keyframes/TransformKeyFrames.cs

52
src/Avalonia.Animation/Animatable.cs

@ -24,54 +24,24 @@ namespace Avalonia.Animation
public Animatable()
{
Transitions = new Transitions.Transitions();
_animationStates = new Dictionary<ulong, AnimationStatus>();
AnimatableTimer = Timing.AnimationStateTimer
.Select(p =>
{
if (p == PlayState.Run
&& this._playState == PlayState.Run)
return _animationTime++;
else
return _animationTime;
})
.Publish()
.RefCount();
.Select(p =>
{
if (this._playState == PlayState.Pause)
{
return PlayState.Pause;
}
else return p;
})
.Publish()
.RefCount();
}
/// <summary>
/// The specific animations timer for this control.
/// </summary>
/// <returns></returns>
public IObservable<ulong> AnimatableTimer;
internal class AnimationStatus
{
public int RepeatCount { get; set; }
public int IterationDirection { get; set; }
public int CurrentIteration { get; set; }
public int TotalIteration { get; set; }
}
internal Dictionary<ulong, AnimationStatus> _animationStates;
internal ulong PrepareAnimatableForAnimation()
{
var iterToken = GetIterationToken();
_animationStates.Add(iterToken, new AnimationStatus());
return iterToken;
}
bool _animationsInitialized = false;
internal ulong _animationTime;
internal ulong _iterationTokenCounter;
internal ulong GetIterationToken()
{
return _iterationTokenCounter++;
}
public IObservable<PlayState> AnimatableTimer;
/// <summary>
/// Defines the <see cref="PlayState"/> property.

8
src/Avalonia.Animation/Animation.cs

@ -35,12 +35,12 @@ namespace Avalonia.Animation
/// <summary>
/// The playback direction for this animation.
/// </summary>
public Direction Direction { get; set; }
public Direction PlaybackDirection { get; set; }
/// <summary>
/// Number of repeat iteration for this animation.
/// </summary>
public int? RepeatCount { get; set; }
public ulong? RepeatCount { get; set; }
/// <summary>
/// Easing function to be used.
@ -64,11 +64,9 @@ namespace Avalonia.Animation
/// <inheritdocs/>
public IDisposable Apply(Animatable control, IObservable<bool> matchObs)
{
var iterToken = control.PrepareAnimatableForAnimation();
foreach (IKeyFrames keyframes in Children)
{
_subscription.Add(keyframes.Apply(this, control, iterToken, matchObs));
_subscription.Add(keyframes.Apply(this, control, matchObs));
}
return this;
}

2
src/Avalonia.Animation/Keyframes/IKeyFrames.cs

@ -12,6 +12,6 @@ namespace Avalonia.Animation.Keyframes
/// <summary>
/// Applies the current KeyFrame group to the specified control.
/// </summary>
IDisposable Apply(Animation animation, Animatable control, ulong IterationToken, IObservable<bool> obsMatch);
IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch);
}
}

14
src/Avalonia.Animation/Keyframes/KeyFrames.cs

@ -31,7 +31,7 @@ namespace Avalonia.Animation.Keyframes
/// <inheritdoc/>
public virtual IDisposable Apply(Animation animation, Animatable control, ulong IterationToken, IObservable<bool> obsMatch)
public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch)
{
if (!IsVerfifiedAndConverted)
VerifyConvertKeyFrames(animation, typeof(T));
@ -91,8 +91,16 @@ namespace Avalonia.Animation.Keyframes
/// </summary>
public IObservable<double> SetupAnimation(Animation animation, Animatable control)
{
var newTimer = Timing.GetAnimationsTimer(control, animation.Duration, animation.Delay);
return newTimer.Select(t => animation.Easing.Ease(t));
var newStateMachine = new KeyFramesStateMachine();
newStateMachine.Start(animation, control);
var playStateObs = Timing.AnimationStateTimer
.TakeWhile(p => !newStateMachine._unsubscribe)
.Select(p =>
{
return newStateMachine.Step(p);
});
// var newTimer = Timing.GetAnimationsTimer(animation, control, animation.Duration, animation.Delay);
return playStateObs.Select(t => animation.Easing.Ease(t));
}

131
src/Avalonia.Animation/Keyframes/KeyFramesStateMachine.cs

@ -0,0 +1,131 @@
using System;
namespace Avalonia.Animation.Keyframes
{
/// <summary>
/// Provides statefulness for an iteration of a keyframe group animation.
/// </summary>
internal class KeyFramesStateMachine
{
ulong _delayTotalFrameCount,
_durationTotalFrameCount,
_delayFrameCount,
_durationFrameCount,
_repeatCount,
_currentIteration,
_totalIteration;
bool _isLooping, _isRepeating, _isReversed;
private Direction _animationDirection;
KeyFramesStates _currentState;
internal bool _unsubscribe = false;
private Animation _parentAnimation;
private Animatable _targetAnimatable;
private enum KeyFramesStates
{
INITIALIZE,
DO_DELAY,
DO_RUN,
RUN_FORWARDS,
RUN_BACKWARDS,
RUN_COMPLETE,
STOP
}
public void Start(Animation animation, Animatable control)
{
this._parentAnimation = animation;
this._targetAnimatable = control;
// int _delayFrameCount,
// _durationFrameCount,
// _repeatCount,
// _iterationDirection,
// _currentIteration,
// _totalIteration;
_delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks);
_durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks);
if (_delayTotalFrameCount > 0)
{
_currentState = KeyFramesStates.DO_DELAY;
}
switch (animation.RepeatBehavior)
{
case RepeatBehavior.Loop:
_isLooping = true;
break;
case RepeatBehavior.Repeat:
_isRepeating = true;
if (animation.RepeatCount != null)
{
if (animation.RepeatCount == 0)
{
throw new InvalidOperationException
($"RepeatCount should be greater than zero when RepeatBehavior is set to Repeat.");
}
_repeatCount = (ulong)animation.RepeatCount;
}
else
{
throw new InvalidOperationException
($"RepeatCount should be defined when RepeatBehavior is set to Repeat.");
}
break;
}
this._animationDirection = animation.PlaybackDirection;
_currentState = KeyFramesStates.DO_RUN;
}
private void SetInitialPlaybackDirection(bool isReversed)
{
_isReversed = isReversed;
}
private bool GetPlaybackDirection()
{
_isReversed = !_isReversed;
return _isReversed;
}
public double Step(PlayState _playState)
{
if (_playState == PlayState.Stop) _currentState = KeyFramesStates.STOP;
switch (_currentState)
{
case KeyFramesStates.DO_DELAY:
if (_delayFrameCount >= _delayTotalFrameCount)
{
_currentState = KeyFramesStates.DO_RUN;
}
_delayFrameCount++;
return 0d;
case KeyFramesStates.DO_RUN:
break;
case KeyFramesStates.RUN_FORWARDS:
// break;
case KeyFramesStates.RUN_BACKWARDS:
// break;
case KeyFramesStates.RUN_COMPLETE:
// break;
case KeyFramesStates.STOP:
_unsubscribe = true;
break;
}
return 0;
}
}
}

3
src/Avalonia.Animation/PlayState.cs

@ -7,6 +7,7 @@ namespace Avalonia.Animation
public enum PlayState
{
Run,
Pause
Pause,
Stop
}
}

35
src/Avalonia.Animation/Timing.cs

@ -26,14 +26,14 @@ namespace Avalonia.Animation
/// <summary>
/// The time span of each frame.
/// </summary>
private static readonly TimeSpan Tick = TimeSpan.FromSeconds(1.0 / FramesPerSecond);
internal static readonly TimeSpan FrameTick = TimeSpan.FromSeconds(1.0 / FramesPerSecond);
/// <summary>
/// Initializes static members of the <see cref="Timing"/> class.
/// </summary>
static Timing()
{
var globalTimer = Observable.Interval(Tick, AvaloniaScheduler.Instance);
var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance);
AnimationStateTimer = globalTimer
.Select(_ =>
@ -72,10 +72,9 @@ namespace Avalonia.Animation
/// Gets the animation timer.
/// </summary>
/// <remarks>
/// The animation timer increments usually 60 times per second as
/// The animation timer triggers usually at 60 times per second or as
/// defined in <see cref="FramesPerSecond"/>.
/// The parameter passed to a subsciber is the number of frames since the animation system was
/// initialized.
/// The parameter passed to a subsciber is the current playstate of the animation.
/// </remarks>
internal static IObservable<PlayState> AnimationStateTimer
{
@ -96,30 +95,6 @@ namespace Avalonia.Animation
get;
}
/// <summary>
/// Gets a timer that fires every frame for the specified duration with delay.
/// This timer's running state can be changed via <see cref="SetGlobalPlayState"/> method.
/// </summary>
/// <returns>
/// An observable that notifies the subscriber of the progress along the animation.
/// </returns>
/// <remarks>
/// The parameter passed to the subscriber is the progress along the animation, with
/// 0 being the start and 1 being the end. The observable is guaranteed to fire 0
/// immediately on subscribe and 1 at the end of the duration.
/// </remarks>
public static IObservable<double> GetAnimationsTimer(Animatable control, TimeSpan duration, TimeSpan delay)
{
var startTime = control._animationTime;
var _duration = (ulong)(duration.Ticks / Tick.Ticks);
var endTime = startTime + _duration;
return control.AnimatableTimer
.TakeWhile(x => x < endTime)
.Select(x => (double)(x - startTime) / _duration)
.StartWith(0.0)
.Concat(Observable.Return(1.0));
}
/// <summary>
/// Gets a timer that fires every frame for the specified duration with delay.
/// </summary>
@ -134,7 +109,7 @@ namespace Avalonia.Animation
public static IObservable<double> GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan))
{
var startTime = _transitionsFrameCount;
var _duration = (ulong)(duration.Ticks / Tick.Ticks);
var _duration = (ulong)(duration.Ticks / FrameTick.Ticks);
var endTime = startTime + _duration;
return TransitionsTimer

6
src/Avalonia.Visuals/Animation/Keyframes/TransformKeyFrames.cs

@ -18,7 +18,7 @@ namespace Avalonia.Animation.Keyframes
DoubleKeyFrames childKeyFrames;
/// <inheritdoc/>
public override IDisposable Apply(Animation animation, Animatable control, ulong IterationToken, IObservable<bool> obsMatch)
public override IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch)
{
var ctrl = (Visual)control;
@ -35,7 +35,7 @@ namespace Avalonia.Animation.Keyframes
// It's a transform object so let's target that.
if (renderTransformType == Property.OwnerType)
{
return childKeyFrames.Apply(animation, ctrl.RenderTransform, IterationToken, obsMatch);
return childKeyFrames.Apply(animation, ctrl.RenderTransform, obsMatch);
}
// It's a TransformGroup and try finding the target there.
else if (renderTransformType == typeof(TransformGroup))
@ -44,7 +44,7 @@ namespace Avalonia.Animation.Keyframes
{
if (transform.GetType() == Property.OwnerType)
{
return childKeyFrames.Apply(animation, transform, IterationToken, obsMatch);
return childKeyFrames.Apply(animation, transform, obsMatch);
}
}
}

Loading…
Cancel
Save