From 5e8bb904c9256ec4e69e8fba1747055b42561b64 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 17 Dec 2014 01:43:10 +0100 Subject: [PATCH] Use a single timer for all animation. Still renders >60fps when there are two concurrent animations though. Hmm. --- Cairo/Perspex.Cairo/Perspex.Cairo.csproj | 4 + Perspex.Animation/Animate.cs | 110 +++++++++++++++--- Perspex.SceneGraph/IVisual.cs | 2 +- Perspex.SceneGraph/Media/Geometry.cs | 8 +- Perspex.SceneGraph/Media/ITransform.cs | 17 --- Perspex.SceneGraph/Media/Transform.cs | 2 +- Perspex.SceneGraph/Perspex.SceneGraph.csproj | 1 - Perspex.SceneGraph/Visual.cs | 10 +- TestApplication/Program.cs | 20 ++-- .../Perspex.Direct2D1.csproj | 4 + 10 files changed, 126 insertions(+), 52 deletions(-) delete mode 100644 Perspex.SceneGraph/Media/ITransform.cs diff --git a/Cairo/Perspex.Cairo/Perspex.Cairo.csproj b/Cairo/Perspex.Cairo/Perspex.Cairo.csproj index 12985d10b8..c8843d6523 100644 --- a/Cairo/Perspex.Cairo/Perspex.Cairo.csproj +++ b/Cairo/Perspex.Cairo/Perspex.Cairo.csproj @@ -76,6 +76,10 @@ + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base diff --git a/Perspex.Animation/Animate.cs b/Perspex.Animation/Animate.cs index ac08c6a2ab..266585a13f 100644 --- a/Perspex.Animation/Animate.cs +++ b/Perspex.Animation/Animate.cs @@ -7,48 +7,126 @@ namespace Perspex.Animation { using System; + using System.Diagnostics; using System.Linq; using System.Reactive.Linq; using Perspex.Threading; + /// + /// Utilities for creating animations. + /// public static class Animate { - private const int FramesPerSecond = 30; + /// + /// The number of frames per second. + /// + public const int FramesPerSecond = 60; + /// + /// The time span of each frame. + /// private static readonly TimeSpan Tick = TimeSpan.FromSeconds(1.0 / FramesPerSecond); + /// + /// Initializes the static class. + /// + static Animate() + { + Stopwatch = new Stopwatch(); + Stopwatch.Start(); + Timer = Observable.Interval(Tick, PerspexScheduler.Instance).Select(_ => Stopwatch.Elapsed); + } + + /// + /// The stopwatch used to track time. + /// + public static Stopwatch Stopwatch + { + get; + private set; + } + + /// + /// Gets the animation timer. + /// + /// + /// The animation timer ticks times per second. The + /// parameter passed to a subsciber is the time span since the animation system was + /// initialized. + /// + public static IObservable Timer + { + get; + private set; + } + + /// + /// Gets a timer that fires every frame for the specified duration. + /// + /// The duration of the animation. + /// + /// An observable that notifies the subscriber of the progress along the animation. + /// + /// + /// 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. + /// + public static IObservable 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)); + } + + /// + /// Animates a . + /// + /// The property type. + /// The target object. + /// The target property. + /// The value of the property at the start of the animation. + /// The value of the property at the end of the animation. + /// The easing function to use. + /// The duration of the animation. + /// An that can be used to stop the animation. public static IDisposable Property( PerspexObject target, PerspexProperty property, object start, object finish, IEasing easing, - TimeSpan duration, - double repeats = 1) + TimeSpan duration) { - var startTime = Environment.TickCount; - var runningTime = (double)duration.TotalMilliseconds; - var endTime = Environment.TickCount + (runningTime * repeats); - - var o = Observable.Interval(Tick, PerspexScheduler.Instance) - .Select(_ => Environment.TickCount) - .TakeWhile(tick => tick < endTime) - .Select(tick => easing.Ease((tick - startTime) / runningTime, start, finish)) - .StartWith(start) - .Concat(Observable.Return(finish)); - + var o = GetTimer(duration).Select(progress => easing.Ease(progress, start, finish)); return target.Bind(property, o, BindingPriority.Animation); } + /// + /// Animates a . + /// + /// The property type. + /// The target object. + /// The target property. + /// The value of the property at the start of the animation. + /// The value of the property at the end of the animation. + /// The easing function to use. + /// The duration of the animation. + /// An that can be used to stop the animation. public static IDisposable Property( PerspexObject target, PerspexProperty property, T start, T finish, - IEasing easing, + IEasing easing, TimeSpan duration) { - return Property(target, (PerspexProperty)property, start, finish, easing, duration); + var o = GetTimer(duration).Select(progress => easing.Ease(progress, start, finish)); + return target.Bind(property, o, BindingPriority.Animation); } } } diff --git a/Perspex.SceneGraph/IVisual.cs b/Perspex.SceneGraph/IVisual.cs index 1f1aa8775a..12a1104864 100644 --- a/Perspex.SceneGraph/IVisual.cs +++ b/Perspex.SceneGraph/IVisual.cs @@ -46,7 +46,7 @@ namespace Perspex /// /// Gets the render transform of the scene graph node. /// - ITransform RenderTransform { get; } + Transform RenderTransform { get; } /// /// Gets the transform origin of the scene graph node. diff --git a/Perspex.SceneGraph/Media/Geometry.cs b/Perspex.SceneGraph/Media/Geometry.cs index b0c971576f..0d30a8ca3b 100644 --- a/Perspex.SceneGraph/Media/Geometry.cs +++ b/Perspex.SceneGraph/Media/Geometry.cs @@ -11,14 +11,14 @@ namespace Perspex.Media public abstract class Geometry : PerspexObject { - public static readonly PerspexProperty TransformProperty = - PerspexProperty.Register("Transform"); + public static readonly PerspexProperty TransformProperty = + PerspexProperty.Register("Transform"); static Geometry() { TransformProperty.Changed.Subscribe(x => { - ((Geometry)x.Sender).PlatformImpl.Transform = ((ITransform)x.NewValue).Value; + ((Geometry)x.Sender).PlatformImpl.Transform = ((Transform)x.NewValue).Value; }); } @@ -33,7 +33,7 @@ namespace Perspex.Media protected set; } - public ITransform Transform + public Transform Transform { get { return this.GetValue(TransformProperty); } set { this.SetValue(TransformProperty, value); } diff --git a/Perspex.SceneGraph/Media/ITransform.cs b/Perspex.SceneGraph/Media/ITransform.cs deleted file mode 100644 index 248063d6ec..0000000000 --- a/Perspex.SceneGraph/Media/ITransform.cs +++ /dev/null @@ -1,17 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright 2014 MIT Licence. See licence.md for more information. -// -// ----------------------------------------------------------------------- - -namespace Perspex.Media -{ - using System; - - public interface ITransform - { - event EventHandler Changed; - - Matrix Value { get; } - } -} diff --git a/Perspex.SceneGraph/Media/Transform.cs b/Perspex.SceneGraph/Media/Transform.cs index fa8fc6abe6..af6cb16832 100644 --- a/Perspex.SceneGraph/Media/Transform.cs +++ b/Perspex.SceneGraph/Media/Transform.cs @@ -9,7 +9,7 @@ namespace Perspex.Media using System; using Perspex.Animation; - public abstract class Transform : Animatable, ITransform + public abstract class Transform : Animatable { public event EventHandler Changed; diff --git a/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/Perspex.SceneGraph/Perspex.SceneGraph.csproj index 81cba21aaa..c3ee00e99c 100644 --- a/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -59,7 +59,6 @@ - diff --git a/Perspex.SceneGraph/Visual.cs b/Perspex.SceneGraph/Visual.cs index 3edb0d72f1..28eed3521f 100644 --- a/Perspex.SceneGraph/Visual.cs +++ b/Perspex.SceneGraph/Visual.cs @@ -27,8 +27,8 @@ namespace Perspex public static readonly PerspexProperty OpacityProperty = PerspexProperty.Register("Opacity", 1); - public static readonly PerspexProperty RenderTransformProperty = - PerspexProperty.Register("RenderTransform"); + public static readonly PerspexProperty RenderTransformProperty = + PerspexProperty.Register("RenderTransform"); public static readonly PerspexProperty TransformOriginProperty = PerspexProperty.Register("TransformOrigin", defaultValue: Origin.Default); @@ -69,7 +69,7 @@ namespace Perspex set { this.SetValue(OpacityProperty, value); } } - public ITransform RenderTransform + public Transform RenderTransform { get { return this.GetValue(RenderTransformProperty); } set { this.SetValue(RenderTransformProperty, value); } @@ -215,8 +215,8 @@ namespace Perspex if (sender != null) { - var oldValue = e.OldValue as ITransform; - var newValue = e.NewValue as ITransform; + var oldValue = e.OldValue as Transform; + var newValue = e.NewValue as Transform; if (oldValue != null) { diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 436aa0bdfa..eacf07597c 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -541,14 +541,20 @@ namespace TestApplication } }; - Animate.Property( - (PerspexObject)rect1.RenderTransform, + var start = Animate.Stopwatch.Elapsed; + var degrees = Animate.Timer + .Select(x => + { + var elapsed = (x - start).TotalSeconds; + var cycles = elapsed / 4; + var progress = cycles % 1; + return 360.0 * progress; + }); + + rect1.RenderTransform.Bind( RotateTransform.AngleProperty, - 0.0, - 360.0, - new LinearDoubleEasing(), - TimeSpan.FromSeconds(4), - double.PositiveInfinity); + degrees, + BindingPriority.Animation); return result; } diff --git a/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj b/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj index 2cb46de7b2..f7fee0a1d5 100644 --- a/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj +++ b/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj @@ -85,6 +85,10 @@ + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base