Skip to content
Snippets Groups Projects
Unverified Commit fcc89105 authored by Jerome Humbert's avatar Jerome Humbert Committed by GitHub
Browse files

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
parent e6bdbbce
No related branches found
No related tags found
No related merge requests found
......@@ -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
......
......@@ -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),
......
......@@ -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();
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment