From fa782cf0252d6de6712c788798d7623d9fd6c06c Mon Sep 17 00:00:00 2001
From: Alex Saveau <saveau.alexandre@gmail.com>
Date: Tue, 31 May 2022 15:20:29 -0700
Subject: [PATCH] Slightly modernize codebase (#26)

---
 .gitignore                        |   3 +-
 README.md                         |   4 +-
 examples/colormaterial_color.rs   |   8 +-
 examples/menu.rs                  |  16 +-
 examples/sequence.rs              |  16 +-
 examples/sprite_color.rs          |  10 +-
 examples/text_color.rs            |  12 +-
 examples/transform_rotation.rs    |   6 +-
 examples/transform_translation.rs |   6 +-
 examples/ui_position.rs           |   6 +-
 src/lens.rs                       |  18 +-
 src/lib.rs                        | 410 ++++++++++++------------------
 src/tweenable.rs                  |  34 ++-
 13 files changed, 233 insertions(+), 316 deletions(-)

diff --git a/.gitignore b/.gitignore
index 8ec23a9..676e74b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@ target/
 Cargo.lock
 
 .vs*
-.DS_Store
\ No newline at end of file
+.DS_Store
+.idea
diff --git a/README.md b/README.md
index 626a337..e4797b4 100644
--- a/README.md
+++ b/README.md
@@ -74,9 +74,9 @@ commands
         sprite: Sprite {
             color: Color::RED,
             custom_size: Some(Vec2::new(size, size)),
-            ..Default::default()
+            ..default()
         },
-        ..Default::default()
+        ..default()
     })
     // Add an Animator component to control and execute the animation.
     .insert(Animator::new(tween));
diff --git a/examples/colormaterial_color.rs b/examples/colormaterial_color.rs
index 69cd4bd..ad6e950 100644
--- a/examples/colormaterial_color.rs
+++ b/examples/colormaterial_color.rs
@@ -5,21 +5,19 @@ use bevy::{
 use bevy_tweening::{lens::*, *};
 use std::time::Duration;
 
-fn main() -> Result<(), Box<dyn std::error::Error>> {
+fn main() {
     App::default()
         .insert_resource(WindowDescriptor {
             title: "ColorMaterialColorLens".to_string(),
             width: 1200.,
             height: 600.,
             present_mode: bevy::window::PresentMode::Fifo, // vsync
-            ..Default::default()
+            ..default()
         })
         .add_plugins(DefaultPlugins)
         .add_plugin(TweeningPlugin)
         .add_startup_system(setup)
         .run();
-
-    Ok(())
 }
 
 fn setup(
@@ -93,7 +91,7 @@ fn setup(
                 transform: Transform::from_translation(Vec3::new(x, y, 0.))
                     .with_scale(Vec3::splat(size)),
                 material: unique_material.clone(),
-                ..Default::default()
+                ..default()
             })
             .insert(AssetAnimator::new(unique_material.clone(), tween));
         y -= size * spacing;
diff --git a/examples/menu.rs b/examples/menu.rs
index 5dafd84..1990afa 100644
--- a/examples/menu.rs
+++ b/examples/menu.rs
@@ -3,22 +3,20 @@ use bevy_inspector_egui::WorldInspectorPlugin;
 use bevy_tweening::{lens::*, *};
 use std::time::Duration;
 
-fn main() -> Result<(), Box<dyn std::error::Error>> {
+fn main() {
     App::default()
         .insert_resource(WindowDescriptor {
             title: "Menu".to_string(),
             width: 800.,
             height: 400.,
             present_mode: bevy::window::PresentMode::Fifo, // vsync
-            ..Default::default()
+            ..default()
         })
         .add_plugins(DefaultPlugins)
         .add_plugin(TweeningPlugin)
         .add_plugin(WorldInspectorPlugin::new())
         .add_startup_system(setup)
         .run();
-
-    Ok(())
 }
 
 fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
@@ -38,10 +36,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
                 align_items: AlignItems::Center,
                 align_self: AlignSelf::Center,
                 justify_content: JustifyContent::Center,
-                ..Default::default()
+                ..default()
             },
             color: UiColor(Color::NONE),
-            ..Default::default()
+            ..default()
         })
         .insert(Name::new("menu"))
         .id();
@@ -73,11 +71,11 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
                     align_items: AlignItems::Center,
                     align_self: AlignSelf::Center,
                     justify_content: JustifyContent::Center,
-                    ..Default::default()
+                    ..default()
                 },
                 color: UiColor(Color::rgb_u8(162, 226, 95)),
                 transform: Transform::from_scale(Vec3::splat(0.01)),
-                ..Default::default()
+                ..default()
             })
             .insert(Name::new(format!("button:{}", text)))
             .insert(Parent(container))
@@ -96,7 +94,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
                             horizontal: HorizontalAlign::Center,
                         },
                     ),
-                    ..Default::default()
+                    ..default()
                 });
             });
     }
diff --git a/examples/sequence.rs b/examples/sequence.rs
index ebbb51f..d79a4bc 100644
--- a/examples/sequence.rs
+++ b/examples/sequence.rs
@@ -2,22 +2,20 @@ use bevy::prelude::*;
 use bevy_tweening::{lens::*, *};
 use std::time::Duration;
 
