Browse Source

Enable Animations to run on a non-global clock.

pull/1715/head
Jeremy Koritzinsky 8 years ago
parent
commit
bf1b78c4ab
  1. 15
      src/Avalonia.Animation/Animation.cs
  2. 8
      src/Avalonia.Animation/AnimationInstance`1.cs
  3. 8
      src/Avalonia.Animation/Animator`1.cs
  4. 10
      src/Avalonia.Animation/Clock.cs
  5. 4
      src/Avalonia.Animation/IAnimation.cs
  6. 2
      src/Avalonia.Animation/IAnimator.cs
  7. 2
      src/Avalonia.Styling/Styling/Style.cs
  8. 5
      src/Avalonia.Visuals/Animation/RenderLoopClock.cs
  9. 6
      src/Avalonia.Visuals/Animation/TransformAnimator.cs

15
src/Avalonia.Animation/Animation.cs

@ -154,12 +154,12 @@ namespace Avalonia.Animation
}
/// <inheritdocs/>
public IDisposable Apply(Animatable control, IObservable<bool> match, Action onComplete)
public IDisposable Apply(Animatable control, Clock clock, IObservable<bool> match, Action onComplete)
{
var (animators, subscriptions) = InterpretKeyframes(control);
if (animators.Count == 1)
{
subscriptions.Add(animators[0].Apply(this, control, match, onComplete));
subscriptions.Add(animators[0].Apply(this, control, clock, match, onComplete));
}
else
{
@ -173,7 +173,7 @@ namespace Avalonia.Animation
animatorOnComplete = () => tcs.SetResult(null);
completionTasks.Add(tcs.Task);
}
subscriptions.Add(animator.Apply(this, control, match, animatorOnComplete));
subscriptions.Add(animator.Apply(this, control, clock, match, animatorOnComplete));
}
if (onComplete != null)
@ -185,15 +185,20 @@ namespace Avalonia.Animation
}
/// <inheritdocs/>
public Task RunAsync(Animatable control)
public Task RunAsync(Animatable control, Clock clock = null)
{
if (clock == null)
{
clock = Clock.GlobalClock;
}
var run = new TaskCompletionSource<object>();
if (this.RepeatCount == RepeatCount.Loop)
run.SetException(new InvalidOperationException("Looping animations must not use the Run method."));
IDisposable subscriptions = null;
subscriptions = this.Apply(control, Observable.Return(true), () =>
subscriptions = this.Apply(control, clock, Observable.Return(true), () =>
{
run.SetResult(null);
subscriptions?.Dispose();

8
src/Avalonia.Animation/AnimationInstance`1.cs

@ -34,8 +34,9 @@ namespace Avalonia.Animation
private Action _onCompleteAction;
private Func<double, T, T> _interpolator;
private IDisposable _timerSubscription;
private readonly Clock _clock;
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, Action OnComplete, Func<double, T, T> Interpolator)
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, Clock clock, Action OnComplete, Func<double, T, T> Interpolator)
{
if (animation.SpeedRatio <= 0)
throw new InvalidOperationException("Speed ratio cannot be negative or zero.");
@ -71,6 +72,7 @@ namespace Avalonia.Animation
_fillMode = animation.FillMode;
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_clock = clock;
}
protected override void Unsubscribed()
@ -80,7 +82,7 @@ namespace Avalonia.Animation
protected override void Subscribed()
{
_timerSubscription = Clock.GlobalClock.Subscribe(Step);
_timerSubscription = _clock.Subscribe(Step);
}
public void Step(TimeSpan frameTick)
@ -115,7 +117,7 @@ namespace Avalonia.Animation
private void DoPlayStatesAndTime(TimeSpan systemTime)
{
if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop)
if (_clock.PlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop)
DoComplete();
if (!_gotFirstKFValue)

8
src/Avalonia.Animation/Animator`1.cs

@ -32,7 +32,7 @@ namespace Avalonia.Animation
}
/// <inheritdoc/>
public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> match, Action onComplete)
public virtual IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable<bool> match, Action onComplete)
{
if (!_isVerifiedAndConverted)
VerifyConvertKeyFrames();
@ -41,7 +41,7 @@ namespace Avalonia.Animation
.Where(p => p)
.Subscribe(_ =>
{
var timerObs = RunKeyFrames(animation, control, onComplete);
var timerObs = RunKeyFrames(animation, control, clock, onComplete);
});
}
@ -101,9 +101,9 @@ namespace Avalonia.Animation
/// <summary>
/// Runs the KeyFrames Animation.
/// </summary>
private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete)
private IDisposable RunKeyFrames(Animation animation, Animatable control, Clock clock, Action onComplete)
{
var instance = new AnimationInstance<T>(animation, control, this, onComplete, DoInterpolation);
var instance = new AnimationInstance<T>(animation, control, this, clock, onComplete, DoInterpolation);
return control.Bind<T>((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);
}

10
src/Avalonia.Animation/Clock.cs

@ -58,6 +58,16 @@ namespace Avalonia.Animation
_observable.Pulse(_internalTime);
CurrentTime = _internalTime;
if (PlayState == PlayState.Stop)
{
Stop();
}
}
protected virtual void Stop()
{
_parentSubscription?.Dispose();
}
public IDisposable Subscribe(IObserver<TimeSpan> observer)

4
src/Avalonia.Animation/IAnimation.cs

@ -11,11 +11,11 @@ namespace Avalonia.Animation
/// <summary>
/// Apply the animation to the specified control
/// </summary>
IDisposable Apply(Animatable control, IObservable<bool> match, Action onComplete = null);
IDisposable Apply(Animatable control, Clock clock, IObservable<bool> match, Action onComplete = null);
/// <summary>
/// Run the animation to the specified control
/// </summary>
Task RunAsync(Animatable control);
Task RunAsync(Animatable control, Clock clock);
}
}

2
src/Avalonia.Animation/IAnimator.cs

@ -16,6 +16,6 @@ namespace Avalonia.Animation
/// <summary>
/// Applies the current KeyFrame group to the specified control.
/// </summary>
IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch, Action onComplete);
IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable<bool> obsMatch, Action onComplete);
}
}

2
src/Avalonia.Styling/Styling/Style.cs

@ -120,7 +120,7 @@ namespace Avalonia.Styling
obsMatch = Observable.Return(true);
}
var sub = animation.Apply((Animatable)control, obsMatch);
var sub = animation.Apply((Animatable)control, Clock.GlobalClock, obsMatch);
subs.Add(sub);
}

5
src/Avalonia.Visuals/Animation/RenderLoopClock.cs

@ -7,6 +7,11 @@ namespace Avalonia.Animation
{
public class RenderLoopClock : Clock, IRenderLoopTask
{
protected override void Stop()
{
AvaloniaLocator.Current.GetService<IRenderLoop>().Remove(this);
}
bool IRenderLoopTask.NeedsUpdate => HasSubscriptions;
void IRenderLoopTask.Render()

6
src/Avalonia.Visuals/Animation/TransformAnimator.cs

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

Loading…
Cancel
Save