diff --git a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs index c76d4db513..f724baf3c6 100644 --- a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs +++ b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs @@ -15,16 +15,16 @@ namespace RenderDemo.ViewModels void TogglePlayState() { - switch (Timing.GlobalPlayState) + switch (Animation.GlobalPlayState) { case PlayState.Run: PlayStateText = "Resume all animations"; - Timing.GlobalPlayState = PlayState.Pause; + Animation.GlobalPlayState = PlayState.Pause; break; case PlayState.Pause: PlayStateText = "Pause all animations"; - Timing.GlobalPlayState = PlayState.Run; + Animation.GlobalPlayState = PlayState.Run; break; } } diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index da2fc75c0b..204cc9d04d 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -21,6 +21,10 @@ namespace Avalonia.Animation /// public class Animation : AvaloniaList, IAnimation { + /// + /// Gets or sets the animation play state for all animations + /// + public static PlayState GlobalPlayState { get; set; } = PlayState.Run; public AvaloniaList _animators { get; set; } = new AvaloniaList(); @@ -73,7 +77,6 @@ namespace Avalonia.Animation /// public bool DelayBetweenIterations { get; set; } - private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> { ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ) diff --git a/src/Avalonia.Animation/AnimationsEngine`1.cs b/src/Avalonia.Animation/AnimationsEngine`1.cs index 169f0a7ae0..cccb3098c0 100644 --- a/src/Avalonia.Animation/AnimationsEngine`1.cs +++ b/src/Avalonia.Animation/AnimationsEngine`1.cs @@ -111,7 +111,7 @@ namespace Avalonia.Animation private void DoPlayStatesAndTime(TimeSpan systemTime) { - if (Timing.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop) + if (Animation.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop) DoComplete(); if (!previousClock.HasValue) @@ -121,7 +121,7 @@ namespace Avalonia.Animation } else { - if (Timing.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause) + if (Animation.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause) { previousClock = systemTime; return; diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index a8b5ce7a27..8079ac69b5 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -98,7 +98,7 @@ namespace Avalonia.Animation /// private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete) { - var stateMachine = new AnimatorStateMachine(animation, control, this, onComplete); + var stateMachine = new AnimationsEngine(animation, control, this, onComplete); Timing.AnimationsTimer .TakeWhile(_ => !stateMachine.unsubscribe) diff --git a/src/Avalonia.Animation/Timing.cs b/src/Avalonia.Animation/Timing.cs index 575cedc620..b8282c05d0 100644 --- a/src/Avalonia.Animation/Timing.cs +++ b/src/Avalonia.Animation/Timing.cs @@ -15,11 +15,6 @@ namespace Avalonia.Animation /// public static class Timing { - /// - /// Gets or sets the animation play state for all animations - /// - public static PlayState GlobalPlayState { get; set; } = PlayState.Run; - /// /// The number of frames per second. /// @@ -38,14 +33,13 @@ namespace Avalonia.Animation var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance); AnimationsTimer = globalTimer - .Select(_ => - { - return TimeSpan.FromMilliseconds(Environment.TickCount); - }) + .Select(_ => GetTickCount()) .Publish() .RefCount(); } + internal static TimeSpan GetTickCount() => TimeSpan.FromMilliseconds(Environment.TickCount); + /// /// Gets the animation timer. /// @@ -58,31 +52,5 @@ namespace Avalonia.Animation { get; } - - /// - /// Gets a timer that fires every frame for the specified duration with delay. - /// - /// - /// An observable that notifies the subscriber of the progress along the transition. - /// - /// - /// The parameter passed to the subscriber is the progress along the transition, 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. - /// - public static IObservable GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan)) - { - // TODO: Fix this mess. - // var _duration = (duration.Ticks / FrameTick.Ticks); - // long? endTime = ((Stopwatch.GetTimestamp() - _tickStartTimeStamp) - // / TicksPerFrame) + _duration; - - // return AnimationsTimer - // .TakeWhile(x => x < endTime) - // .Select(x => (double)x / _duration) - // .StartWith(0.0) - // .Concat(Observable.Return(1.0)); - return Observable.Empty(); - } } } \ No newline at end of file diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition`1.cs index c097b930a5..346c328809 100644 --- a/src/Avalonia.Animation/Transition`1.cs +++ b/src/Avalonia.Animation/Transition`1.cs @@ -5,6 +5,7 @@ using Avalonia.Metadata; using System; using System.Reactive.Linq; using Avalonia.Animation.Easings; +using Avalonia.Animation.Utils; namespace Avalonia.Animation { @@ -51,8 +52,10 @@ namespace Avalonia.Animation /// public virtual IDisposable Apply(Animatable control, object oldValue, object newValue) { - var transition = DoTransition(Timing.GetTransitionsTimer(control, Duration, TimeSpan.Zero), (T)oldValue, (T)newValue); + var transition = DoTransition(new TransitionsEngine(Duration), (T)oldValue, (T)newValue); return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } + + } } \ No newline at end of file diff --git a/src/Avalonia.Animation/TransitionsEngine.cs b/src/Avalonia.Animation/TransitionsEngine.cs new file mode 100644 index 0000000000..4d52b2bd48 --- /dev/null +++ b/src/Avalonia.Animation/TransitionsEngine.cs @@ -0,0 +1,58 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Metadata; +using System; +using System.Reactive.Linq; +using Avalonia.Animation.Easings; +using Avalonia.Animation.Utils; + +namespace Avalonia.Animation +{ + public class TransitionsEngine : IObservable, IDisposable + { + private IObserver observer; + private IDisposable timerSubscription; + private readonly TimeSpan startTime; + private readonly TimeSpan duration; + + public TransitionsEngine(TimeSpan Duration) + { + startTime = Timing.GetTickCount(); + duration = Duration; + + timerSubscription = Timing + .AnimationsTimer + .Subscribe(t => TimerTick(t)); + } + + private void TimerTick(TimeSpan t) + { + var interpVal = (double)(t.Ticks - startTime.Ticks) / duration.Ticks; + + if (interpVal > 1d + || interpVal < 0d) + { + this.Dispose(); + return; + } + + observer?.OnNext(interpVal); + } + + public void Dispose() + { + timerSubscription?.Dispose(); + observer?.OnCompleted(); + } + + public IDisposable Subscribe(IObserver Observer) + { + if (Observer is null) + throw new InvalidProgramException("Can only set the subscription once."); + + observer = Observer; + return this; + } + } +} \ No newline at end of file