diff --git a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs index e4276fa1b5..7b89b7944c 100644 --- a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs +++ b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs @@ -8,7 +8,7 @@ namespace RenderDemo.ViewModels { private bool _isPlaying = true; - private string _playStateText = "Pause all animations"; + private string _playStateText = "Pause animations on this page"; public void TogglePlayState() { diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 65463f3d52..d7efc69e10 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -177,11 +177,6 @@ namespace Avalonia.Animation /// public Task RunAsync(Animatable control, IClock clock = null) { - if (clock == null) - { - clock = Clock.GlobalClock; - } - var run = new TaskCompletionSource(); if (this.RepeatCount == RepeatCount.Loop) diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 99ebbe752a..468c9c93a4 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -19,7 +19,6 @@ namespace Avalonia.Animation private double _currentIteration; private bool _isLooping; private bool _gotFirstKFValue; - private bool _gotFirstFrameCount; private bool _iterationDelay; private FillMode _fillMode; private PlaybackDirection _animationDirection; @@ -29,14 +28,14 @@ namespace Avalonia.Animation private double _speedRatio; private TimeSpan _delay; private TimeSpan _duration; - private TimeSpan _firstFrameCount; private Easings.Easing _easeFunc; private Action _onCompleteAction; private Func _interpolator; private IDisposable _timerSubscription; - private readonly IClock _clock; + private readonly IClock _baseClock; + private IClock _clock; - public AnimationInstance(Animation animation, Animatable control, Animator animator, Clock clock, Action OnComplete, Func Interpolator) + public AnimationInstance(Animation animation, Animatable control, Animator animator, IClock baseClock, Action OnComplete, Func Interpolator) { if (animation.SpeedRatio <= 0) throw new InvalidOperationException("Speed ratio cannot be negative or zero."); @@ -72,16 +71,18 @@ namespace Avalonia.Animation _fillMode = animation.FillMode; _onCompleteAction = OnComplete; _interpolator = Interpolator; - _clock = clock; + _baseClock = baseClock; } protected override void Unsubscribed() { _timerSubscription?.Dispose(); + _clock.PlayState = PlayState.Stop; } protected override void Subscribed() { + _clock = new Clock(_baseClock); _timerSubscription = _clock.Subscribe(Step); } @@ -115,9 +116,9 @@ namespace Avalonia.Animation PublishNext(_lastInterpValue); } - private void DoPlayStatesAndTime(TimeSpan systemTime) + private void DoPlayStates() { - if (_clock.PlayState == PlayState.Stop) + if (_clock.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop) DoComplete(); if (!_gotFirstKFValue) @@ -125,19 +126,12 @@ namespace Avalonia.Animation _firstKFValue = (T)_parent.First().Value; _gotFirstKFValue = true; } - - if (!_gotFirstFrameCount) - { - _firstFrameCount = systemTime; - _gotFirstFrameCount = true; - } } - private void InternalStep(TimeSpan systemTime) + private void InternalStep(TimeSpan time) { - DoPlayStatesAndTime(systemTime); - - var time = systemTime - _firstFrameCount; + DoPlayStates(); + var delayEndpoint = _delay; var iterationEndpoint = delayEndpoint + _duration; @@ -158,14 +152,14 @@ namespace Avalonia.Animation } //Calculate the current iteration number - _currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2; + _currentIteration = (int)Math.Floor((double)((double)time.Ticks / iterationEndpoint.Ticks)) + 2; } else { return; } - time = TimeSpan.FromTicks(time.Ticks % iterationEndpoint.Ticks); + time = TimeSpan.FromTicks((long)(time.Ticks % iterationEndpoint.Ticks)); if (!_isLooping) { diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index b68f2fc79a..b79e2d9342 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -52,52 +52,72 @@ namespace Avalonia.Animation /// (i.e., the normalized time between the selected keyframes, relative to the /// time parameter). /// - /// The time parameter, relative to the total animation time - protected (double IntraKFTime, KeyFramePair KFPair) GetKFPairAndIntraKFTime(double t) + /// The time parameter, relative to the total animation time + protected (double IntraKFTime, KeyFramePair KFPair) GetKFPairAndIntraKFTime(double animationTime) { - AnimatorKeyFrame firstCue, lastCue ; + AnimatorKeyFrame firstKeyframe, lastKeyframe ; int kvCount = _convertedKeyframes.Count; if (kvCount > 2) { - if (t <= 0.0) + if (animationTime <= 0.0) { - firstCue = _convertedKeyframes[0]; - lastCue = _convertedKeyframes[1]; + firstKeyframe = _convertedKeyframes[0]; + lastKeyframe = _convertedKeyframes[1]; } - else if (t >= 1.0) + else if (animationTime >= 1.0) { - firstCue = _convertedKeyframes[_convertedKeyframes.Count - 2]; - lastCue = _convertedKeyframes[_convertedKeyframes.Count - 1]; + firstKeyframe = _convertedKeyframes[_convertedKeyframes.Count - 2]; + lastKeyframe = _convertedKeyframes[_convertedKeyframes.Count - 1]; } else { - (double time, int index) maxval = (0.0d, 0); - for (int i = 0; i < _convertedKeyframes.Count; i++) - { - var comp = _convertedKeyframes[i].Cue.CueValue; - if (t >= comp) - { - maxval = (comp, i); - } - } - firstCue = _convertedKeyframes[maxval.index]; - lastCue = _convertedKeyframes[maxval.index + 1]; + int index = FindClosestBeforeKeyFrame(animationTime); + firstKeyframe = _convertedKeyframes[index]; + lastKeyframe = _convertedKeyframes[index + 1]; } } else { - firstCue = _convertedKeyframes[0]; - lastCue = _convertedKeyframes[1]; + firstKeyframe = _convertedKeyframes[0]; + lastKeyframe = _convertedKeyframes[1]; } - double t0 = firstCue.Cue.CueValue; - double t1 = lastCue.Cue.CueValue; - var intraframeTime = (t - t0) / (t1 - t0); - var firstFrameData = (firstCue.GetTypedValue(), firstCue.isNeutral); - var lastFrameData = (lastCue.GetTypedValue(), lastCue.isNeutral); + double t0 = firstKeyframe.Cue.CueValue; + double t1 = lastKeyframe.Cue.CueValue; + var intraframeTime = (animationTime - t0) / (t1 - t0); + var firstFrameData = (firstKeyframe.GetTypedValue(), firstKeyframe.isNeutral); + var lastFrameData = (lastKeyframe.GetTypedValue(), lastKeyframe.isNeutral); return (intraframeTime, new KeyFramePair(firstFrameData, lastFrameData)); } + private int FindClosestBeforeKeyFrame(double time) + { + int FindClosestBeforeKeyFrame(int startIndex, int length) + { + if (length == 0 || length == 1) + { + return startIndex; + } + + int middle = startIndex + (length / 2); + + if (_convertedKeyframes[middle].Cue.CueValue < time) + { + return FindClosestBeforeKeyFrame(middle, length - middle); + } + else if (_convertedKeyframes[middle].Cue.CueValue > time) + { + return FindClosestBeforeKeyFrame(startIndex, middle - startIndex); + } + else + { + return middle; + } + } + + return FindClosestBeforeKeyFrame(0, _convertedKeyframes.Count); + } + /// /// Runs the KeyFrames Animation. /// @@ -130,14 +150,6 @@ namespace Avalonia.Animation AddNeutralKeyFramesIfNeeded(); - var copy = _convertedKeyframes.ToList().OrderBy(p => p.Cue.CueValue); - _convertedKeyframes.Clear(); - - foreach (AnimatorKeyFrame keyframe in copy) - { - _convertedKeyframes.Add(keyframe); - } - _isVerifiedAndConverted = true; } @@ -167,7 +179,7 @@ namespace Avalonia.Animation { if (!hasStartKey) { - _convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true }); + _convertedKeyframes.Insert(0, new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true }); } if (!hasEndKey) diff --git a/src/Avalonia.Animation/ClockBase.cs b/src/Avalonia.Animation/ClockBase.cs index ea784269d9..a2b29e728e 100644 --- a/src/Avalonia.Animation/ClockBase.cs +++ b/src/Avalonia.Animation/ClockBase.cs @@ -21,9 +21,7 @@ namespace Avalonia.Animation _connectedObservable = _observable.Publish().RefCount(); } - public bool HasSubscriptions => _observable.HasSubscriptions; - - public TimeSpan CurrentTime { get; private set; } + protected bool HasSubscriptions => _observable.HasSubscriptions; public PlayState PlayState { get; set; } @@ -47,7 +45,6 @@ namespace Avalonia.Animation } _observable.Pulse(_internalTime); - CurrentTime = _internalTime; if (PlayState == PlayState.Stop) { diff --git a/src/Avalonia.Animation/IClock.cs b/src/Avalonia.Animation/IClock.cs index 58c997841d..ae44102077 100644 --- a/src/Avalonia.Animation/IClock.cs +++ b/src/Avalonia.Animation/IClock.cs @@ -6,8 +6,6 @@ namespace Avalonia.Animation { public interface IClock : IObservable { - bool HasSubscriptions { get; } - TimeSpan CurrentTime { get; } PlayState PlayState { get; set; } } } diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index ad87ad7010..eff2c4e9f3 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -15,23 +15,22 @@ namespace Avalonia.Animation /// internal class TransitionInstance : SingleSubscriberObservableBase { - private IDisposable timerSubscription; - private TimeSpan startTime; - private TimeSpan duration; - private readonly IClock _clock; + private IDisposable _timerSubscription; + private TimeSpan _duration; + private readonly IClock _baseClock; + private IClock _clock; - public TransitionInstance(Clock clock, TimeSpan Duration) + public TransitionInstance(IClock clock, TimeSpan Duration) { - duration = Duration; - _clock = clock; + _duration = Duration; + _baseClock = clock; } private void TimerTick(TimeSpan t) { - var interpVal = (double)(t.Ticks - startTime.Ticks) / duration.Ticks; + var interpVal = (double)t.Ticks / _duration.Ticks; - if (interpVal > 1d - || interpVal < 0d) + if (interpVal > 1d || interpVal < 0d) { PublishCompleted(); return; @@ -42,13 +41,14 @@ namespace Avalonia.Animation protected override void Unsubscribed() { - timerSubscription?.Dispose(); + _timerSubscription?.Dispose(); + _clock.PlayState = PlayState.Stop; } protected override void Subscribed() { - startTime = _clock.CurrentTime; - timerSubscription = _clock.Subscribe(TimerTick); + _clock = new Clock(_baseClock); + _timerSubscription = _clock.Subscribe(TimerTick); PublishNext(0.0d); } } diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition`1.cs index b54ec8f51c..cd0d5d9ce9 100644 --- a/src/Avalonia.Animation/Transition`1.cs +++ b/src/Avalonia.Animation/Transition`1.cs @@ -14,7 +14,6 @@ namespace Avalonia.Animation public abstract class Transition : AvaloniaObject, ITransition { private AvaloniaProperty _prop; - private Easing _easing; /// /// Gets the duration of the animation. @@ -54,7 +53,5 @@ namespace Avalonia.Animation var transition = DoTransition(new TransitionInstance(clock, Duration), (T)oldValue, (T)newValue); return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } - - } }