From 4c2f49daee435e610b0ad889b3e841c8e191b1eb Mon Sep 17 00:00:00 2001
From: Jerome Humbert <djeedai@gmail.com>
Date: Sat, 1 Oct 2022 10:55:58 +0100
Subject: [PATCH] Fix animator speed feature (#63)

Fix the animator speed applying, which got broken as part of the
refactor of #44.

Add a `speed()` getter to both `Animator<T>` and `AssetAnimator<T>`.

Add some simple test for speed, but this is not enough to make sure the
feature doesn't regress, so logged #62 to follow-up with a proper
regression test.

Fixes #61
---
 CHANGELOG.md  |  5 ++++
 src/lib.rs    | 81 +++++++++++++++++++++++++++++++++++++++++++++++++--
 src/plugin.rs | 20 +++++++++----
 3 files changed, 98 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 52c2c3f..54bb6c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Added `RepeatCount` and `RepeatStrategy` for more granular control over animation looping.
 - Added `with_repeat_count()` and `with_repeat_strategy()` builder methods to `Tween<T>`.
+- Added a `speed()` getter on `Animator<T>` and `AssetAnimator<T>`.
 
 ### Changed
 
@@ -22,6 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Removed `Tweenable::is_looping()`, which was not implemented for most tweenables.
 - Removed `TweeningType` in favor of `RepeatCount` and `RepeatStrategy`.
 
+### Fixed
+
+- Fixed the animator speed feature, which got broken in #44.
+
 ## [0.5.0] - 2022-08-04
 
 ### Added
diff --git a/src/lib.rs b/src/lib.rs
index 013ae84..211050a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -361,6 +361,15 @@ macro_rules! animator_impl {
             self.speed = speed;
         }
 
+        /// Get the animation speed.
+        ///
+        /// See [`set_speed()`] for a definition of what the animation speed is.
+        ///
+        /// [`set_speed()`]: Animator::speed
+        pub fn speed(&self) -> f32 {
+            self.speed
+        }
+
         /// Set the top-level tweenable item this animator controls.
         pub fn set_tweenable(&mut self, tween: impl Tweenable<T> + Send + Sync + 'static) {
             self.tweenable = Box::new(tween);
@@ -468,18 +477,23 @@ mod tests {
 
     use super::{lens::*, *};
 
+    /// Utility to compare floating-point values with a tolerance.
+    fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool {
+        (a - b).abs() < tol
+    }
+
     struct DummyLens {
         start: f32,
         end: f32,
     }
 
-    #[derive(Component)]
+    #[derive(Debug, Component)]
     struct DummyComponent {
         value: f32,
     }
 
     #[cfg(feature = "bevy_asset")]
-    #[derive(Reflect, TypeUuid)]
+    #[derive(Debug, Reflect, TypeUuid)]
     #[uuid = "a33abc11-264e-4bbb-82e8-b87226bb4383"]
     struct DummyAsset {
         value: f32,
@@ -574,6 +588,13 @@ mod tests {
             );
             let animator = Animator::new(tween).with_state(state);
             assert_eq!(animator.state, state);
+
+            // impl Debug
+            let debug_string = format!("{:?}", animator);
+            assert_eq!(
+                debug_string,
+                format!("Animator {{ state: {:?} }}", animator.state)
+            );
         }
     }
 
@@ -614,6 +635,30 @@ mod tests {
         assert!(animator.tweenable().progress().abs() <= 1e-5);
     }
 
+    #[test]
+    fn animator_speed() {
+        let tween = Tween::<DummyComponent>::new(
+            EaseFunction::QuadraticInOut,
+            Duration::from_secs(1),
+            DummyLens { start: 0., end: 1. },
+        );
+
+        let mut animator = Animator::new(tween);
+        assert!(abs_diff_eq(animator.speed(), 1., 1e-5)); // default speed
+
+        animator.set_speed(2.4);
+        assert!(abs_diff_eq(animator.speed(), 2.4, 1e-5));
+
+        let tween = Tween::<DummyComponent>::new(
+            EaseFunction::QuadraticInOut,
+            Duration::from_secs(1),
+            DummyLens { start: 0., end: 1. },
+        );
+
+        let animator = Animator::new(tween).with_speed(3.5);
+        assert!(abs_diff_eq(animator.speed(), 3.5, 1e-5));
+    }
+
     #[cfg(feature = "bevy_asset")]
     #[test]
     fn asset_animator_new() {
@@ -641,6 +686,13 @@ mod tests {
             let animator =
                 AssetAnimator::new(Handle::<DummyAsset>::default(), tween).with_state(state);
             assert_eq!(animator.state, state);
+
+            // impl Debug
+            let debug_string = format!("{:?}", animator);
+            assert_eq!(
+                debug_string,
+                format!("AssetAnimator {{ state: {:?} }}", animator.state)
+            );
         }
     }
 
@@ -681,4 +733,29 @@ mod tests {
         assert_eq!(animator.state, AnimatorState::Paused);
         assert!(animator.tweenable().progress().abs() <= 1e-5);
     }
+
+    #[cfg(feature = "bevy_asset")]
+    #[test]
+    fn asset_animator_speed() {
+        let tween = Tween::new(
+            EaseFunction::QuadraticInOut,
+            Duration::from_secs(1),
+            DummyLens { start: 0., end: 1. },
+        );
+
+        let mut animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween);
+        assert!(abs_diff_eq(animator.speed(), 1., 1e-5)); // default speed
+
+        animator.set_speed(2.4);
+        assert!(abs_diff_eq(animator.speed(), 2.4, 1e-5));
+
+        let tween = Tween::new(
+            EaseFunction::QuadraticInOut,
+            Duration::from_secs(1),
+            DummyLens { start: 0., end: 1. },
+        );
+
+        let animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween).with_speed(3.5);
+        assert!(abs_diff_eq(animator.speed(), 3.5, 1e-5));
+    }
 }
diff --git a/src/plugin.rs b/src/plugin.rs
index feb22f3..bdbf3dc 100644
--- a/src/plugin.rs
+++ b/src/plugin.rs
@@ -75,9 +75,13 @@ pub fn component_animator_system<T: Component>(
 ) {
     for (entity, ref mut target, ref mut animator) in query.iter_mut() {
         if animator.state != AnimatorState::Paused {
-            animator
-                .tweenable_mut()
-                .tick(time.delta(), target, entity, &mut event_writer);
+            let speed = animator.speed();
+            animator.tweenable_mut().tick(
+                time.delta().mul_f32(speed),
+                target,
+                entity,
+                &mut event_writer,
+            );
         }
     }
 }
@@ -97,10 +101,14 @@ pub fn asset_animator_system<T: Asset>(
 ) {
     for (entity, ref mut animator) in query.iter_mut() {
         if animator.state != AnimatorState::Paused {
+            let speed = animator.speed();
             if let Some(target) = assets.get_mut(&animator.handle()) {
-                animator
-                    .tweenable_mut()
-                    .tick(time.delta(), target, entity, &mut event_writer);
+                animator.tweenable_mut().tick(
+                    time.delta().mul_f32(speed),
+                    target,
+                    entity,
+                    &mut event_writer,
+                );
             }
         }
     }
-- 
GitLab