Browse Source

Pass target control to the keyframes and other animation classes.

Make the global timer discrete to avoid costly TimeSpan conversions and enable global animation pause/play support.
pull/1461/head
Jumar Macato 9 years ago
parent
commit
fdeb946af6
  1. 33
      src/Avalonia.Animation/Animatable.cs
  2. 17
      src/Avalonia.Animation/Keyframes/DoubleKeyFrames.cs
  3. 7
      src/Avalonia.Animation/Keyframes/KeyFrames.cs
  4. 45
      src/Avalonia.Animation/Timing.cs
  5. 2
      src/Avalonia.Animation/Transitions/Transition.cs

33
src/Avalonia.Animation/Animatable.cs

@ -10,11 +10,44 @@ using Avalonia.Animation.Transitions;
namespace Avalonia.Animation
{
/// <summary>
///
/// </summary>
public enum AnimationPlayState
{
/// <summary>
///
/// </summary>
Running,
/// <summary>
///
/// </summary>
Paused
}
/// <summary>
/// Base class for control which can have property transitions.
/// </summary>
public class Animatable : AvaloniaObject
{
/// <summary>
/// Defines the <see cref="AnimationPlayState"/> property.
/// </summary>
public static readonly StyledProperty<AnimationPlayState> AnimationPlayStateProperty =
AvaloniaProperty.Register<Animatable, AnimationPlayState>(nameof(AnimationPlayState));
/// <summary>
/// Gets or sets the state of the animation for this
/// control.
/// </summary>
public AnimationPlayState AnimationPlayState
{
get { return GetValue(AnimationPlayStateProperty); }
set { SetValue(AnimationPlayStateProperty, value); }
}
/// <summary>
/// Defines the <see cref="Transitions"/> property.
/// </summary>

17
src/Avalonia.Animation/Keyframes/DoubleKeyFrames.cs

@ -17,20 +17,19 @@ namespace Avalonia.Animation.Keyframes
/// <inheritdocs/>
public override IObservable<double> DoInterpolation(Animation animation, Animatable control)
=> GetKeyFramesTimer(animation)
=> SetupAnimation(animation, control)
.Select(t =>
{
// Get a pair of keyframes to make the interpolation.
var pair = GetKeyFramePairByTime(t);
var pair = GetKeyFramePairByTime(t.Time);
// Do linear parametric interpolation
double y0 = pair.firstKF.Value;
double t0 = pair.firstKF.Key;
double y1 = pair.lastKF.Value;
double t1 = pair.lastKF.Key;
double y0 = pair.FirstKeyFrame.Value;
double t0 = pair.FirstKeyFrame.Key;
double y1 = pair.SecondKeyFrame.Value;
double t1 = pair.SecondKeyFrame.Key;
// Calculate the final interpolated value
return y0 + ((t - t0) / (t1 - t0)) * (y1 - y0);
// Do linear parametric interpolation
return y0 + ((t.Time - t0) / (t1 - t0)) * (y1 - y0);
});
}

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

@ -113,9 +113,10 @@ namespace Avalonia.Animation.Keyframes
/// Returns an observable timer with the specific Animation
/// duration and delay and applies the Animation's easing function.
/// </summary>
public IObservable<double> GetKeyFramesTimer(Animation animation) =>
Timing.GetTimer(animation.Duration, animation.Delay)
.Select(t => animation.Easing.Ease(t));
public IObservable<(double Time, Animatable Target)>
SetupAnimation(Animation animation, Animatable control) =>
Timing.GetTimer(control, animation.Duration, animation.Delay)
.Select(t => (animation.Easing.Ease(t), control));
/// <summary>
/// Interpolates the given keyframes to the control.

45
src/Avalonia.Animation/Timing.cs

@ -25,6 +25,8 @@ namespace Avalonia.Animation
/// </summary>
private static readonly TimeSpan Tick = TimeSpan.FromSeconds(1.0 / FramesPerSecond);
static ulong _frameCount = 0;
/// <summary>
/// Initializes static members of the <see cref="Timing"/> class.
/// </summary>
@ -33,7 +35,11 @@ namespace Avalonia.Animation
Stopwatch = new Stopwatch();
Stopwatch.Start();
Timer = Observable.Interval(Tick, AvaloniaScheduler.Instance)
.Select(_ => Stopwatch.Elapsed)
.Select(_ =>
{
_frameCount += 1;
return _frameCount;
})
.Publish()
.RefCount();
}
@ -54,39 +60,17 @@ namespace Avalonia.Animation
/// </summary>
/// <remarks>
/// The animation timer ticks <see cref="FramesPerSecond"/> times per second. The
/// parameter passed to a subsciber is the time span since the animation system was
/// parameter passed to a subsciber is the number of frames since the animation system was
/// initialized.
/// </remarks>
/// <value>
/// The animation timer.
/// </value>
public static IObservable<TimeSpan> Timer
public static IObservable<ulong> Timer
{
get;
}
/// <summary>
/// Gets a timer that fires every frame for the specified duration.
/// </summary>
/// <param name="duration">The duration of the animation.</param>
/// <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> GetTimer(TimeSpan duration)
{
var startTime = Stopwatch.Elapsed.Ticks;
var endTime = startTime + duration.Ticks;
return Timer
.TakeWhile(x => x.Ticks < endTime)
.Select(x => (x.Ticks - startTime) / (double)duration.Ticks)
.StartWith(0.0)
.Concat(Observable.Return(1.0));
}
/// <summary>
/// Gets a timer that fires every frame for the specified duration with delay.
@ -99,13 +83,14 @@ namespace Avalonia.Animation
/// 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> GetTimer(TimeSpan duration, TimeSpan delay)
public static IObservable<double> GetTimer(Animatable control, TimeSpan duration, TimeSpan delay)
{
var startTime = Stopwatch.Elapsed.Ticks + delay.Ticks;
var endTime = startTime + duration.Ticks;
var startTime = _frameCount;
var _duration = (ulong)(duration.Ticks/Tick.Ticks);
var endTime = startTime + _duration;
return Timer
.TakeWhile(x => x.Ticks < endTime)
.Select(x => (x.Ticks - startTime) / (double)duration.Ticks)
.TakeWhile(x => x < endTime)
.Select(x => (double)(x - startTime) / _duration)
.StartWith(0.0)
.Concat(Observable.Return(1.0));
}

2
src/Avalonia.Animation/Transitions/Transition.cs

@ -61,7 +61,7 @@ namespace Avalonia.Animation.Transitions
/// <inheritdocs/>
public IDisposable Apply(Animatable control, object oldValue, object newValue)
{
var transition = DoTransition(Timing.GetTimer(Duration), (T)oldValue, (T)newValue).Select(p => (object)p);
var transition = DoTransition(Timing.GetTimer(control, Duration, TimeSpan.Zero), (T)oldValue, (T)newValue).Select(p => (object)p);
return control.Bind(Property, transition, Data.BindingPriority.Animation);
}

Loading…
Cancel
Save