Newer
Older
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
pub fn with_completed_event(mut self, user_data: u64) -> Self {
self.event_data = Some(user_data);
self
}
/// Set a callback invoked when the delay completes.
///
/// The callback when invoked receives as parameters the [`Entity`] on which
/// the target and the animator are, as well as a reference to the
/// current [`Delay`]. This is similar to [`with_completed_event()`], but
/// with a callback instead.
///
/// Only non-looping tweenables can complete.
///
/// # Example
///
/// ```
/// # use bevy_tweening::{lens::*, *};
/// # use bevy::{ecs::event::EventReader, math::Vec3};
/// # use std::time::Duration;
/// let tween = Tween::new(
/// // [...]
/// # EaseFunction::QuadraticInOut,
/// # Duration::from_secs(1),
/// # TransformPositionLens {
/// # start: Vec3::ZERO,
/// # end: Vec3::new(3.5, 0., 0.),
/// # },
/// )
/// .with_completed(|entity, delay| {
/// println!("Delay of {} seconds elapsed on entity {:?}",
/// delay.duration().as_secs(), entity);
/// });
/// ```
///
/// [`with_completed_event()`]: Tween::with_completed_event
pub fn with_completed<C>(mut self, callback: C) -> Self
where
C: Fn(Entity, &Self) + Send + Sync + 'static,
{
self.on_completed = Some(Box::new(callback));
self
}
/// Check if the delay completed.
pub fn is_completed(&self) -> bool {
self.timer.finished()
}
/// Get the current tweenable state.
pub fn state(&self) -> TweenState {
if self.is_completed() {
TweenState::Completed
} else {
TweenState::Active
}
}
/// Set a callback invoked when the animation completes.
///
/// The callback when invoked receives as parameters the [`Entity`] on which
/// the target and the animator are, as well as a reference to the
/// current [`Tween`].
///
/// Only non-looping tweenables can complete.
pub fn set_completed<C>(&mut self, callback: C)
where
C: Fn(Entity, &Self) + Send + Sync + 'static,
{
self.on_completed = Some(Box::new(callback));
}
/// Clear the callback invoked when the animation completes.
///
/// See also [`set_completed()`].
///
/// [`set_completed()`]: Tween::set_completed
pub fn clear_completed(&mut self) {
self.on_completed = None;
}
/// Enable or disable raising a completed event.
///
/// If enabled, the tween will raise a [`TweenCompleted`] event when the
/// animation completed. This is similar to the [`set_completed()`]
/// callback, but uses Bevy events instead.
///
/// See [`with_completed_event()`] for details.
///
/// [`set_completed()`]: Tween::set_completed
/// [`with_completed_event()`]: Tween::with_completed_event
pub fn set_completed_event(&mut self, user_data: u64) {
self.event_data = Some(user_data);
}
/// Clear the event sent when the animation completes.
///
/// See also [`set_completed_event()`].
///
/// [`set_completed_event()`]: Tween::set_completed_event
pub fn clear_completed_event(&mut self) {
self.event_data = None;
impl<T> Tweenable<T> for Delay<T> {
fn duration(&self) -> Duration {
self.timer.duration()
}
fn total_duration(&self) -> TotalDuration {
TotalDuration::Finite(self.duration())
}
// need to reset() to clear finished() unfortunately
self.timer.reset();
// set_elapsed() does not update finished() etc. which we rely on
self.timer.tick(Duration::ZERO);
}
fn elapsed(&self) -> Duration {
self.timer.elapsed()
}
_target: &'a mut dyn Targetable<T>,
entity: Entity,
events: &mut Mut<Events<TweenCompleted>>,
let was_completed = self.is_completed();
let state = self.state();
// If completed this frame, notify the user
if (state == TweenState::Completed) && !was_completed {
if let Some(user_data) = &self.event_data {
events.send(TweenCompleted {
entity,
user_data: *user_data,
});
}
if let Some(cb) = &self.on_completed {
cb(entity, self);
}
}
fn rewind(&mut self) {
self.timer.reset();
}
}
#[cfg(test)]
mod tests {
use std::{
sync::{Arc, Mutex},
time::Duration,
};
use bevy::ecs::{event::Events, system::SystemState};
use super::*;
use crate::{lens::*, test_utils::*};
#[derive(Default, Copy, Clone)]
struct CallbackMonitor {
invoke_count: u64,
last_reported_count: u32,
}
/// Utility to create a tween for testing.
fn make_test_tween() -> Tween<Transform> {
Tween::new(
EaseMethod::Linear,
Duration::from_secs(1),
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::ONE,
},
)
}
/// Utility to create a test environment to tick a tween.
fn make_test_env() -> (World, Entity) {
let mut world = World::new();
world.init_resource::<Events<TweenCompleted>>();
let entity = world.spawn().insert(Transform::default()).id();
(world, entity)
}
/// Manually tick a test tweenable targeting a component.
fn manual_tick_component<T: Component>(
duration: Duration,
tween: &mut dyn Tweenable<T>,
world: &mut World,
entity: Entity,
) -> TweenState {
world.resource_scope(
|world: &mut World, mut events: Mut<Events<TweenCompleted>>| {
let transform = world.get_mut::<T>(entity).unwrap();
let mut target = ComponentTarget::new(transform);
tween.tick(duration, &mut target, entity, &mut events)
},
)
#[test]
fn anim_clock_precision() {
let duration = Duration::from_millis(1);
let mut clock = AnimClock::new(duration);
clock.total_duration = TotalDuration::Infinite;
let test_ticks = [
Duration::from_micros(123),
Duration::from_millis(1),
Duration::from_secs_f32(1. / 24.),
Duration::from_secs_f32(1. / 30.),
Duration::from_secs_f32(1. / 60.),
Duration::from_secs_f32(1. / 120.),
Duration::from_secs_f32(1. / 144.),
Duration::from_secs_f32(1. / 240.),
];
let mut times_completed = 0;
let mut total_duration = Duration::ZERO;
for i in 0..10_000_000 {
let tick = test_ticks[i % test_ticks.len()];
times_completed += clock.tick(tick).1;
total_duration += tick;
}
assert_eq!(
(total_duration.as_secs_f64() / duration.as_secs_f64()) as i32,
times_completed
);
}
/// Test ticking of a single tween in isolation.
#[test]
fn tween_tick() {
for tweening_direction in &[TweeningDirection::Forward, TweeningDirection::Backward] {
for (count, strategy) in &[
(RepeatCount::Finite(1), RepeatStrategy::default()),
(RepeatCount::Infinite, RepeatStrategy::Repeat),
(RepeatCount::Finite(2), RepeatStrategy::Repeat),
(RepeatCount::Infinite, RepeatStrategy::MirroredRepeat),
(RepeatCount::Finite(2), RepeatStrategy::MirroredRepeat),
"TweeningType: count={count:?} strategy={strategy:?} dir={tweening_direction:?}",
// Create a linear tween over 1 second
let mut tween = make_test_tween()
.with_direction(*tweening_direction)
.with_repeat_count(*count)
.with_repeat_strategy(*strategy);
assert_eq!(tween.direction(), *tweening_direction);
assert!(tween.on_completed.is_none());
assert!(tween.event_data.is_none());
let (mut world, entity) = make_test_env();
let mut event_reader_system_state: SystemState<EventReader<TweenCompleted>> =
SystemState::new(&mut world);
// Register callbacks to count started/ended events
let callback_monitor = Arc::new(Mutex::new(CallbackMonitor::default()));
let cb_mon_ptr = Arc::clone(&callback_monitor);
let reference_entity = entity;
tween.set_completed(move |completed_entity, tween| {
assert_eq!(completed_entity, reference_entity);
let mut cb_mon = cb_mon_ptr.lock().unwrap();
cb_mon.invoke_count += 1;
cb_mon.last_reported_count = tween.times_completed();
});
assert!(tween.on_completed.is_some());
assert!(tween.event_data.is_none());
assert_eq!(callback_monitor.lock().unwrap().invoke_count, 0);
// Activate event sending
const USER_DATA: u64 = 54789; // dummy
tween.set_completed_event(USER_DATA);
assert!(tween.event_data.is_some());
assert_eq!(tween.event_data.unwrap(), USER_DATA);
// Loop over 2.2 seconds, so greater than one ping-pong loop
let tick_duration = Duration::from_millis(200);
for i in 1..=11 {
// Calculate expected values
let (progress, times_completed, mut direction, expected_state, just_completed) =
match count {
RepeatCount::Finite(1) => {
let progress = (i as f32 * 0.2).min(1.0);
let times_completed = u32::from(i >= 5);
let state = if i < 5 {
TweenState::Active
} else {
TweenState::Completed
};
let just_completed = i == 5;
(
progress,
times_completed,
TweeningDirection::Forward,
state,
just_completed,
)
}
RepeatCount::Finite(count) => {
let total_progress = i as f32 * 0.2;
let progress = if total_progress >= *count as f32 {
1.
} else {
total_progress.fract()
};
if *strategy == RepeatStrategy::Repeat {
let times_completed = i / 5;
let just_completed = i % 5 == 0;
(
progress,
times_completed,
TweeningDirection::Forward,
if i < 10 {
TweenState::Active
} else {
TweenState::Completed
},
just_completed,
)
} else {
let i5 = i % 5;
let times_completed = i / 5;
// Once Completed, the direction doesn't change
let direction = if i >= 5 {
TweeningDirection::Backward
} else {
TweeningDirection::Forward
};
let just_completed = i5 == 0;
(
progress,
times_completed,
direction,
if i < 10 {
TweenState::Active
} else {
TweenState::Completed
},
just_completed,
)
}
RepeatCount::Infinite => {
let progress = (i as f32 * 0.2).fract();
if *strategy == RepeatStrategy::Repeat {
let times_completed = i / 5;
let just_completed = i % 5 == 0;
(
progress,
times_completed,
TweeningDirection::Forward,
TweenState::Active,
just_completed,
)
let i5 = i % 5;
let times_completed = i / 5;
let i10 = i % 10;
let direction = if i10 >= 5 {
TweeningDirection::Backward
} else {
TweeningDirection::Forward
};
let just_completed = i5 == 0;
(
progress,
times_completed,
direction,
TweenState::Active,
just_completed,
)
}
RepeatCount::For(_) => panic!("Untested"),
};
let factor = if tweening_direction.is_backward() {
direction = !direction;
1. - progress
} else {
progress
};
let expected_translation = if direction.is_forward() {
Vec3::splat(progress)
} else {
Vec3::splat(1. - progress)
};
println!(
"Expected: progress={} factor={} times_completed={} direction={:?} state={:?} just_completed={} translation={:?}",
progress, factor, times_completed, direction, expected_state, just_completed, expected_translation
);
// Tick the tween
let actual_state =
manual_tick_component(tick_duration, &mut tween, &mut world, entity);
// Propagate events
{
let mut events = world.resource_mut::<Events<TweenCompleted>>();
events.update();
}
// Check actual values
assert_eq!(tween.direction(), direction);
assert_eq!(actual_state, expected_state);
assert_approx_eq!(tween.progress(), progress);
assert_eq!(tween.times_completed(), times_completed);
let transform = world.entity(entity).get::<Transform>().unwrap();
assert!(transform
.translation
.abs_diff_eq(expected_translation, 1e-5));
assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
let cb_mon = callback_monitor.lock().unwrap();
assert_eq!(cb_mon.invoke_count, times_completed as u64);
assert_eq!(cb_mon.last_reported_count, times_completed);
{
let mut event_reader = event_reader_system_state.get_mut(&mut world);
let event = event_reader.iter().next();
if just_completed {
assert!(event.is_some());
if let Some(event) = event {
assert_eq!(event.entity, entity);
assert_eq!(event.user_data, USER_DATA);
}
} else {
assert!(event.is_none());
}
}
}
// Rewind
tween.rewind();
assert_eq!(tween.direction(), *tweening_direction); // does not change
assert_approx_eq!(tween.progress(), 0.);
assert_eq!(tween.times_completed(), 0);
// Dummy tick to update target
let actual_state =
manual_tick_component(Duration::ZERO, &mut tween, &mut world, entity);
assert_eq!(actual_state, TweenState::Active);
let expected_translation = if tweening_direction.is_backward() {
Vec3::ONE
} else {
Vec3::ZERO
};
let transform = world.entity(entity).get::<Transform>().unwrap();
assert!(transform
.translation
.abs_diff_eq(expected_translation, 1e-5));
assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
// Clear callback
tween.clear_completed();
assert!(tween.on_completed.is_none());
// Clear event sending
tween.clear_completed_event();
assert!(tween.event_data.is_none());
#[test]
fn tween_dir() {
let mut tween = make_test_tween();
// Default
assert_eq!(tween.direction(), TweeningDirection::Forward);
assert_approx_eq!(tween.progress(), 0.0);
// no-op
tween.set_direction(TweeningDirection::Forward);
assert_eq!(tween.direction(), TweeningDirection::Forward);
assert_approx_eq!(tween.progress(), 0.0);
// Backward
tween.set_direction(TweeningDirection::Backward);
assert_eq!(tween.direction(), TweeningDirection::Backward);
// progress is independent of direction
assert_approx_eq!(tween.progress(), 0.0);
// Progress-invariant
tween.set_direction(TweeningDirection::Forward);
tween.set_progress(0.3);
assert_approx_eq!(tween.progress(), 0.3);
tween.set_direction(TweeningDirection::Backward);
// progress is independent of direction
assert_approx_eq!(tween.progress(), 0.3);
let (mut world, entity) = make_test_env();
// Progress always increases alongside the current direction
tween.set_direction(TweeningDirection::Backward);
assert_approx_eq!(tween.progress(), 0.3);
manual_tick_component(Duration::from_millis(100), &mut tween, &mut world, entity);
assert_approx_eq!(tween.progress(), 0.4);
let transform = world.entity(entity).get::<Transform>().unwrap();
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.6), 1e-5));
}
#[test]
fn tween_elapsed() {
let mut tween = make_test_tween();
let duration = tween.duration();
let elapsed = tween.elapsed();
assert_eq!(elapsed, Duration::ZERO);
assert_eq!(duration, Duration::from_secs(1));
for ms in [0, 1, 500, 100, 300, 999, 847, 1000, 900] {
let elapsed = Duration::from_millis(ms);
tween.set_elapsed(elapsed);
assert_eq!(tween.elapsed(), elapsed);
let progress = (elapsed.as_secs_f64() / duration.as_secs_f64()) as f32;
assert_approx_eq!(tween.progress(), progress);
let times_completed = u32::from(ms == 1000);
assert_eq!(tween.times_completed(), times_completed);
}
}
/// Test ticking a sequence of tweens.
#[test]
fn seq_tick() {
let tween1 = Tween::new(
EaseMethod::Linear,
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::ONE,
},
);
let tween2 = Tween::new(
EaseMethod::Linear,
TransformRotationLens {
start: Quat::IDENTITY,
},
);
let mut seq = tween1.then(tween2);
let (mut world, entity) = make_test_env();
let state =
manual_tick_component(Duration::from_millis(200), &mut seq, &mut world, entity);
let transform = world.entity(entity).get::<Transform>().unwrap();
assert_eq!(state, TweenState::Active);
assert_eq!(*transform, Transform::from_translation(Vec3::splat(r)));
assert_eq!(state, TweenState::Active);
assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
assert!(transform
.rotation
.abs_diff_eq(Quat::from_rotation_x(alpha_deg.to_radians()), 1e-5));
} else {
assert_eq!(state, TweenState::Completed);
assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
.abs_diff_eq(Quat::from_rotation_x(90_f32.to_radians()), 1e-5));
/// Test crossing tween boundaries in one tick.
#[test]
fn seq_tick_boundaries() {
let mut seq = Sequence::new((0..3).map(|i| {
Tween::new(
EaseMethod::Linear,
Duration::from_secs(1),
TransformPositionLens {
start: Vec3::splat(i as f32),
end: Vec3::splat((i + 1) as f32),
},
)
.with_repeat_count(RepeatCount::Finite(1))
}));
let (mut world, entity) = make_test_env();
// Tick halfway through the first tween, then in one tick:
// - Finish the first tween
// - Start and finish the second tween
// - Start the third tween
for delta_ms in [500, 2000] {
manual_tick_component(
Duration::from_millis(delta_ms),
&mut seq,
&mut world,
);
}
assert_eq!(seq.index(), 2);
let transform = world.entity(entity).get::<Transform>().unwrap();
assert!(transform.translation.abs_diff_eq(Vec3::splat(2.5), 1e-5));
}
/// Sequence::new() and various Sequence-specific methods
#[test]
fn seq_iter() {
let mut seq = Sequence::new((1..5).map(|i| {
Tween::new(
EaseMethod::Linear,
Duration::from_millis(200 * i),
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::ONE,
},
)
}));
let mut progress = 0.;
for i in 1..5 {
assert_eq!(seq.index(), i - 1);
assert_approx_eq!(seq.progress(), progress);
let duration = Duration::from_millis(200 * i as u64);
assert_eq!(seq.current().duration(), duration);
progress += 0.25;
seq.set_progress(progress);
assert_eq!(seq.times_completed(), u32::from(i == 4));
}
seq.rewind();
assert_eq!(seq.progress(), 0.);
assert_eq!(seq.times_completed(), 0);
}
/// Sequence::from_single()
#[test]
fn seq_from_single() {
let tween = Tween::new(
EaseMethod::Linear,
Duration::from_secs(1),
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::ONE,
},
);
let seq = Sequence::from_single(tween);
assert_eq!(seq.duration(), Duration::from_secs(1));
}
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
#[test]
fn seq_elapsed() {
let mut seq = Sequence::new((1..5).map(|i| {
Tween::new(
EaseMethod::Linear,
Duration::from_millis(200 * i),
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::ONE,
},
)
}));
let mut elapsed = Duration::ZERO;
for i in 1..5 {
assert_eq!(seq.index(), i - 1);
assert_eq!(seq.elapsed(), elapsed);
let duration = Duration::from_millis(200 * i as u64);
assert_eq!(seq.current().duration(), duration);
elapsed += duration;
seq.set_elapsed(elapsed);
assert_eq!(seq.times_completed(), u32::from(i == 4));
/// Test ticking parallel tracks of tweens.
#[test]
fn tracks_tick() {
let tween1 = Tween::new(
EaseMethod::Linear,
Duration::from_millis(1000),
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::ONE,
},
);
let tween2 = Tween::new(
EaseMethod::Linear,
Duration::from_millis(800), // shorter
TransformRotationLens {
start: Quat::IDENTITY,
},
);
let mut tracks = Tracks::new([tween1, tween2]);
assert_eq!(tracks.duration(), Duration::from_secs(1)); // max(1., 0.8)
let (mut world, entity) = make_test_env();
let state =
manual_tick_component(Duration::from_millis(200), &mut tracks, &mut world, entity);
let transform = world.entity(entity).get::<Transform>().unwrap();
assert_eq!(state, TweenState::Active);
assert_eq!(tracks.times_completed(), 0);
assert_approx_eq!(tracks.progress(), r);
assert!(transform.translation.abs_diff_eq(Vec3::splat(r), 1e-5));
assert!(transform
.rotation
.abs_diff_eq(Quat::from_rotation_x(alpha_deg.to_radians()), 1e-5));
} else {
assert_eq!(state, TweenState::Completed);
assert_eq!(tracks.times_completed(), 1);
assert_approx_eq!(tracks.progress(), 1.);
assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
.abs_diff_eq(Quat::from_rotation_x(90_f32.to_radians()), 1e-5));
tracks.rewind();
assert_eq!(tracks.times_completed(), 0);
assert_approx_eq!(tracks.progress(), 0.);
tracks.set_progress(0.9);
assert_approx_eq!(tracks.progress(), 0.9);
// tick to udpate state (set_progress() does not update state)
let state = manual_tick_component(Duration::ZERO, &mut tracks, &mut world, entity);
assert_eq!(state, TweenState::Active);
assert_eq!(tracks.times_completed(), 0);
tracks.set_progress(3.2);
assert_approx_eq!(tracks.progress(), 1.);
// tick to udpate state (set_progress() does not update state)
let state = manual_tick_component(Duration::ZERO, &mut tracks, &mut world, entity);
assert_eq!(state, TweenState::Completed);
assert_eq!(tracks.times_completed(), 1); // no looping
tracks.set_progress(-0.5);
assert_approx_eq!(tracks.progress(), 0.);
// tick to udpate state (set_progress() does not update state)
let state = manual_tick_component(Duration::ZERO, &mut tracks, &mut world, entity);
assert_eq!(state, TweenState::Active);
assert_eq!(tracks.times_completed(), 0); // no looping
}
/// Delay::then()
#[test]
fn delay_then() {
let seq: Sequence<Transform> =
Delay::new(Duration::from_secs(1)).then(Delay::new(Duration::from_secs(2)));
assert_eq!(seq.duration(), Duration::from_secs(3));
assert_eq!(seq.tweens.len(), 2);
for (i, t) in seq.tweens.iter().enumerate() {
assert_eq!(t.duration(), Duration::from_secs(i as u64 + 1));
}
}
/// Test ticking a delay.
#[test]
fn delay_tick() {
const USER_DATA: u64 = 42;
let mut delay = Delay::new(duration).with_completed_event(USER_DATA);
assert!(delay.event_data.is_some());
assert_eq!(delay.event_data.unwrap(), USER_DATA);
delay.clear_completed_event();
assert!(delay.event_data.is_none());
delay.set_completed_event(USER_DATA);
assert!(delay.event_data.is_some());
assert_eq!(delay.event_data.unwrap(), USER_DATA);
{
let tweenable: &dyn Tweenable<Transform> = &delay;
assert_eq!(tweenable.duration(), duration);
assert_approx_eq!(tweenable.progress(), 0.);
assert_eq!(tweenable.elapsed(), Duration::ZERO);
}
// Dummy world and event writer
let (mut world, entity) = make_test_env();
let mut event_reader_system_state: SystemState<EventReader<TweenCompleted>> =
SystemState::new(&mut world);
// Register callbacks to count completed events
let callback_monitor = Arc::new(Mutex::new(CallbackMonitor::default()));
let cb_mon_ptr = Arc::clone(&callback_monitor);
let reference_entity = entity;
assert!(delay.on_completed.is_none());
delay.set_completed(move |completed_entity, delay| {
assert_eq!(completed_entity, reference_entity);
let mut cb_mon = cb_mon_ptr.lock().unwrap();
cb_mon.invoke_count += 1;
cb_mon.last_reported_count = delay.times_completed();
});
assert!(delay.on_completed.is_some());
assert_eq!(callback_monitor.lock().unwrap().invoke_count, 0);
for i in 1..=6 {
let state = manual_tick_component::<Transform>(
&mut delay,
&mut world,
// Propagate events
{
let mut events = world.resource_mut::<Events<TweenCompleted>>();
events.update();
}
// Check state
assert_eq!(state, delay.state());
let tweenable: &dyn Tweenable<Transform> = &delay;
{
let mut event_reader = event_reader_system_state.get_mut(&mut world);
let event = event_reader.iter().next();
if i == 5 {
assert!(event.is_some());
let event = event.unwrap();
assert_eq!(event.entity, entity);
assert_eq!(event.user_data, USER_DATA);
} else {
assert!(event.is_none());
}
}
let times_completed = if i < 5 {
assert_eq!(state, TweenState::Active);
assert!(!delay.is_completed());
let r = i as f32 * 0.2;
assert_approx_eq!(tweenable.progress(), r);
} else {
assert_eq!(state, TweenState::Completed);
assert!(delay.is_completed());
assert_approx_eq!(tweenable.progress(), 1.);
1
};
let cb_mon = callback_monitor.lock().unwrap();
assert_eq!(cb_mon.invoke_count, times_completed as u64);
assert_eq!(cb_mon.last_reported_count, times_completed);
delay.rewind();
assert_eq!(delay.times_completed(), 0);
assert_approx_eq!(delay.progress(), 0.);
let state = manual_tick_component(Duration::ZERO, &mut delay, &mut world, entity);
delay.set_progress(0.3);
assert_eq!(delay.times_completed(), 0);
assert_approx_eq!(delay.progress(), 0.3);
delay.set_progress(1.);
assert_eq!(delay.times_completed(), 1);
assert_approx_eq!(delay.progress(), 1.);
// Clear callback
delay.clear_completed();
assert!(delay.on_completed.is_none());
// Clear event sending
delay.clear_completed_event();
assert!(delay.event_data.is_none());
let mut delay: Delay<f32> = Delay::new(Duration::from_secs(1));
let duration = delay.duration();
for ms in [0, 1, 500, 100, 300, 999, 847, 1000, 900] {
let elapsed = Duration::from_millis(ms);
delay.set_elapsed(elapsed);
assert_eq!(delay.elapsed(), elapsed);
let progress = (elapsed.as_secs_f64() / duration.as_secs_f64()) as f32;
assert_approx_eq!(delay.progress(), progress);
let times_completed = u32::from(ms == 1000);
assert_eq!(delay.times_completed(), times_completed);
assert_eq!(delay.is_completed(), ms >= 1000);
assert_eq!(
delay.state(),
if ms >= 1000 {
TweenState::Completed
} else {
TweenState::Active
}
);
#[test]
#[should_panic]
fn delay_zero_duration_panics() {
let _: Delay<f32> = Delay::new(Duration::ZERO);
#[test]
fn tween_repeat() {
let mut tween = make_test_tween()
.with_repeat_count(RepeatCount::Finite(5))
.with_repeat_strategy(RepeatStrategy::Repeat);
assert_approx_eq!(tween.progress(), 0.);
let (mut world, entity) = make_test_env();
let state =
manual_tick_component(Duration::from_millis(100), &mut tween, &mut world, entity);
assert_eq!(TweenState::Active, state);
assert_eq!(0, tween.times_completed());
assert_approx_eq!(tween.progress(), 0.1);
let transform = world.entity(entity).get::<Transform>().unwrap();
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5));
// 130%
let state =
manual_tick_component(Duration::from_millis(1200), &mut tween, &mut world, entity);
assert_eq!(TweenState::Active, state);
assert_eq!(1, tween.times_completed());
assert_approx_eq!(tween.progress(), 0.3);
let transform = world.entity(entity).get::<Transform>().unwrap();
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.3), 1e-5));
// 480%
let state =
manual_tick_component(Duration::from_millis(3500), &mut tween, &mut world, entity);
assert_eq!(TweenState::Active, state);
assert_eq!(4, tween.times_completed());
assert_approx_eq!(tween.progress(), 0.8);
let transform = world.entity(entity).get::<Transform>().unwrap();
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5));
// 500% - done
let state =
manual_tick_component(Duration::from_millis(200), &mut tween, &mut world, entity);
assert_eq!(TweenState::Completed, state);
assert_eq!(5, tween.times_completed());
assert_approx_eq!(tween.progress(), 1.0);
let transform = world.entity(entity).get::<Transform>().unwrap();
assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
}
#[test]
fn tween_mirrored_rewind() {
let mut tween = make_test_tween()
.with_repeat_count(RepeatCount::Finite(4))
.with_repeat_strategy(RepeatStrategy::MirroredRepeat);
assert_approx_eq!(tween.progress(), 0.);
let (mut world, entity) = make_test_env();
let state =
manual_tick_component(Duration::from_millis(100), &mut tween, &mut world, entity);
assert_eq!(TweenState::Active, state);
assert_eq!(TweeningDirection::Forward, tween.direction());
assert_eq!(0, tween.times_completed());
assert_approx_eq!(tween.progress(), 0.1);
let transform = world.entity(entity).get::<Transform>().unwrap();
assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5));