From fcc8910568a00b892e66586a7359ea3552c840a2 Mon Sep 17 00:00:00 2001 From: Jerome Humbert <djeedai@gmail.com> Date: Mon, 14 Nov 2022 20:55:32 +0000 Subject: [PATCH] Added total duration and shared implementations (#74) Exposed the total animation duration via the `TotalDuration` enum and the `Tweenable<T>::total_duration() -> TotalDuration` API. Added a default implementation for some methods of `Tweenable<T>`, namely `set_progress()`, `progress()`, and `times_completed()`. These shared implementations are used by all built-in tweenables. Issue: #31 --- CHANGELOG.md | 2 + src/lib.rs | 14 +-- src/tweenable.rs | 230 ++++++++++++++++++----------------------------- 3 files changed, 97 insertions(+), 149 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 499fec6..8d6dd16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `Targetable`, `ComponentTarget`, and `AssetTarget`, which should be considered private even though they appear in the public API. They are a workaround for Bevy 0.8 and will likely be removed in the future once the related Bevy limitation is lifted. - Added the missing `Tween::with_completed()` to raise a callback. - Added completion event and callback support to `Delay<T>`, similar to what existed for `Tween<T>`. +- Added `TotalDuration` and a new `Tweenable<T>::total_duration()` method to retrieve the total duration of the animation including looping. ### Changed @@ -26,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed the signature of the `component_animator_system()` and `asset_animator_system()` public functions to directly consume a `ResMut<Events<TweenCompleted>>` instead of an `EventWriter<TweenCompleted>`, to work around some internal limitations. - Changed `Delay` into `Delay<T>`, taking the animation target type like other tweenables, to be able to emit events and raise callbacks. - Changed `CompletedCallback<T>` to take the tweenable type itself, instead of the target type. Users upgrading should replace `CompletedCallback<T>` with `CompletedCallback<Tween<T>>`. +- The `set_progress()`, `progress()`, and `times_completed()` method of `Tweenable<T>` now have a default implementation, and all built-in tweenables use that implementation. ### Removed diff --git a/src/lib.rs b/src/lib.rs index 7d5fc03..f139914 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,8 +164,8 @@ pub use lens::Lens; pub use plugin::asset_animator_system; pub use plugin::{component_animator_system, AnimationSystem, TweeningPlugin}; pub use tweenable::{ - BoxedTweenable, Delay, Sequence, Targetable, Tracks, Tween, TweenCompleted, TweenState, - Tweenable, + BoxedTweenable, Delay, Sequence, Targetable, TotalDuration, Tracks, Tween, TweenCompleted, + TweenState, Tweenable, }; pub mod lens; @@ -375,19 +375,19 @@ macro_rules! animator_impl { } /// Set the top-level tweenable item this animator controls. - pub fn set_tweenable(&mut self, tween: impl Tweenable<T> + Send + Sync + 'static) { + pub fn set_tweenable(&mut self, tween: impl Tweenable<T> + 'static) { self.tweenable = Box::new(tween); } /// Get the top-level tweenable this animator is currently controlling. #[must_use] - pub fn tweenable(&self) -> &(dyn Tweenable<T> + Send + Sync + 'static) { + pub fn tweenable(&self) -> &dyn Tweenable<T> { self.tweenable.as_ref() } /// Get the top-level mutable tweenable this animator is currently controlling. #[must_use] - pub fn tweenable_mut(&mut self) -> &mut (dyn Tweenable<T> + Send + Sync + 'static) { + pub fn tweenable_mut(&mut self) -> &mut dyn Tweenable<T> { self.tweenable.as_mut() } @@ -422,7 +422,7 @@ impl<T: Component + std::fmt::Debug> std::fmt::Debug 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 { + pub fn new(tween: impl Tweenable<T> + 'static) -> Self { Self { state: default(), tweenable: Box::new(tween), @@ -457,7 +457,7 @@ impl<T: Asset + std::fmt::Debug> std::fmt::Debug 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 { + pub fn new(handle: Handle<T>, tween: impl Tweenable<T> + 'static) -> Self { Self { state: default(), tweenable: Box::new(tween), diff --git a/src/tweenable.rs b/src/tweenable.rs index 0fc9ad8..442f4fe 100644 --- a/src/tweenable.rs +++ b/src/tweenable.rs @@ -26,17 +26,15 @@ use crate::{EaseMethod, Lens, RepeatCount, RepeatStrategy, TweeningDirection}; /// ```no_run /// # use std::time::Duration; /// # use bevy::prelude::{Entity, Events, Mut, Transform}; -/// # use bevy_tweening::{BoxedTweenable, Sequence, Tweenable, TweenCompleted, TweenState, Targetable}; +/// # use bevy_tweening::{BoxedTweenable, Sequence, Tweenable, TweenCompleted, TweenState, Targetable, TotalDuration}; /// # /// # struct MyTweenable; /// # impl Tweenable<Transform> for MyTweenable { /// # fn duration(&self) -> Duration { unimplemented!() } +/// # fn total_duration(&self) -> TotalDuration { unimplemented!() } /// # fn set_elapsed(&mut self, elapsed: Duration) { unimplemented!() } /// # fn elapsed(&self) -> Duration { unimplemented!() } -/// # fn set_progress(&mut self, progress: f32) { unimplemented!() } -/// # fn progress(&self) -> f32 { unimplemented!() } /// # fn tick<'a>(&mut self, delta: Duration, target: &'a mut dyn Targetable<Transform>, entity: Entity, events: &mut Mut<Events<TweenCompleted>>) -> TweenState { unimplemented!() } -/// # fn times_completed(&self) -> u32 { unimplemented!() } /// # fn rewind(&mut self) { unimplemented!() } /// # } /// @@ -52,7 +50,7 @@ use crate::{EaseMethod, Lens, RepeatCount, RepeatStrategy, TweeningDirection}; /// } /// } /// ``` -pub type BoxedTweenable<T> = Box<dyn Tweenable<T> + Send + Sync + 'static>; +pub type BoxedTweenable<T> = Box<dyn Tweenable<T> + 'static>; /// Playback state of a [`Tweenable`]. /// @@ -161,19 +159,6 @@ impl AnimClock { self.elapsed } - fn set_progress(&mut self, progress: f32) -> (TweenState, i32) { - self.set_elapsed(self.duration.mul_f32(progress.max(0.))) - } - - fn progress(&self) -> f32 { - if let TotalDuration::Finite(total_duration) = self.total_duration { - if self.elapsed >= total_duration { - return 1.; - } - } - fraction_progress(self.elapsed, self.duration) - } - fn state(&self) -> TweenState { match self.total_duration { TotalDuration::Finite(total_duration) => { @@ -192,9 +177,14 @@ impl AnimClock { } } -#[derive(Debug)] -enum TotalDuration { +/// Possibly infinite duration of an animation. +/// +/// Used to measure the total duration of an animation including any looping. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TotalDuration { + /// The duration is finite, of the given value. Finite(Duration), + /// The duration is infinite. Infinite, } @@ -262,9 +252,7 @@ impl<'a, T: Asset> Targetable<T> for AssetTarget<'a, T> { /// An animatable entity, either a single [`Tween`] or a collection of them. pub trait Tweenable<T>: Send + Sync { - /// Get the total duration of the animation. - /// - /// This is always the duration of a single iteration, even when looping. + /// Get the duration of a single iteration of the animation. /// /// Note that for [`RepeatStrategy::MirroredRepeat`], this is the duration /// of a single way, either from start to end or back from end to start. @@ -272,6 +260,13 @@ pub trait Tweenable<T>: Send + Sync { /// same state in this case is the double of the returned value. fn duration(&self) -> Duration; + /// Get the total duration of the entire animation, including looping. + /// + /// For [`TotalDuration::Finite`], this is the number of repeats times the duration of a single iteration ([`duration()`]). + /// + /// [`duration()`]: Tweenable::duration + fn total_duration(&self) -> TotalDuration; + /// Set the current animation playback elapsed time. /// /// See [`elapsed()`] for details on the meaning. If `elapsed` is greater @@ -297,27 +292,6 @@ pub trait Tweenable<T>: Send + Sync { /// [`duration()`]: Tweenable::duration fn elapsed(&self) -> Duration; - /// Set the current animation playback progress. - /// - /// See [`progress()`] for details on the meaning. - /// - /// Setting the progress seeks the animation to a new position, but does not - /// apply that change to the underlying component being animated. To - /// force the change to apply, call [`tick()`] with a `delta` of - /// `Duration::ZERO`. - /// - /// [`progress()`]: Tweenable::progress - /// [`tick()`]: Tweenable::tick - fn set_progress(&mut self, progress: f32); - - /// Get the current progress in \[0:1\] of the animation. - /// - /// While looping, the exact value `1.0` is never reached, since the - /// tweenable loops over to `0.0` immediately when it changes direction at - /// either endpoint. Upon completion, the tweenable always reports exactly - /// `1.0`. - fn progress(&self) -> f32; - /// Tick the animation, advancing it by the given delta time and mutating /// the given target component or asset. /// @@ -342,47 +316,71 @@ pub trait Tweenable<T>: Send + Sync { events: &mut Mut<Events<TweenCompleted>>, ) -> TweenState; - /// Get the number of times this tweenable completed. - /// - /// For looping animations, this returns the number of times a single - /// playback was completed. In the case of - /// [`RepeatStrategy::MirroredRepeat`] this corresponds to a playback in - /// a single direction, so tweening from start to end and back to start - /// counts as two completed times (one forward, one backward). - fn times_completed(&self) -> u32; - /// Rewind the animation to its starting state. /// /// Note that the starting state depends on the current direction. For /// [`TweeningDirection::Forward`] this is the start point of the lens, /// whereas for [`TweeningDirection::Backward`] this is the end one. fn rewind(&mut self); -} -impl<T: 'static> From<Delay<T>> for BoxedTweenable<T> { - fn from(d: Delay<T>) -> Self { - Box::new(d) + /// Set the current animation playback progress. + /// + /// See [`progress()`] for details on the meaning. + /// + /// Setting the progress seeks the animation to a new position, but does not + /// apply that change to the underlying component being animated. To + /// force the change to apply, call [`tick()`] with a `delta` of + /// `Duration::ZERO`. + /// + /// [`progress()`]: Tweenable::progress + /// [`tick()`]: Tweenable::tick + fn set_progress(&mut self, progress: f32) { + self.set_elapsed(self.duration().mul_f32(progress.max(0.))); } -} -impl<T: 'static> From<Sequence<T>> for BoxedTweenable<T> { - fn from(s: Sequence<T>) -> Self { - Box::new(s) + /// Get the current progress in \[0:1\] of the animation. + /// + /// While looping, the exact value `1.0` is never reached, since the + /// tweenable loops over to `0.0` immediately when it changes direction at + /// either endpoint. Upon completion, the tweenable always reports exactly + /// `1.0`. + fn progress(&self) -> f32 { + let elapsed = self.elapsed(); + if let TotalDuration::Finite(total_duration) = self.total_duration() { + if elapsed >= total_duration { + return 1.; + } + } + fraction_progress(elapsed, self.duration()) } -} -impl<T: 'static> From<Tracks<T>> for BoxedTweenable<T> { - fn from(t: Tracks<T>) -> Self { - Box::new(t) + /// Get the number of times this tweenable completed. + /// + /// For looping animations, this returns the number of times a single + /// playback was completed. In the case of + /// [`RepeatStrategy::MirroredRepeat`] this corresponds to a playback in + /// a single direction, so tweening from start to end and back to start + /// counts as two completed times (one forward, one backward). + fn times_completed(&self) -> u32 { + (self.elapsed().as_nanos() / self.duration().as_nanos()) as u32 } } -impl<T: 'static> From<Tween<T>> for BoxedTweenable<T> { - fn from(t: Tween<T>) -> Self { - Box::new(t) - } +macro_rules! impl_boxed { + ($tweenable:ty) => { + impl<T: 'static> From<$tweenable> for BoxedTweenable<T> { + fn from(t: $tweenable) -> Self { + Box::new(t) + } + } + }; } +impl_boxed!(Tween<T>); +impl_boxed!(Sequence<T>); +impl_boxed!(Tracks<T>); +impl_boxed!(Delay<T>); + /// Type of a callback invoked when a [`Tween`] or [`Delay`] has completed. /// /// See [`Tween::set_completed()`] or [`Delay::set_completed()`] for usage. @@ -426,7 +424,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> { + pub fn then(self, tween: impl Tweenable<T> + 'static) -> Sequence<T> { Sequence::with_capacity(2).then(self).then(tween) } } @@ -639,6 +637,10 @@ impl<T> Tweenable<T> for Tween<T> { self.clock.duration } + fn total_duration(&self) -> TotalDuration { + self.clock.total_duration + } + fn set_elapsed(&mut self, elapsed: Duration) { self.clock.set_elapsed(elapsed); } @@ -647,14 +649,6 @@ impl<T> Tweenable<T> for Tween<T> { self.clock.elapsed() } - fn set_progress(&mut self, progress: f32) { - self.clock.set_progress(progress); - } - - fn progress(&self) -> f32 { - self.clock.progress() - } - fn tick<'a>( &mut self, delta: Duration, @@ -704,10 +698,6 @@ impl<T> Tweenable<T> for Tween<T> { state } - fn times_completed(&self) -> u32 { - self.clock.times_completed() - } - fn rewind(&mut self) { if self.clock.strategy == RepeatStrategy::MirroredRepeat { // In mirrored mode, direction alternates each loop. To reset to the original @@ -733,7 +723,6 @@ pub struct Sequence<T> { index: usize, duration: Duration, elapsed: Duration, - times_completed: u32, } impl<T> Sequence<T> { @@ -754,13 +743,12 @@ impl<T> Sequence<T> { index: 0, duration, elapsed: Duration::ZERO, - times_completed: 0, } } /// Create a new sequence containing a single tween. #[must_use] - pub fn from_single(tween: impl Tweenable<T> + Send + Sync + 'static) -> Self { + pub fn from_single(tween: impl Tweenable<T> + 'static) -> Self { let duration = tween.duration(); let boxed: BoxedTweenable<T> = Box::new(tween); Self { @@ -768,7 +756,6 @@ impl<T> Sequence<T> { index: 0, duration, elapsed: Duration::ZERO, - times_completed: 0, } } @@ -780,13 +767,12 @@ impl<T> Sequence<T> { index: 0, duration: Duration::ZERO, elapsed: Duration::ZERO, - times_completed: 0, } } /// Append a [`Tweenable`] to this sequence. #[must_use] - pub fn then(mut self, tween: impl Tweenable<T> + Send + Sync + 'static) -> Self { + pub fn then(mut self, tween: impl Tweenable<T> + 'static) -> Self { self.duration += tween.duration(); self.tweens.push(Box::new(tween)); self @@ -810,10 +796,13 @@ impl<T> Tweenable<T> for Sequence<T> { self.duration } + fn total_duration(&self) -> TotalDuration { + TotalDuration::Finite(self.duration) + } + fn set_elapsed(&mut self, elapsed: Duration) { // Set the total sequence progress self.elapsed = elapsed; - self.times_completed = u32::from(elapsed >= self.duration); // Find which tween is active in the sequence let mut accum_duration = Duration::ZERO; @@ -839,18 +828,6 @@ impl<T> Tweenable<T> for Sequence<T> { self.elapsed } - fn set_progress(&mut self, progress: f32) { - self.set_elapsed(self.duration.mul_f32(progress.max(0.))) - } - - fn progress(&self) -> f32 { - if self.elapsed >= self.duration { - 1. - } else { - fraction_progress(self.elapsed, self.duration) - } - } - fn tick<'a>( &mut self, mut delta: Duration, @@ -861,7 +838,7 @@ impl<T> Tweenable<T> for Sequence<T> { self.elapsed = self.elapsed.saturating_add(delta).min(self.duration); while self.index < self.tweens.len() { let tween = &mut self.tweens[self.index]; - let tween_remaining = tween.duration().mul_f32(1.0 - tween.progress()); + let tween_remaining = tween.duration() - tween.elapsed(); if let TweenState::Active = tween.tick(delta, target, entity, events) { return TweenState::Active; } @@ -871,18 +848,12 @@ impl<T> Tweenable<T> for Sequence<T> { self.index += 1; } - self.times_completed = 1; TweenState::Completed } - fn times_completed(&self) -> u32 { - self.times_completed - } - fn rewind(&mut self) { self.elapsed = Duration::ZERO; self.index = 0; - self.times_completed = 0; for tween in &mut self.tweens { // or only first? tween.rewind(); @@ -895,7 +866,6 @@ pub struct Tracks<T> { tracks: Vec<BoxedTweenable<T>>, duration: Duration, elapsed: Duration, - times_completed: u32, } impl<T> Tracks<T> { @@ -914,7 +884,6 @@ impl<T> Tracks<T> { tracks, duration, elapsed: Duration::ZERO, - times_completed: 0, } } } @@ -924,9 +893,12 @@ impl<T> Tweenable<T> for Tracks<T> { self.duration } + fn total_duration(&self) -> TotalDuration { + TotalDuration::Finite(self.duration) + } + fn set_elapsed(&mut self, elapsed: Duration) { self.elapsed = elapsed; - self.times_completed = u32::from(elapsed >= self.duration); // not looping for tweenable in &mut self.tracks { tweenable.set_elapsed(elapsed); @@ -937,18 +909,6 @@ impl<T> Tweenable<T> for Tracks<T> { self.elapsed } - fn set_progress(&mut self, progress: f32) { - self.set_elapsed(self.duration.mul_f32(progress.max(0.))) - } - - fn progress(&self) -> f32 { - if self.elapsed >= self.duration { - 1. - } else { - fraction_progress(self.elapsed, self.duration) - } - } - fn tick<'a>( &mut self, delta: Duration, @@ -965,18 +925,12 @@ impl<T> Tweenable<T> for Tracks<T> { if any_active { TweenState::Active } else { - self.times_completed = 1; TweenState::Completed } } - fn times_completed(&self) -> u32 { - self.times_completed - } - fn rewind(&mut self) { self.elapsed = Duration::ZERO; - self.times_completed = 0; for tween in &mut self.tracks { tween.rewind(); } @@ -999,7 +953,7 @@ impl<T: 'static> Delay<T> { /// Chain another [`Tweenable`] after this tween, making a [`Sequence`] with /// the two. #[must_use] - pub fn then(self, tween: impl Tweenable<T> + Send + Sync + 'static) -> Sequence<T> { + pub fn then(self, tween: impl Tweenable<T> + 'static) -> Sequence<T> { Sequence::with_capacity(2).then(self).then(tween) } } @@ -1155,6 +1109,10 @@ impl<T> Tweenable<T> for Delay<T> { self.timer.duration() } + fn total_duration(&self) -> TotalDuration { + TotalDuration::Finite(self.duration()) + } + fn set_elapsed(&mut self, elapsed: Duration) { // need to reset() to clear finished() unfortunately self.timer.reset(); @@ -1167,14 +1125,6 @@ impl<T> Tweenable<T> for Delay<T> { self.timer.elapsed() } - fn set_progress(&mut self, progress: f32) { - self.set_elapsed(self.timer.duration().mul_f32(progress.max(0.))); - } - - fn progress(&self) -> f32 { - self.timer.percent() - } - fn tick<'a>( &mut self, delta: Duration, @@ -1204,10 +1154,6 @@ impl<T> Tweenable<T> for Delay<T> { state } - fn times_completed(&self) -> u32 { - u32::from(self.is_completed()) - } - fn rewind(&mut self) { self.timer.reset(); } -- GitLab