-fn main() -> Result<(), Box<dyn std::error::Error>> {
+fn main() {
     App::default()
         .insert_resource(WindowDescriptor {
             title: "Sequence".to_string(),
             width: 600.,
             height: 600.,
             present_mode: bevy::window::PresentMode::Fifo, // vsync
-            ..Default::default()
+            ..default()
         })
         .add_plugins(DefaultPlugins)
         .add_plugin(TweeningPlugin)
         .add_startup_system(setup)
         .add_system(update_text)
         .run();
-
-    Ok(())
 }
 
 #[derive(Component)]
@@ -69,7 +67,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
                 alignment: text_alignment,
             },
             transform: Transform::from_translation(Vec3::new(0., 40., 0.)),
-            ..Default::default()
+            ..default()
         })
         .insert(RedProgress);
 
@@ -90,7 +88,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
                 alignment: text_alignment,
             },
             transform: Transform::from_translation(Vec3::new(0., -40., 0.)),
-            ..Default::default()
+            ..default()
         })
         .insert(BlueProgress);
 
@@ -128,9 +126,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
             sprite: Sprite {
                 color: Color::RED,
                 custom_size: Some(Vec2::new(size, size)),
-                ..Default::default()
+                ..default()
             },
-            ..Default::default()
+            ..default()
         })
         .insert(RedSprite)
         .insert(Animator::new(seq));
@@ -179,7 +177,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
             sprite: Sprite {
                 color: Color::BLUE,
                 custom_size: Some(Vec2::new(size * 3., size)),
-                ..Default::default()
+                ..default()
             },
             ..Default::default()
         })
diff --git a/examples/sprite_color.rs b/examples/sprite_color.rs
index 44b8a82..3f1a94a 100644
--- a/examples/sprite_color.rs
+++ b/examples/sprite_color.rs
@@ -1,21 +1,19 @@
 use bevy::prelude::*;
 use bevy_tweening::{lens::*, *};
 
-fn main() -> Result<(), Box<dyn std::error::Error>> {
+fn main() {
     App::default()
         .insert_resource(WindowDescriptor {
             title: "SpriteColorLens".to_string(),
             width: 1200.,
             height: 600.,
             present_mode: bevy::window::PresentMode::Fifo, // vsync
-            ..Default::default()
+            ..default()
         })
         .add_plugins(DefaultPlugins)
         .add_plugin(TweeningPlugin)
         .add_startup_system(setup)
         .run();
-
-    Ok(())
 }
 
 fn setup(mut commands: Commands) {
@@ -77,9 +75,9 @@ fn setup(mut commands: Commands) {
                 sprite: Sprite {
                     color: Color::BLACK,
                     custom_size: Some(Vec2::new(size, size)),
-                    ..Default::default()
+                    ..default()
                 },
-                ..Default::default()
+                ..default()
             })
             .insert(Animator::new(tween));
 
diff --git a/examples/text_color.rs b/examples/text_color.rs
index 29a9dae..5232330 100644
--- a/examples/text_color.rs
+++ b/examples/text_color.rs
@@ -4,21 +4,19 @@ use bevy_tweening::{lens::*, *};
 const WIDTH: f32 = 1200.;
 const HEIGHT: f32 = 600.;
 
-fn main() -> Result<(), Box<dyn std::error::Error>> {
+fn main() {
     App::default()
         .insert_resource(WindowDescriptor {
             title: "TextColorLens".to_string(),
             width: WIDTH,
             height: HEIGHT,
             present_mode: bevy::window::PresentMode::Fifo, // vsync
-            ..Default::default()
+            ..default()
         })
         .add_plugins(DefaultPlugins)
         .add_plugin(TweeningPlugin)
         .add_startup_system(setup)
         .run();
-
-    Ok(())
 }
 
 fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
@@ -94,7 +92,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
                     align_items: AlignItems::Center,
                     align_self: AlignSelf::Center,
                     justify_content: JustifyContent::Center,
-                    ..Default::default()
+                    ..default()
                 },
                 text: Text::with_section(
                     *ease_name,
@@ -104,9 +102,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
                         color: Color::WHITE,
                     },
                     // you can still use Default
-                    Default::default(),
+                    default(),
                 ),
-                ..Default::default()
+                ..default()
             })
             .insert(Animator::new(tween));
 
diff --git a/examples/transform_rotation.rs b/examples/transform_rotation.rs
index cd100ff..a259ff3 100644
--- a/examples/transform_rotation.rs
+++ b/examples/transform_rotation.rs
@@ -10,7 +10,7 @@ fn main() {
             width: 1400.,
             height: 600.,
             present_mode: bevy::window::PresentMode::Fifo, // vsync
-            ..Default::default()
+            ..default()
         })
         .add_plugins(DefaultPlugins)
         .add_plugin(TweeningPlugin)
@@ -96,9 +96,9 @@ fn setup(mut commands: Commands) {
                         sprite: Sprite {
                             color: Color::RED,
                             custom_size: Some(Vec2::new(size, size * 0.5)),
-                            ..Default::default()
+                            ..default()
                         },
-                        ..Default::default()
+                        ..default()
                     })
                     .insert(Animator::new(tween));
             });
diff --git a/examples/transform_translation.rs b/examples/transform_translation.rs
index a1e996b..75cc678 100644
--- a/examples/transform_translation.rs
+++ b/examples/transform_translation.rs
@@ -10,7 +10,7 @@ fn main() {
             width: 1400.,
             height: 600.,
             present_mode: bevy::window::PresentMode::Fifo, // vsync
-            ..Default::default()
+            ..default()
         })
         .add_plugins(DefaultPlugins)
         .add_plugin(TweeningPlugin)
@@ -89,9 +89,9 @@ fn setup(mut commands: Commands) {
                 sprite: Sprite {
                     color: Color::RED,
                     custom_size: Some(Vec2::new(size, size)),
-                    ..Default::default()
+                    ..default()
                 },
-                ..Default::default()
+                ..default()
             })
             .insert(Animator::new(tween));
 
diff --git a/examples/ui_position.rs b/examples/ui_position.rs
index 1778b58..24ff0e3 100644
--- a/examples/ui_position.rs
+++ b/examples/ui_position.rs
@@ -10,7 +10,7 @@ fn main() {
             width: 1400.,
             height: 600.,
             present_mode: bevy::window::PresentMode::Fifo, // vsync
-            ..Default::default()
+            ..default()
         })
         .add_plugins(DefaultPlugins)
         .add_plugin(TweeningPlugin)
@@ -109,10 +109,10 @@ fn setup(mut commands: Commands) {
                     align_items: AlignItems::Center,
                     align_self: AlignSelf::Center,
                     justify_content: JustifyContent::Center,
-                    ..Default::default()
+                    ..default()
                 },
                 color: UiColor(Color::RED),
-                ..Default::default()
+                ..default()
             })
             .insert(Animator::new(tween));
 
