-
Alex Saveau authored
It needs to be built-in because otherwise you have to completely recreate the animation to change the speed.
Unverified454bcd00
lib.rs 28.80 KiB
#![deny(
warnings,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications,
missing_docs
)]
//! Tweening animation plugin for the Bevy game engine
//!
//! 🍃 Bevy Tweening provides interpolation-based animation between ("tweening") two values, for Bevy components
//! and assets. Each field of a component or asset can be animated via a collection or predefined easing functions,
//! or providing a custom animation curve. Custom components and assets are also supported.
//!
//! # Example
//!
//! Add the tweening plugin to your app:
//!
//! ```no_run
//! use bevy::prelude::*;
//! use bevy_tweening::*;
//!
//! App::default()
//! .add_plugins(DefaultPlugins)
//! .add_plugin(TweeningPlugin)
//! .run();
//! ```
//!
//! Animate the position ([`Transform::translation`]) of an [`Entity`]:
//!
//! ```
//! # use bevy::prelude::*;
//! # use bevy_tweening::{lens::*, *};
//! # use std::time::Duration;
//! # fn system(mut commands: Commands) {
//! # let size = 16.;
//! // Create a single animation (tween) to move an entity.
//! let tween = Tween::new(
//! // Use a quadratic easing on both endpoints.
//! EaseFunction::QuadraticInOut,
//! // Loop animation back and forth.
//! TweeningType::PingPong,
//! // Animation time (one way only; for ping-pong it takes 2 seconds
//! // to come back to start).
//! Duration::from_secs(1),
//! // The lens gives access to the Transform component of the Entity,
//! // for the Animator to animate it. It also contains the start and
//! // end values respectively associated with the progress ratios 0. and 1.
//! TransformPositionLens {
//! start: Vec3::ZERO,
//! end: Vec3::new(1., 2., -4.),
//! },
//! );
//!
//! commands
//! // Spawn an entity to animate the position of.
//! .spawn_bundle(TransformBundle::default())
//! // Add an Animator component to control and execute the animation.
//! .insert(Animator::new(tween));
//! # }
//! ```
//!
//! # Tweenables
//!
//! 🍃 Bevy Tweening supports several types of _tweenables_, building blocks that can be combined to form complex
//! animations. A tweenable is a type implementing the [`Tweenable`] trait.
//!
//! - [`Tween`] - A simple tween (easing) animation between two values.
//! - [`Sequence`] - A series of tweenables executing in series, one after the other.
//! - [`Tracks`] - A collection of tweenables executing in parallel.
//! - [`Delay`] - A time delay.
//!
//! ## Chaining animations
//!
//! Most tweenables can be chained with the `then()` operator to produce a [`Sequence`] tweenable:
//!
//! ```
//! # use bevy::prelude::*;
//! # use bevy_tweening::{lens::*, *};
//! # use std::time::Duration;
//! let tween1 = Tween::new(
//! // [...]
//! # EaseFunction::BounceOut,
//! # TweeningType::Once,
//! # Duration::from_secs(2),
//! # TransformScaleLens {
//! # start: Vec3::ZERO,
//! # end: Vec3::ONE,
//! # },
//! );
//! let tween2 = Tween::new(
//! // [...]
//! # EaseFunction::QuadraticInOut,
//! # TweeningType::Once,
//! # Duration::from_secs(1),
//! # TransformPositionLens {
//! # start: Vec3::ZERO,
//! # end: Vec3::new(3.5, 0., 0.),
//! # },
//! );
//! // Produce a Sequence executing first 'tween1' then 'tween2'
//! let seq = tween1.then(tween2);
//! ```
//!
//! # Animators and lenses
//!
//! Bevy components and assets are animated with tweening _animator_ components, which take a tweenable and
//! apply it to another component on the same [`Entity`]. Those animators determine that other component and
//! its fields to animate using a _lens_.
//!
//! ## Components animation
//!
//! Components are animated with the [`Animator`] component, which is generic over the type of component
//! it animates. This is a restriction imposed by Bevy, to access the animated component as a mutable
//! reference via a [`Query`] and comply with the ECS rules.
//!
//! The [`Animator`] itself is not generic over the subset of fields of the components it animates.
//! This limits the proliferation of generic types when animating e.g. both the position and rotation
//! of an entity.
//!
//! ## Assets animation
//!
//! Assets are animated in a similar way to component, via the [`AssetAnimator`] component.
//!
//! Because assets are typically shared, and the animation applies to the asset itself, all users of the asset
//! see the animation. For example, animating the color of a [`ColorMaterial`] will change the color of all the
//! 2D meshes using that material.
//!
//! ## Lenses
//!
//! Both [`Animator`] and [`AssetAnimator`] access the field(s) to animate via a lens, a type that implements
//! the [`Lens`] trait.
//!
//! Several predefined lenses are provided in the [`lens`] module for the most commonly animated fields, like the
//! components of a [`Transform`]. A custom lens can also be created by implementing the trait, allowing to animate
//! virtually any field of any Bevy component or asset.
//!
//! [`Transform::translation`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html#structfield.translation
//! [`Entity`]: https://docs.rs/bevy/0.7.0/bevy/ecs/entity/struct.Entity.html
//! [`Query`]: https://docs.rs/bevy/0.7.0/bevy/ecs/system/struct.Query.html
//! [`ColorMaterial`]: https://docs.rs/bevy/0.7.0/bevy/sprite/struct.ColorMaterial.html
//! [`Sprite`]: https://docs.rs/bevy/0.7.0/bevy/sprite/struct.Sprite.html
//! [`Transform`]: https://docs.rs/bevy/0.7.0/bevy/transform/components/struct.Transform.html
use bevy::{asset::Asset, prelude::*};
use std::time::Duration;
use interpolation::Ease as IEase;
pub use interpolation::EaseFunction;
pub use interpolation::Lerp;
pub mod lens;
mod plugin;
mod tweenable;
pub use lens::Lens;
pub use plugin::{asset_animator_system, component_animator_system, TweeningPlugin};
pub use tweenable::{Delay, Sequence, Tracks, Tween, TweenCompleted, TweenState, Tweenable};
/// Type of looping for a tween animation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TweeningType {
/// Run the animation once from start to end only.
Once,
/// Loop the animation indefinitely, restarting from the start each time the end is reached.
Loop,
/// Loop the animation back and forth, changing direction each time an endpoint is reached.
/// A complete cycle start -> end -> start always counts as 2 loop iterations for the various
/// operations where looping matters.
PingPong,
}
impl Default for TweeningType {
fn default() -> Self {
TweeningType::Once
}
}
/// Playback state of an animator.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimatorState {
/// The animation is playing. This is the default state.
Playing,
/// The animation is paused in its current state.
Paused,
}
impl Default for AnimatorState {
fn default() -> Self {
AnimatorState::Playing
}
}
impl std::ops::Not for AnimatorState {
type Output = AnimatorState;
fn not(self) -> Self::Output {
match self {
AnimatorState::Paused => AnimatorState::Playing,
AnimatorState::Playing => AnimatorState::Paused,
}
}
}
/// Describe how eased value should be computed.
#[derive(Clone, Copy)]
pub enum EaseMethod {
/// Follow `EaseFunction`.
EaseFunction(EaseFunction),
/// Linear interpolation, with no function.
Linear,
/// Discrete interpolation, eased value will jump from start to end when
/// stepping over the discrete limit.
Discrete(f32),
/// Use a custom function to interpolate the value.
CustomFunction(fn(f32) -> f32),
}
impl EaseMethod {
fn sample(self, x: f32) -> f32 {
match self {
EaseMethod::EaseFunction(function) => x.calc(function),
EaseMethod::Linear => x,
EaseMethod::Discrete(limit) => {
if x > limit {
1.
} else {
0.
}
}
EaseMethod::CustomFunction(function) => function(x),
}
}
}
impl Default for EaseMethod {
fn default() -> Self {
EaseMethod::Linear
}
}
impl From<EaseFunction> for EaseMethod {
fn from(ease_function: EaseFunction) -> Self {
EaseMethod::EaseFunction(ease_function)
}
}
/// Direction a tweening animation is playing.
///
/// When playing a tweenable forward, the progress values `0` and `1` are respectively mapped to
/// the start and end bounds of the lens(es) being used. Conversely, when playing backward, this
/// mapping is reversed, such that a progress value of `0` corresponds to the state of the target
/// at the end bound of the lens, while a progress value of `1` corresponds to the state of that
/// target at the start bound of the lens, effectively making the animation play backward.
///
/// For all but [`TweeningType::PingPong`] this is always [`TweeningDirection::Forward`], unless
/// manually configured with [`Tween::set_direction()`] in which case the value is constant equal
/// to the value set. For the [`TweeningType::PingPong`] tweening type, this is either forward
/// (from start to end; ping) or backward (from end to start; pong), depending on the current
/// iteration of the loop.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TweeningDirection {
/// Animation playing from start to end.
Forward,
/// Animation playing from end to start, in reverse.
Backward,
}
impl TweeningDirection {
/// Is the direction equal to [`TweeningDirection::Forward`]?
pub fn is_forward(&self) -> bool {
*self == TweeningDirection::Forward
}
/// Is the direction equal to [`TweeningDirection::Backward`]?
pub fn is_backward(&self) -> bool {
*self == TweeningDirection::Backward
}
}
impl Default for TweeningDirection {
fn default() -> Self {
TweeningDirection::Forward
}
}
impl std::ops::Not for TweeningDirection {
type Output = TweeningDirection;
fn not(self) -> Self::Output {
match self {
TweeningDirection::Forward => TweeningDirection::Backward,
TweeningDirection::Backward => TweeningDirection::Forward,
}
}
}
/// Component to control the animation of another component.
#[derive(Component)]
pub struct Animator<T: Component> {
/// Control if this animation is played or not.
pub state: AnimatorState,
tweenable: Option<Box<dyn Tweenable<T> + Send + Sync + 'static>>,
speed: f32,
}
impl<T: Component + std::fmt::Debug> std::fmt::Debug for Animator<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Animator")
.field("state", &self.state)
.finish()
}
}
impl<T: Component> Default for Animator<T> {
fn default() -> Self {
Animator {
state: Default::default(),
tweenable: None,
speed: 1.,
}
}
}
impl<T: Component> Animator<T> {
/// Create a new animator component from a single tweenable.
pub fn new(tween: impl Tweenable<T> + Send + Sync + 'static) -> Self {
Animator {
tweenable: Some(Box::new(tween)),
..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();
}
}
}
/// Component to control the animation of an asset.
#[derive(Component)]
pub struct AssetAnimator<T: Asset> {
/// Control if this animation is played or not.
pub state: AnimatorState,
tweenable: Option<Box<dyn Tweenable<T> + Send + Sync + 'static>>,
handle: Handle<T>,
speed: f32,
}
impl<T: Asset + std::fmt::Debug> std::fmt::Debug for AssetAnimator<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AssetAnimator")
.field("state", &self.state)
.finish()
}
}
impl<T: Asset> Default for AssetAnimator<T> {
fn default() -> Self {
AssetAnimator {
state: Default::default(),
tweenable: None,
handle: Default::default(),
speed: 1.,
}
}
}
impl<T: Asset> AssetAnimator<T> {
/// Create a new asset animator component from a single tweenable.
pub fn new(handle: Handle<T>, tween: impl Tweenable<T> + Send + Sync + 'static) -> Self {
AssetAnimator {
tweenable: Some(Box::new(tween)),
handle,
..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();
}
}
fn handle(&self) -> Handle<T> {
self.handle.clone()
}
}
#[cfg(test)]
mod tests {
use super::{lens::*, *};
use bevy::reflect::TypeUuid;
struct DummyLens {
start: f32,
end: f32,
}
#[derive(Component)]
struct DummyComponent {
value: f32,
}
#[derive(Reflect, TypeUuid)]
#[uuid = "a33abc11-264e-4bbb-82e8-b87226bb4383"]
struct DummyAsset {
value: f32,
}
impl Lens<DummyComponent> for DummyLens {
fn lerp(&mut self, target: &mut DummyComponent, ratio: f32) {
target.value = self.start.lerp(&self.end, &ratio);
}
}
impl Lens<DummyAsset> for DummyLens {
fn lerp(&mut self, target: &mut DummyAsset, ratio: f32) {
target.value = self.start.lerp(&self.end, &ratio);
}
}
#[test]
fn tweening_type() {
let tweening_type = TweeningType::default();
assert_eq!(tweening_type, TweeningType::Once);
}
#[test]
fn tweening_direction() {
let tweening_direction = TweeningDirection::default();
assert_eq!(tweening_direction, TweeningDirection::Forward);
}
#[test]
fn animator_state() {
let mut state = AnimatorState::default();
assert_eq!(state, AnimatorState::Playing);
state = !state;
assert_eq!(state, AnimatorState::Paused);
state = !state;
assert_eq!(state, AnimatorState::Playing);
}
#[test]
fn ease_method() {
let ease = EaseMethod::default();
assert!(matches!(ease, EaseMethod::Linear));
let ease = EaseMethod::EaseFunction(EaseFunction::QuadraticIn);
assert_eq!(0., ease.sample(0.));
assert_eq!(0.25, ease.sample(0.5));
assert_eq!(1., ease.sample(1.));
let ease = EaseMethod::Linear;
assert_eq!(0., ease.sample(0.));
assert_eq!(0.5, ease.sample(0.5));
assert_eq!(1., ease.sample(1.));
let ease = EaseMethod::Discrete(0.3);
assert_eq!(0., ease.sample(0.));
assert_eq!(1., ease.sample(0.5));
assert_eq!(1., ease.sample(1.));
let ease = EaseMethod::CustomFunction(|f| 1. - f);
assert_eq!(0., ease.sample(1.));
assert_eq!(0.5, ease.sample(0.5));
assert_eq!(1., ease.sample(0.));
}
/// Animator::new()
#[test]
fn animator_new() {
let tween = Tween::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
std::time::Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
let animator = Animator::<DummyComponent>::new(tween);
assert_eq!(animator.state, AnimatorState::default());
let tween = animator.tweenable().unwrap();
assert_eq!(tween.progress(), 0.);
}
/// Animator::with_state()
#[test]
fn animator_with_state() {
for state in [AnimatorState::Playing, AnimatorState::Paused] {
let tween = Tween::<DummyComponent>::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
std::time::Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
let animator = Animator::new(tween).with_state(state);
assert_eq!(animator.state, state);
}
}
/// Animator::default() + Animator::set_tweenable()
#[test]
fn animator_default() {
let mut animator = Animator::<DummyComponent>::default();
assert!(animator.tweenable().is_none());
assert!(animator.tweenable_mut().is_none());
let tween = Tween::<DummyComponent>::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
std::time::Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
animator.set_tweenable(tween);
assert!(animator.tweenable().is_some());
assert!(animator.tweenable_mut().is_some());
}
/// Animator control playback
#[test]
fn animator_controls() {
let tween = Tween::<DummyComponent>::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
std::time::Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
let mut animator = Animator::new(tween);
assert_eq!(animator.state, AnimatorState::Playing);
assert!(animator.progress().abs() <= 1e-5);
animator.stop();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.progress().abs() <= 1e-5);
animator.set_progress(0.5);
assert_eq!(animator.state, AnimatorState::Paused);
assert!((animator.progress() - 0.5).abs() <= 1e-5);
animator.rewind();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.progress().abs() <= 1e-5);
animator.set_progress(0.5);
animator.state = AnimatorState::Playing;
assert_eq!(animator.state, AnimatorState::Playing);
assert!((animator.progress() - 0.5).abs() <= 1e-5);
animator.rewind();
assert_eq!(animator.state, AnimatorState::Playing);
assert!(animator.progress().abs() <= 1e-5);
animator.stop();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.progress().abs() <= 1e-5);
}
/// AssetAnimator::new()
#[test]
fn asset_animator_new() {
let tween = Tween::<DummyAsset>::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
std::time::Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
let animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween);
assert_eq!(animator.state, AnimatorState::default());
let tween = animator.tweenable().unwrap();
assert_eq!(tween.progress(), 0.);
}
/// AssetAnimator::with_state()
#[test]
fn asset_animator_with_state() {
for state in [AnimatorState::Playing, AnimatorState::Paused] {
let tween = Tween::<DummyAsset>::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
std::time::Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
let animator =
AssetAnimator::new(Handle::<DummyAsset>::default(), tween).with_state(state);
assert_eq!(animator.state, state);
}
}
/// AssetAnimator::default() + AssetAnimator::set_tweenable()
#[test]
fn asset_animator_default() {
let mut animator = AssetAnimator::<DummyAsset>::default();
assert!(animator.tweenable().is_none());
assert!(animator.tweenable_mut().is_none());
assert_eq!(animator.handle(), Handle::<DummyAsset>::default());
let tween = Tween::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
std::time::Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
animator.set_tweenable(tween);
assert!(animator.tweenable().is_some());
assert!(animator.tweenable_mut().is_some());
assert_eq!(animator.handle(), Handle::<DummyAsset>::default());
}
/// AssetAnimator control playback
#[test]
fn asset_animator_controls() {
let tween = Tween::new(
EaseFunction::QuadraticInOut,
TweeningType::PingPong,
std::time::Duration::from_secs(1),
DummyLens { start: 0., end: 1. },
);
let mut animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween);
assert_eq!(animator.state, AnimatorState::Playing);
assert!(animator.progress().abs() <= 1e-5);
animator.stop();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.progress().abs() <= 1e-5);
animator.set_progress(0.5);
assert_eq!(animator.state, AnimatorState::Paused);
assert!((animator.progress() - 0.5).abs() <= 1e-5);
animator.rewind();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.progress().abs() <= 1e-5);
animator.set_progress(0.5);
animator.state = AnimatorState::Playing;
assert_eq!(animator.state, AnimatorState::Playing);
assert!((animator.progress() - 0.5).abs() <= 1e-5);
animator.rewind();
assert_eq!(animator.state, AnimatorState::Playing);
assert!(animator.progress().abs() <= 1e-5);
animator.stop();
assert_eq!(animator.state, AnimatorState::Paused);
assert!(animator.progress().abs() <= 1e-5);
}
}