diff --git a/src/lens.rs b/src/lens.rs
index 2886589..a8eb345 100644
--- a/src/lens.rs
+++ b/src/lens.rs
@@ -167,7 +167,7 @@ pub struct TransformRotateXLens {
 
 impl Lens<Transform> for TransformRotateXLens {
     fn lerp(&mut self, target: &mut Transform, ratio: f32) {
-        let angle = self.start + (self.end - self.start) * ratio;
+        let angle = (self.end - self.start).mul_add(ratio, self.start);
         target.rotation = Quat::from_rotation_x(angle);
     }
 }
@@ -193,7 +193,7 @@ pub struct TransformRotateYLens {
 
 impl Lens<Transform> for TransformRotateYLens {
     fn lerp(&mut self, target: &mut Transform, ratio: f32) {
-        let angle = self.start + (self.end - self.start) * ratio;
+        let angle = (self.end - self.start).mul_add(ratio, self.start);
         target.rotation = Quat::from_rotation_y(angle);
     }
 }
@@ -219,7 +219,7 @@ pub struct TransformRotateZLens {
 
 impl Lens<Transform> for TransformRotateZLens {
     fn lerp(&mut self, target: &mut Transform, ratio: f32) {
-        let angle = self.start + (self.end - self.start) * ratio;
+        let angle = (self.end - self.start).mul_add(ratio, self.start);
         target.rotation = Quat::from_rotation_z(angle);
     }
 }
@@ -251,7 +251,7 @@ pub struct TransformRotateAxisLens {
 
 impl Lens<Transform> for TransformRotateAxisLens {
     fn lerp(&mut self, target: &mut Transform, ratio: f32) {
-        let angle = self.start + (self.end - self.start) * ratio;
+        let angle = (self.end - self.start).mul_add(ratio, self.start);
         target.rotation = Quat::from_axis_angle(self.axis, angle);
     }
 }
@@ -291,8 +291,10 @@ pub struct UiPositionLens {
 #[cfg(feature = "bevy_ui")]
 fn lerp_val(start: &Val, end: &Val, ratio: f32) -> Val {
     match (start, end) {
-        (Val::Percent(start), Val::Percent(end)) => Val::Percent(start + (end - start) * ratio),
-        (Val::Px(start), Val::Px(end)) => Val::Px(start + (end - start) * ratio),
+        (Val::Percent(start), Val::Percent(end)) => {
+            Val::Percent((end - start).mul_add(ratio, *start))
+        }
+        (Val::Px(start), Val::Px(end)) => Val::Px((end - start).mul_add(ratio, *start)),
         _ => *start,
     }
 }
@@ -370,7 +372,7 @@ mod tests {
             end: Color::BLUE,
             section: 0,
         };
-        let mut text = Text::with_section("", Default::default(), Default::default());
+        let mut text = Text::with_section("", default(), default());
 
         lens.lerp(&mut text, 0.);
         assert_eq!(text.sections[0].style.color, Color::RED);
@@ -623,7 +625,7 @@ mod tests {
         };
         let mut sprite = Sprite {
             color: Color::WHITE,
-            ..Default::default()
+            ..default()
         };
 
         lens.lerp(&mut sprite, 0.);
diff --git a/src/lib.rs b/src/lib.rs
index 51f0f86..79b1024 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -178,7 +178,7 @@ pub enum TweeningType {
 
 impl Default for TweeningType {
     fn default() -> Self {
-        TweeningType::Once
+        Self::Once
     }
 }
 
@@ -193,17 +193,17 @@ pub enum AnimatorState {
 
 impl Default for AnimatorState {
     fn default() -> Self {
-        AnimatorState::Playing
+        Self::Playing
     }
 }
 
 impl std::ops::Not for AnimatorState {
-    type Output = AnimatorState;
+    type Output = Self;
 
     fn not(self) -> Self::Output {
         match self {
-            AnimatorState::Paused => AnimatorState::Playing,
-            AnimatorState::Playing => AnimatorState::Paused,
+            Self::Paused => Self::Playing,
+            Self::Playing => Self::Paused,
         }
     }
 }
@@ -223,31 +223,32 @@ pub enum EaseMethod {
 }
 
 impl EaseMethod {
+    #[must_use]
     fn sample(self, x: f32) -> f32 {
         match self {
-            EaseMethod::EaseFunction(function) => x.calc(function),
-            EaseMethod::Linear => x,
-            EaseMethod::Discrete(limit) => {
+            Self::EaseFunction(function) => x.calc(function),
+            Self::Linear => x,
+            Self::Discrete(limit) => {
                 if x > limit {
                     1.
                 } else {
                     0.
                 }
             }
-            EaseMethod::CustomFunction(function) => function(x),
+            Self::CustomFunction(function) => function(x),
         }
     }
 }
 
 impl Default for EaseMethod {
     fn default() -> Self {
-        EaseMethod::Linear
+        Self::Linear
     }
 }
 
 impl From<EaseFunction> for EaseMethod {
     fn from(ease_function: EaseFunction) -> Self {
-        EaseMethod::EaseFunction(ease_function)
+        Self::EaseFunction(ease_function)
     }
 }
 
@@ -274,33 +275,153 @@ pub enum TweeningDirection {
 
 impl TweeningDirection {
     /// Is the direction equal to [`TweeningDirection::Forward`]?
+    #[must_use]
     pub fn is_forward(&self) -> bool {
-        *self == TweeningDirection::Forward
+        *self == Self::Forward
     }
 
     /// Is the direction equal to [`TweeningDirection::Backward`]?
+    #[must_use]
     pub fn is_backward(&self) -> bool {
-        *self == TweeningDirection::Backward
+        *self == Self::Backward
     }
 }
 
 impl Default for TweeningDirection {
     fn default() -> Self {
-        TweeningDirection::Forward
+        Self::Forward
     }
 }
 
 impl std::ops::Not for TweeningDirection {
-    type Output = TweeningDirection;
+    type Output = Self;
 
     fn not(self) -> Self::Output {
         match self {
-            TweeningDirection::Forward => TweeningDirection::Backward,
-            TweeningDirection::Backward => TweeningDirection::Forward,
+            Self::Forward => Self::Backward,
+            Self::Backward => Self::Forward,
         }
     }
 }
 
+macro_rules! animator_impl {
+    () => {
+        /// Set the initial playback state of the animator.
+        #[must_use]
+        pub fn with_state(mut self, state: AnimatorState) -> Self {
+            self.state = state;
+            self
+        }
+
+        /// Set the initial speed of the animator. See [`Animator::set_speed`] for details.
+        #[must_use]
+        pub fn with_speed(mut self, speed: f32) -> Self {
+            self.speed = speed;
+            self
+        }
+
+        /// Set the animation speed. Defaults to 1.
+        ///
+        /// A speed of 2 means the animation will run twice as fast while a speed of 0.1 will result in
+        /// a 10x slowed animation.
+        pub fn set_speed(&mut self, speed: f32) {
+            self.speed = 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 = Some(Box::new(tween));
+        }
+
+        /// Get the top-level tweenable this animator is currently controlling.
+        #[must_use]
+        pub fn tweenable(&self) -> Option<&(dyn Tweenable<T> + Send + Sync + 'static)> {
+            if let Some(tweenable) = &self.tweenable {
+                Some(tweenable.as_ref())
+            } else {
+                None
+            }
+        }
+
+        /// Get the top-level mutable tweenable this animator is currently controlling.
+        #[must_use]
+        pub fn tweenable_mut(&mut self) -> Option<&mut (dyn Tweenable<T> + Send + Sync + 'static)> {
+            if let Some(tweenable) = &mut self.tweenable {
+                Some(tweenable.as_mut())
+            } else {
+                None
+            }
+        }
+
+        /// Set the current animation playback progress.
+        ///
+        /// See [`progress()`] for details on the meaning.
+        ///
+        /// [`progress()`]: Animator::progress
+        pub fn set_progress(&mut self, progress: f32) {
+            if let Some(tweenable) = &mut self.tweenable {
+                tweenable.set_progress(progress)
+            }
+        }
+
+        /// Get the current progress in \[0:1\] (non-looping) or \[0:1\[ (looping) of the animation.
+        ///
+        /// For looping animations, this reports the progress of the current iteration, in the current direction:
+        /// - [`TweeningType::Loop`] is 0 at start and 1 at end. The exact value 1.0 is never reached,
+        ///   since the tweenable loops over to 0.0 immediately.
+        /// - [`TweeningType::PingPong`] is 0 at the source endpoint and 1 and the destination one,
+        ///   which are respectively the start/end for [`TweeningDirection::Forward`], or the end/start
+        ///   for [`TweeningDirection::Backward`]. The exact value 1.0 is never reached, since the tweenable
+        ///   loops over to 0.0 immediately when it changes direction at either endpoint.
+        ///
+        /// For sequences, the progress is measured over the entire sequence, from 0 at the start of the first
+        /// child tweenable to 1 at the end of the last one.
+        ///
+        /// For tracks (parallel execution), the progress is measured like a sequence over the longest "path" of
+        /// child tweenables. In other words, this is the current elapsed time over the total tweenable duration.
+        #[must_use]
+        pub fn progress(&self) -> f32 {
+            if let Some(tweenable) = &self.tweenable {
+                tweenable.progress()
+            } else {
+                0.
+            }
+        }
+
+        /// Ticks the tween, if present. See [`Tweenable::tick`] for details.
+        pub fn tick(
+            &mut self,
+            delta: Duration,
+            target: &mut T,
+            entity: Entity,
+            event_writer: &mut EventWriter<TweenCompleted>,
+        ) -> Option<TweenState> {
+            if let Some(tweenable) = &mut self.tweenable {
+                Some(tweenable.tick(delta.mul_f32(self.speed), target, entity, event_writer))
+            } else {
+                None
+            }
+        }
+
+        /// Stop animation playback and rewind the animation.
+        ///
+        /// This changes the animator state to [`AnimatorState::Paused`] and rewind its tweenable.
+        pub fn stop(&mut self) {
+            self.state = AnimatorState::Paused;
+            self.rewind();
+        }
+
+        /// Rewind animation playback to its initial state.
+        ///
+        /// This does not change the playback state (playing/paused).
+        pub fn rewind(&mut self) {
+            if let Some(tweenable) = &mut self.tweenable {
+                tweenable.rewind();
+            }
+        }
+    };
+}
+
 /// Component to control the animation of another component.
 #[derive(Component)]
 pub struct Animator<T: Component> {
@@ -320,8 +441,8 @@ impl<T: Component + std::fmt::Debug> std::fmt::Debug for Animator<T> {
 
 impl<T: Component> Default for Animator<T> {
     fn default() -> Self {
-        Animator {
-            state: Default::default(),
+        Self {
+            state: default(),
             tweenable: None,
             speed: 1.,
         }
@@ -330,121 +451,15 @@ impl<T: Component> Default for Animator<T> {
 
 impl<T: Component> Animator<T> {
     /// Create a new animator component from a single tweenable.
+    #[must_use]
     pub fn new(tween: impl Tweenable<T> + Send + Sync + 'static) -> Self {
-        Animator {
+        Self {
             tweenable: Some(Box::new(tween)),
-            ..Default::default()
+            ..default()
         }
     }
 
-    /// Set the initial playback state of the animator.
-    pub fn with_state(mut self, state: AnimatorState) -> Self {
-        self.state = state;
-        self
-    }
-
-    /// Set the initial speed of the animator. See [`Animator::set_speed`] for details.
-    pub fn with_speed(mut self, speed: f32) -> Self {
-        self.speed = speed;
-        self
-    }
-
-    /// Set the animation speed. Defaults to 1.
-    ///
-    /// A speed of 2 means the animation will run twice as fast while a speed of 0.1 will result in
-    /// a 10x slowed animation.
-    pub fn set_speed(&mut self, speed: f32) {
-        self.speed = 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 = Some(Box::new(tween));
-    }
-
-    /// Get the top-level tweenable this animator is currently controlling.
-    pub fn tweenable(&self) -> Option<&(dyn Tweenable<T> + Send + Sync + 'static)> {
-        if let Some(tweenable) = &self.tweenable {
-            Some(tweenable.as_ref())
-        } else {
-            None
-        }
-    }
-
-    /// Get the top-level mutable tweenable this animator is currently controlling.
-    pub fn tweenable_mut(&mut self) -> Option<&mut (dyn Tweenable<T> + Send + Sync + 'static)> {
-        if let Some(tweenable) = &mut self.tweenable {
-            Some(tweenable.as_mut())
-        } else {
-            None
-        }
-    }
-
-    /// Set the current animation playback progress.
-    ///
-    /// See [`progress()`] for details on the meaning.
-    ///
-    /// [`progress()`]: Animator::progress
-    pub fn set_progress(&mut self, progress: f32) {
-        if let Some(tweenable) = &mut self.tweenable {
-            tweenable.set_progress(progress)
-        }
-    }
-
-    /// Get the current progress in \[0:1\] (non-looping) or \[0:1\[ (looping) of the animation.
-    ///
-    /// For looping animations, this reports the progress of the current iteration, in the current direction:
-    /// - [`TweeningType::Loop`] is 0 at start and 1 at end. The exact value 1.0 is never reached,
-    ///   since the tweenable loops over to 0.0 immediately.
-    /// - [`TweeningType::PingPong`] is 0 at the source endpoint and 1 and the destination one,
-    ///   which are respectively the start/end for [`TweeningDirection::Forward`], or the end/start
-    ///   for [`TweeningDirection::Backward`]. The exact value 1.0 is never reached, since the tweenable
-    ///   loops over to 0.0 immediately when it changes direction at either endpoint.
-    ///
-    /// For sequences, the progress is measured over the entire sequence, from 0 at the start of the first
-    /// child tweenable to 1 at the end of the last one.
-    ///
-    /// For tracks (parallel execution), the progress is measured like a sequence over the longest "path" of
-    /// child tweenables. In other words, this is the current elapsed time over the total tweenable duration.
-    pub fn progress(&self) -> f32 {
-        if let Some(tweenable) = &self.tweenable {
-            tweenable.progress()
-        } else {
-            0.
-        }
-    }
-
-    /// Ticks the tween, if present. See [`Tweenable::tick`] for details.
-    pub fn tick(
-        &mut self,
-        delta: Duration,
-        target: &mut T,
-        entity: Entity,
-        event_writer: &mut EventWriter<TweenCompleted>,
-    ) -> Option<TweenState> {
-        if let Some(tweenable) = &mut self.tweenable {
-            Some(tweenable.tick(delta.mul_f32(self.speed), target, entity, event_writer))
-        } else {
-            None
-        }
-    }
-
-    /// Stop animation playback and rewind the animation.
-    ///
-    /// This changes the animator state to [`AnimatorState::Paused`] and rewind its tweenable.
-    pub fn stop(&mut self) {
-        self.state = AnimatorState::Paused;
-        self.rewind();
-    }
-
-    /// Rewind animation playback to its initial state.
-    ///
-    /// This does not change the playback state (playing/paused).
-    pub fn rewind(&mut self) {
-        if let Some(tweenable) = &mut self.tweenable {
-            tweenable.rewind();
-        }
-    }
+    animator_impl!();
 }
 
 /// Component to control the animation of an asset.
@@ -467,10 +482,10 @@ impl<T: Asset + std::fmt::Debug> std::fmt::Debug for AssetAnimator<T> {
 
 impl<T: Asset> Default for AssetAnimator<T> {
     fn default() -> Self {
-        AssetAnimator {
-            state: Default::default(),
+        Self {
+            state: default(),
             tweenable: None,
-            handle: Default::default(),
+            handle: default(),
             speed: 1.,
         }
     }
@@ -478,123 +493,18 @@ impl<T: Asset> Default for AssetAnimator<T> {
 
 impl<T: Asset> AssetAnimator<T> {
     /// Create a new asset animator component from a single tweenable.
+    #[must_use]
     pub fn new(handle: Handle<T>, tween: impl Tweenable<T> + Send + Sync + 'static) -> Self {
-        AssetAnimator {
+        Self {
             tweenable: Some(Box::new(tween)),
             handle,
-            ..Default::default()
+            ..default()
         }
     }
 
-    /// Set the initial playback state of the animator.
-    pub fn with_state(mut self, state: AnimatorState) -> Self {
-        self.state = state;
-        self
-    }
-
-    /// Set the initial speed of the animator. See [`Animator::set_speed`] for details.
-    pub fn with_speed(mut self, speed: f32) -> Self {
-        self.speed = speed;
-        self
-    }
-
-    /// Set the animation speed. Defaults to 1.
-    ///
-    /// A speed of 2 means the animation will run twice as fast while a speed of 0.1 will result in
-    /// a 10x slowed animation.
-    pub fn set_speed(&mut self, speed: f32) {
-        self.speed = 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 = Some(Box::new(tween));
-    }
-
-    /// Get the top-level tweenable this animator is currently controlling.
-    pub fn tweenable(&self) -> Option<&(dyn Tweenable<T> + Send + Sync + 'static)> {
-        if let Some(tweenable) = &self.tweenable {
-            Some(tweenable.as_ref())
-        } else {
-            None
-        }
-    }
-
-    /// Get the top-level mutable tweenable this animator is currently controlling.
-    pub fn tweenable_mut(&mut self) -> Option<&mut (dyn Tweenable<T> + Send + Sync + 'static)> {
-        if let Some(tweenable) = &mut self.tweenable {
-            Some(tweenable.as_mut())
-        } else {
-            None
-        }
-    }
-
-    /// Set the current animation playback progress.
-    ///
-    /// See [`progress()`] for details on the meaning.
-    ///
-    /// [`progress()`]: Animator::progress
-    pub fn set_progress(&mut self, progress: f32) {
-        if let Some(tweenable) = &mut self.tweenable {
-            tweenable.set_progress(progress)
-        }
-    }
-
-    /// Get the current progress in \[0:1\] (non-looping) or \[0:1\[ (looping) of the animation.
-    ///
-    /// For looping animations, this reports the progress of the current iteration, in the current direction:
-    /// - [`TweeningType::Loop`] is 0 at start and 1 at end. The exact value 1.0 is never reached,
-    ///   since the tweenable loops over to 0.0 immediately.
-    /// - [`TweeningType::PingPong`] is 0 at the source endpoint and 1 and the destination one,
-    ///   which are respectively the start/end for [`TweeningDirection::Forward`], or the end/start
-    ///   for [`TweeningDirection::Backward`]. The exact value 1.0 is never reached, since the tweenable
-    ///   loops over to 0.0 immediately when it changes direction at either endpoint.
-    ///
-    /// For sequences, the progress is measured over the entire sequence, from 0 at the start of the first
-    /// child tweenable to 1 at the end of the last one.
-    ///
-    /// For tracks (parallel execution), the progress is measured like a sequence over the longest "path" of
-    /// child tweenables. In other words, this is the current elapsed time over the total tweenable duration.
-    pub fn progress(&self) -> f32 {
-        if let Some(tweenable) = &self.tweenable {
-            tweenable.progress()
-        } else {
-            0.
-        }
-    }
-
-    /// Ticks the tween, if present. See [`Tweenable::tick`] for details.
-    pub fn tick(
-        &mut self,
-        delta: Duration,
-        target: &mut T,
-        entity: Entity,
-        event_writer: &mut EventWriter<TweenCompleted>,
-    ) -> Option<TweenState> {
-        if let Some(tweenable) = &mut self.tweenable {
-            Some(tweenable.tick(delta.mul_f32(self.speed), target, entity, event_writer))
-        } else {
-            None
-        }
-    }
-
-    /// Stop animation playback and rewind the animation.
-    ///
-    /// This changes the animator state to [`AnimatorState::Paused`] and rewind its tweenable.
-    pub fn stop(&mut self) {
-        self.state = AnimatorState::Paused;
-        self.rewind();
-    }
-
-    /// Rewind animation playback to its initial state.
-    ///
-    /// This does not change the playback state (playing/paused).
-    pub fn rewind(&mut self) {
-        if let Some(tweenable) = &mut self.tweenable {
-            tweenable.rewind();
-        }
-    }
+    animator_impl!();
 
+    #[must_use]
     fn handle(&self) -> Handle<T> {
         self.handle.clone()
     }
@@ -687,7 +597,7 @@ mod tests {
         let tween = Tween::new(
             EaseFunction::QuadraticInOut,
             TweeningType::PingPong,
-            std::time::Duration::from_secs(1),
+            Duration::from_secs(1),
             DummyLens { start: 0., end: 1. },
         );
         let animator = Animator::<DummyComponent>::new(tween);
@@ -703,7 +613,7 @@ mod tests {
             let tween = Tween::<DummyComponent>::new(
                 EaseFunction::QuadraticInOut,
                 TweeningType::PingPong,
-                std::time::Duration::from_secs(1),
+                Duration::from_secs(1),
                 DummyLens { start: 0., end: 1. },
             );
             let animator = Animator::new(tween).with_state(state);
@@ -721,7 +631,7 @@ mod tests {
         let tween = Tween::<DummyComponent>::new(
             EaseFunction::QuadraticInOut,
             TweeningType::PingPong,
-            std::time::Duration::from_secs(1),
+            Duration::from_secs(1),
             DummyLens { start: 0., end: 1. },
         );
         animator.set_tweenable(tween);
@@ -735,7 +645,7 @@ mod tests {
         let tween = Tween::<DummyComponent>::new(
             EaseFunction::QuadraticInOut,
             TweeningType::PingPong,
-            std::time::Duration::from_secs(1),
+            Duration::from_secs(1),
             DummyLens { start: 0., end: 1. },
         );
         let mut animator = Animator::new(tween);
@@ -774,7 +684,7 @@ mod tests {
         let tween = Tween::<DummyAsset>::new(
             EaseFunction::QuadraticInOut,
             TweeningType::PingPong,
-            std::time::Duration::from_secs(1),
+            Duration::from_secs(1),
             DummyLens { start: 0., end: 1. },
         );
         let animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween);
@@ -790,7 +700,7 @@ mod tests {
             let tween = Tween::<DummyAsset>::new(
                 EaseFunction::QuadraticInOut,
                 TweeningType::PingPong,
-                std::time::Duration::from_secs(1),
+                Duration::from_secs(1),
                 DummyLens { start: 0., end: 1. },
             );
             let animator =
@@ -810,7 +720,7 @@ mod tests {
         let tween = Tween::new(
             EaseFunction::QuadraticInOut,
             TweeningType::PingPong,
-            std::time::Duration::from_secs(1),
+            Duration::from_secs(1),
             DummyLens { start: 0., end: 1. },
         );
         animator.set_tweenable(tween);
@@ -825,7 +735,7 @@ mod tests {
         let tween = Tween::new(
             EaseFunction::QuadraticInOut,
             TweeningType::PingPong,
-            std::time::Duration::from_secs(1),
+            Duration::from_secs(1),
             DummyLens { start: 0., end: 1. },
         );
         let mut animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween);
diff --git a/src/tweenable.rs b/src/tweenable.rs
index 5004487..03a868e 100644
--- a/src/tweenable.rs
+++ b/src/tweenable.rs
@@ -51,7 +51,7 @@ struct AnimClock {
 
 impl AnimClock {
     fn new(duration: Duration, is_looping: bool) -> Self {
-        AnimClock {
+        Self {
             elapsed: Duration::ZERO,
             duration,
             is_looping,
@@ -256,6 +256,7 @@ impl<T: 'static> Tween<T> {
     /// );
     /// let seq = tween1.then(tween2);
     /// ```
+    #[must_use]
     pub fn then(self, tween: impl Tweenable<T> + Send + Sync + 'static) -> Sequence<T> {
         Sequence::with_capacity(2).then(self).then(tween)
     }
@@ -279,6 +280,7 @@ impl<T> Tween<T> {
     ///     },
     /// );
     /// ```
+    #[must_use]
     pub fn new<L>(
         ease_function: impl Into<EaseMethod>,
         tweening_type: TweeningType,
@@ -288,7 +290,7 @@ impl<T> Tween<T> {
     where
         L: Lens<T> + Send + Sync + 'static,
     {
-        Tween {
+        Self {
             ease_function: ease_function.into(),
             clock: AnimClock::new(duration, tweening_type != TweeningType::Once),
             times_completed: 0,
@@ -331,6 +333,7 @@ impl<T> Tween<T> {
     /// ```
     ///
     /// [`set_completed()`]: Tween::set_completed
+    #[must_use]
     pub fn with_completed_event(mut self, enabled: bool, user_data: u64) -> Self {
         self.event_data = if enabled { Some(user_data) } else { None };
         self
@@ -354,6 +357,7 @@ impl<T> Tween<T> {
     /// Set the playback direction of the tween.
     ///
     /// See [`Tween::set_direction()`].
+    #[must_use]
     pub fn with_direction(mut self, direction: TweeningDirection) -> Self {
         self.direction = direction;
         self
@@ -362,6 +366,7 @@ impl<T> Tween<T> {
     /// The current animation direction.
     ///
     /// See [`TweeningDirection`] for details.
+    #[must_use]
     pub fn direction(&self) -> TweeningDirection {
         self.direction
     }
@@ -374,7 +379,7 @@ impl<T> Tween<T> {
     /// Only non-looping tweenables can complete.
     pub fn set_completed<C>(&mut self, callback: C)
     where
-        C: Fn(Entity, &Tween<T>) + Send + Sync + 'static,
+        C: Fn(Entity, &Self) + Send + Sync + 'static,
     {
         self.on_completed = Some(Box::new(callback));
     }
@@ -486,14 +491,15 @@ impl<T> Sequence<T> {
     /// Create a new sequence of tweens.
     ///
     /// This method panics if the input collection is empty.
+    #[must_use]
     pub fn new(items: impl IntoIterator<Item = impl IntoBoxDynTweenable<T>>) -> Self {
         let tweens: Vec<_> = items
             .into_iter()
             .map(IntoBoxDynTweenable::into_box_dyn)
             .collect();
         assert!(!tweens.is_empty());
-        let duration = tweens.iter().map(|t| t.duration()).sum();
-        Sequence {
+        let duration = tweens.iter().map(Tweenable::duration).sum();
+        Self {
             tweens,
             index: 0,
             duration,
@@ -503,9 +509,10 @@ impl<T> Sequence<T> {
     }
 
     /// Create a new sequence containing a single tween.
+    #[must_use]
     pub fn from_single(tween: impl Tweenable<T> + Send + Sync + 'static) -> Self {
         let duration = tween.duration();
-        Sequence {
+        Self {
             tweens: vec![Box::new(tween)],
             index: 0,
             duration,
@@ -515,8 +522,9 @@ impl<T> Sequence<T> {
     }
 
     /// Create a new sequence with the specified capacity.
+    #[must_use]
     pub fn with_capacity(capacity: usize) -> Self {
-        Sequence {
+        Self {
             tweens: Vec::with_capacity(capacity),
             index: 0,
             duration: Duration::ZERO,
@@ -526,6 +534,7 @@ impl<T> Sequence<T> {
     }
 
     /// Append a [`Tweenable`] to this sequence.
+    #[must_use]
     pub fn then(mut self, tween: impl Tweenable<T> + Send + Sync + 'static) -> Self {
         self.duration += tween.duration();
         self.tweens.push(Box::new(tween));
@@ -533,11 +542,13 @@ impl<T> Sequence<T> {
     }
 
     /// Index of the current active tween in the sequence.
+    #[must_use]
     pub fn index(&self) -> usize {
         self.index.min(self.tweens.len() - 1)
     }
 
     /// Get the current active tween in the sequence.
+    #[must_use]
     pub fn current(&self) -> &dyn Tweenable<T> {
         self.tweens[self.index()].as_ref()
     }
@@ -634,13 +645,14 @@ pub struct Tracks<T> {
 
 impl<T> Tracks<T> {
     /// Create a new [`Tracks`] from an iterator over a collection of [`Tweenable`].
+    #[must_use]
     pub fn new(items: impl IntoIterator<Item = impl IntoBoxDynTweenable<T>>) -> Self {
         let tracks: Vec<_> = items
             .into_iter()
             .map(IntoBoxDynTweenable::into_box_dyn)
             .collect();
-        let duration = tracks.iter().map(|t| t.duration()).max().unwrap();
-        Tracks {
+        let duration = tracks.iter().map(Tweenable::duration).max().unwrap();
+        Self {
             tracks,
             duration,
             time: Duration::ZERO,
@@ -718,13 +730,15 @@ pub struct Delay {
 
 impl Delay {
     /// Create a new [`Delay`] with a given duration.
+    #[must_use]
     pub fn new(duration: Duration) -> Self {
-        Delay {
+        Self {
             timer: Timer::new(duration, false),
         }
     }
 
     /// Chain another [`Tweenable`] after this tween, making a sequence with the two.
+    #[must_use]
     pub fn then<T>(self, tween: impl Tweenable<T> + Send + Sync + 'static) -> Sequence<T> {
         Sequence::with_capacity(2).then(self).then(tween)
     }
-- 
GitLab