diff --git a/examples/menu.rs b/examples/menu.rs index 731b49a6e11e5aa8e4d800a4d301b0d8a5240228..244a99e18b6056a3d5ec64bba929b615b36b0142 100644 --- a/examples/menu.rs +++ b/examples/menu.rs @@ -1,213 +1,214 @@ -use bevy::prelude::*; -use bevy_inspector_egui::WorldInspectorPlugin; -use bevy_tweening::{lens::*, *}; -use std::time::Duration; - -const NORMAL_COLOR: Color = Color::rgba(162. / 255., 226. / 255., 95. / 255., 1.); -const HOVER_COLOR: Color = Color::AZURE; -const CLICK_COLOR: Color = Color::ALICE_BLUE; -const TEXT_COLOR: Color = Color::rgba(83. / 255., 163. / 255., 130. / 255., 1.); -const INIT_TRANSITION_DONE: u64 = 1; - -/// The menu in this example has two set of animations: -/// one for appearance, one for interaction. Interaction animations -/// are only enabled after appearance animations finished. -/// -/// The logic is handled as: -/// 1. Appearance animations send a `TweenComplete` event with `INIT_TRANSITION_DONE` -/// 2. The `enable_interaction_after_initial_animation` system adds a label component -/// `InitTransitionDone` to any button component which completed its appearance animation, -/// to mark it as active. -/// 3. The `interaction` system only queries buttons with a `InitTransitionDone` marker. -fn main() { - App::default() - .insert_resource(WindowDescriptor { - title: "Menu".to_string(), - width: 800., - height: 400., - present_mode: bevy::window::PresentMode::Fifo, // vsync - ..default() - }) - .add_plugins(DefaultPlugins) - .add_system(bevy::window::close_on_esc) - .add_system(interaction) - .add_system(enable_interaction_after_initial_animation) - .add_plugin(TweeningPlugin) - .add_plugin(WorldInspectorPlugin::new()) - .add_startup_system(setup) - .run(); -} - -fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { - commands.spawn_bundle(Camera2dBundle::default()); - - let font = asset_server.load("fonts/FiraMono-Regular.ttf"); - - commands - .spawn_bundle(NodeBundle { - style: Style { - position_type: PositionType::Absolute, - position: UiRect::all(Val::Px(0.)), - margin: UiRect::all(Val::Px(16.)), - padding: UiRect::all(Val::Px(16.)), - flex_direction: FlexDirection::ColumnReverse, - align_content: AlignContent::Center, - align_items: AlignItems::Center, - align_self: AlignSelf::Center, - justify_content: JustifyContent::Center, - ..default() - }, - color: UiColor(Color::NONE), - ..default() - }) - .insert(Name::new("menu")) - .with_children(|container| { - let mut start_time_ms = 0; - for (text, label) in [ - ("Continue", ButtonLabel::Continue), - ("New Game", ButtonLabel::NewGame), - ("Settings", ButtonLabel::Settings), - ("Quit", ButtonLabel::Quit), - ] { - let tween_scale = Tween::new( - EaseFunction::BounceOut, - Duration::from_secs(2), - TransformScaleLens { - start: Vec3::splat(0.01), - end: Vec3::ONE, - }, - ) - .with_completed_event(INIT_TRANSITION_DONE); - - let animator = if start_time_ms > 0 { - let delay = Delay::new(Duration::from_millis(start_time_ms)); - Animator::new(delay.then(tween_scale)) - } else { - Animator::new(tween_scale) - }; - - start_time_ms += 500; - container - .spawn_bundle(ButtonBundle { - node: Node { - size: Vec2::new(300., 80.), - }, - style: Style { - min_size: Size::new(Val::Px(300.), Val::Px(80.)), - margin: UiRect::all(Val::Px(8.)), - padding: UiRect::all(Val::Px(8.)), - align_content: AlignContent::Center, - align_items: AlignItems::Center, - align_self: AlignSelf::Center, - justify_content: JustifyContent::Center, - ..default() - }, - color: UiColor(NORMAL_COLOR), - transform: Transform::from_scale(Vec3::splat(0.01)), - ..default() - }) - .insert(Name::new(format!("button:{}", text))) - .insert(animator) - .insert(label) - .with_children(|parent| { - parent.spawn_bundle(TextBundle { - text: Text::from_section( - text.to_string(), - TextStyle { - font: font.clone(), - font_size: 48.0, - color: TEXT_COLOR, - }, - ) - .with_alignment(TextAlignment { - vertical: VerticalAlign::Center, - horizontal: HorizontalAlign::Center, - }), - ..default() - }); - }); - } - }); -} - -fn enable_interaction_after_initial_animation( - mut commands: Commands, - mut reader: EventReader<TweenCompleted>, -) { - for event in reader.iter() { - if event.user_data == INIT_TRANSITION_DONE { - commands.entity(event.entity).insert(InitTransitionDone); - } - } -} - -#[derive(Component)] -struct InitTransitionDone; - -#[derive(Component, Clone, Copy)] -enum ButtonLabel { - Continue, - NewGame, - Settings, - Quit, -} - -fn interaction( - mut interaction_query: Query< - ( - &mut Animator<Transform>, - &Transform, - &Interaction, - &mut UiColor, - &ButtonLabel, - ), - (Changed<Interaction>, With<InitTransitionDone>), - >, -) { - for (mut animator, transform, interaction, mut color, button_label) in &mut interaction_query { - match *interaction { - Interaction::Clicked => { - *color = CLICK_COLOR.into(); - - match button_label { - ButtonLabel::Continue => { - println!("Continue clicked"); - } - ButtonLabel::NewGame => { - println!("NewGame clicked"); - } - ButtonLabel::Settings => { - println!("Settings clicked"); - } - ButtonLabel::Quit => { - println!("Quit clicked"); - } - } - } - Interaction::Hovered => { - *color = HOVER_COLOR.into(); - animator.set_tweenable(Tween::new( - EaseFunction::QuadraticIn, - Duration::from_millis(200), - TransformScaleLens { - start: Vec3::ONE, - end: Vec3::splat(1.1), - }, - )); - } - Interaction::None => { - *color = NORMAL_COLOR.into(); - let start_scale = transform.scale; - - animator.set_tweenable(Tween::new( - EaseFunction::QuadraticIn, - Duration::from_millis(200), - TransformScaleLens { - start: start_scale, - end: Vec3::ONE, - }, - )); - } - } - } -} +use bevy::prelude::*; +use bevy_inspector_egui::WorldInspectorPlugin; +use bevy_tweening::{lens::*, *}; +use std::time::Duration; + +const NORMAL_COLOR: Color = Color::rgba(162. / 255., 226. / 255., 95. / 255., 1.); +const HOVER_COLOR: Color = Color::AZURE; +const CLICK_COLOR: Color = Color::ALICE_BLUE; +const TEXT_COLOR: Color = Color::rgba(83. / 255., 163. / 255., 130. / 255., 1.); +const INIT_TRANSITION_DONE: u64 = 1; + +/// The menu in this example has two set of animations: +/// one for appearance, one for interaction. Interaction animations +/// are only enabled after appearance animations finished. +/// +/// The logic is handled as: +/// 1. Appearance animations send a `TweenComplete` event with +/// `INIT_TRANSITION_DONE` 2. The `enable_interaction_after_initial_animation` +/// system adds a label component `InitTransitionDone` to any button component +/// which completed its appearance animation, to mark it as active. +/// 3. The `interaction` system only queries buttons with a `InitTransitionDone` +/// marker. +fn main() { + App::default() + .insert_resource(WindowDescriptor { + title: "Menu".to_string(), + width: 800., + height: 400., + present_mode: bevy::window::PresentMode::Fifo, // vsync + ..default() + }) + .add_plugins(DefaultPlugins) + .add_system(bevy::window::close_on_esc) + .add_system(interaction) + .add_system(enable_interaction_after_initial_animation) + .add_plugin(TweeningPlugin) + .add_plugin(WorldInspectorPlugin::new()) + .add_startup_system(setup) + .run(); +} + +fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { + commands.spawn_bundle(Camera2dBundle::default()); + + let font = asset_server.load("fonts/FiraMono-Regular.ttf"); + + commands + .spawn_bundle(NodeBundle { + style: Style { + position_type: PositionType::Absolute, + position: UiRect::all(Val::Px(0.)), + margin: UiRect::all(Val::Px(16.)), + padding: UiRect::all(Val::Px(16.)), + flex_direction: FlexDirection::ColumnReverse, + align_content: AlignContent::Center, + align_items: AlignItems::Center, + align_self: AlignSelf::Center, + justify_content: JustifyContent::Center, + ..default() + }, + color: UiColor(Color::NONE), + ..default() + }) + .insert(Name::new("menu")) + .with_children(|container| { + let mut start_time_ms = 0; + for (text, label) in [ + ("Continue", ButtonLabel::Continue), + ("New Game", ButtonLabel::NewGame), + ("Settings", ButtonLabel::Settings), + ("Quit", ButtonLabel::Quit), + ] { + let tween_scale = Tween::new( + EaseFunction::BounceOut, + Duration::from_secs(2), + TransformScaleLens { + start: Vec3::splat(0.01), + end: Vec3::ONE, + }, + ) + .with_completed_event(INIT_TRANSITION_DONE); + + let animator = if start_time_ms > 0 { + let delay = Delay::new(Duration::from_millis(start_time_ms)); + Animator::new(delay.then(tween_scale)) + } else { + Animator::new(tween_scale) + }; + + start_time_ms += 500; + container + .spawn_bundle(ButtonBundle { + node: Node { + size: Vec2::new(300., 80.), + }, + style: Style { + min_size: Size::new(Val::Px(300.), Val::Px(80.)), + margin: UiRect::all(Val::Px(8.)), + padding: UiRect::all(Val::Px(8.)), + align_content: AlignContent::Center, + align_items: AlignItems::Center, + align_self: AlignSelf::Center, + justify_content: JustifyContent::Center, + ..default() + }, + color: UiColor(NORMAL_COLOR), + transform: Transform::from_scale(Vec3::splat(0.01)), + ..default() + }) + .insert(Name::new(format!("button:{}", text))) + .insert(animator) + .insert(label) + .with_children(|parent| { + parent.spawn_bundle(TextBundle { + text: Text::from_section( + text.to_string(), + TextStyle { + font: font.clone(), + font_size: 48.0, + color: TEXT_COLOR, + }, + ) + .with_alignment(TextAlignment { + vertical: VerticalAlign::Center, + horizontal: HorizontalAlign::Center, + }), + ..default() + }); + }); + } + }); +} + +fn enable_interaction_after_initial_animation( + mut commands: Commands, + mut reader: EventReader<TweenCompleted>, +) { + for event in reader.iter() { + if event.user_data == INIT_TRANSITION_DONE { + commands.entity(event.entity).insert(InitTransitionDone); + } + } +} + +#[derive(Component)] +struct InitTransitionDone; + +#[derive(Component, Clone, Copy)] +enum ButtonLabel { + Continue, + NewGame, + Settings, + Quit, +} + +fn interaction( + mut interaction_query: Query< + ( + &mut Animator<Transform>, + &Transform, + &Interaction, + &mut UiColor, + &ButtonLabel, + ), + (Changed<Interaction>, With<InitTransitionDone>), + >, +) { + for (mut animator, transform, interaction, mut color, button_label) in &mut interaction_query { + match *interaction { + Interaction::Clicked => { + *color = CLICK_COLOR.into(); + + match button_label { + ButtonLabel::Continue => { + println!("Continue clicked"); + } + ButtonLabel::NewGame => { + println!("NewGame clicked"); + } + ButtonLabel::Settings => { + println!("Settings clicked"); + } + ButtonLabel::Quit => { + println!("Quit clicked"); + } + } + } + Interaction::Hovered => { + *color = HOVER_COLOR.into(); + animator.set_tweenable(Tween::new( + EaseFunction::QuadraticIn, + Duration::from_millis(200), + TransformScaleLens { + start: Vec3::ONE, + end: Vec3::splat(1.1), + }, + )); + } + Interaction::None => { + *color = NORMAL_COLOR.into(); + let start_scale = transform.scale; + + animator.set_tweenable(Tween::new( + EaseFunction::QuadraticIn, + Duration::from_millis(200), + TransformScaleLens { + start: start_scale, + end: Vec3::ONE, + }, + )); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 8be9eb234a34eba9881af8793d6040818d49aff2..f5cb2f11926c248055b3c33b95c6fdeccdb7f68a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,6 +171,9 @@ pub mod lens; mod plugin; mod tweenable; +#[cfg(test)] +mod test_utils; + /// How many times to repeat a tween animation. See also: [`RepeatStrategy`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RepeatCount { @@ -475,12 +478,8 @@ mod tests { #[cfg(feature = "bevy_asset")] use bevy::reflect::TypeUuid; - use super::{lens::*, *}; - - /// Utility to compare floating-point values with a tolerance. - fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool { - (a - b).abs() < tol - } + use super::*; + use crate::test_utils::*; struct DummyLens { start: f32, @@ -511,7 +510,7 @@ mod tests { let mut l = DummyLens { start: 0., end: 1. }; for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] { l.lerp(&mut c, r); - assert!(abs_diff_eq(c.value, r, 1e-5)); + assert_approx_eq!(c.value, r); } } @@ -529,7 +528,7 @@ mod tests { let mut l = DummyLens { start: 0., end: 1. }; for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] { l.lerp(&mut a, r); - assert!(abs_diff_eq(a.value, r, 1e-5)); + assert_approx_eq!(a.value, r); } } @@ -628,32 +627,32 @@ mod tests { ); let mut animator = Animator::new(tween); assert_eq!(animator.state, AnimatorState::Playing); - assert!(animator.tweenable().progress().abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.); animator.stop(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.tweenable().progress().abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.); animator.tweenable_mut().set_progress(0.5); assert_eq!(animator.state, AnimatorState::Paused); - assert!((animator.tweenable().progress() - 0.5).abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.5); animator.tweenable_mut().rewind(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.tweenable().progress().abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.); animator.tweenable_mut().set_progress(0.5); animator.state = AnimatorState::Playing; assert_eq!(animator.state, AnimatorState::Playing); - assert!((animator.tweenable().progress() - 0.5).abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.5); animator.tweenable_mut().rewind(); assert_eq!(animator.state, AnimatorState::Playing); - assert!(animator.tweenable().progress().abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.); animator.stop(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.tweenable().progress().abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.); } #[test] @@ -665,10 +664,10 @@ mod tests { ); let mut animator = Animator::new(tween); - assert!(abs_diff_eq(animator.speed(), 1., 1e-5)); // default speed + assert_approx_eq!(animator.speed(), 1.); // default speed animator.set_speed(2.4); - assert!(abs_diff_eq(animator.speed(), 2.4, 1e-5)); + assert_approx_eq!(animator.speed(), 2.4); let tween = Tween::<DummyComponent>::new( EaseFunction::QuadraticInOut, @@ -677,7 +676,7 @@ mod tests { ); let animator = Animator::new(tween).with_speed(3.5); - assert!(abs_diff_eq(animator.speed(), 3.5, 1e-5)); + assert_approx_eq!(animator.speed(), 3.5); } #[test] @@ -746,32 +745,32 @@ mod tests { ); let mut animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween); assert_eq!(animator.state, AnimatorState::Playing); - assert!(animator.tweenable().progress().abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.); animator.stop(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.tweenable().progress().abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.); animator.tweenable_mut().set_progress(0.5); assert_eq!(animator.state, AnimatorState::Paused); - assert!((animator.tweenable().progress() - 0.5).abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.5); animator.tweenable_mut().rewind(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.tweenable().progress().abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.); animator.tweenable_mut().set_progress(0.5); animator.state = AnimatorState::Playing; assert_eq!(animator.state, AnimatorState::Playing); - assert!((animator.tweenable().progress() - 0.5).abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.5); animator.tweenable_mut().rewind(); assert_eq!(animator.state, AnimatorState::Playing); - assert!(animator.tweenable().progress().abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.); animator.stop(); assert_eq!(animator.state, AnimatorState::Paused); - assert!(animator.tweenable().progress().abs() <= 1e-5); + assert_approx_eq!(animator.tweenable().progress(), 0.); } #[cfg(feature = "bevy_asset")] @@ -784,10 +783,10 @@ mod tests { ); let mut animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween); - assert!(abs_diff_eq(animator.speed(), 1., 1e-5)); // default speed + assert_approx_eq!(animator.speed(), 1.); // default speed animator.set_speed(2.4); - assert!(abs_diff_eq(animator.speed(), 2.4, 1e-5)); + assert_approx_eq!(animator.speed(), 2.4); let tween = Tween::new( EaseFunction::QuadraticInOut, @@ -796,7 +795,7 @@ mod tests { ); let animator = AssetAnimator::new(Handle::<DummyAsset>::default(), tween).with_speed(3.5); - assert!(abs_diff_eq(animator.speed(), 3.5, 1e-5)); + assert_approx_eq!(animator.speed(), 3.5); } #[cfg(feature = "bevy_asset")] diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..f525fd3730da5dbdec8ca0841aa0c4a159fc5cb0 --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,51 @@ +/// Utility to compare floating-point values with a tolerance. +pub(crate) fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool { + (a - b).abs() < tol +} + +/// Assert that two floating-point quantities are approximately equal. +/// +/// This macro asserts that the absolute difference between the two first +/// arguments is strictly less than a tolerance factor, which can be explicitly +/// passed as third argument or implicitly defaults to `1e-5`. +/// +/// # Usage +/// +/// ``` +/// let x = 3.500009; +/// assert_approx_eq!(x, 3.5); // default tolerance 1e-5 +/// +/// let x = 3.509; +/// assert_approx_eq!(x, 3.5, 0.01); // explicit tolerance +/// ``` +macro_rules! assert_approx_eq { + ($left:expr, $right:expr $(,)?) => { + match (&$left, &$right) { + (left_val, right_val) => { + assert!( + abs_diff_eq(*left_val, *right_val, 1e-5), + "assertion failed: expected={} actual={} delta={} tol=1e-5(default)", + left_val, + right_val, + (left_val - right_val).abs(), + ); + } + } + }; + ($left:expr, $right:expr, $tol:expr $(,)?) => { + match (&$left, &$right, &$tol) { + (left_val, right_val, tol_val) => { + assert!( + abs_diff_eq(*left_val, *right_val, *tol_val), + "assertion failed: expected={} actual={} delta={} tol={}", + left_val, + right_val, + (left_val - right_val).abs(), + tol_val + ); + } + } + }; +} + +pub(crate) use assert_approx_eq; diff --git a/src/tweenable.rs b/src/tweenable.rs index 5402dd24397974a53bf97e90ae47ef2a0b7dbf82..d5ead020ab0beecc9e9d147226791ac9430b8cc9 100644 --- a/src/tweenable.rs +++ b/src/tweenable.rs @@ -301,7 +301,7 @@ impl<T: 'static> Tween<T> { /// # use std::time::Duration; /// let tween1 = Tween::new( /// EaseFunction::QuadraticInOut, - /// Duration::from_secs_f32(1.0), + /// Duration::from_secs(1), /// TransformPositionLens { /// start: Vec3::ZERO, /// end: Vec3::new(3.5, 0., 0.), @@ -309,7 +309,7 @@ impl<T: 'static> Tween<T> { /// ); /// let tween2 = Tween::new( /// EaseFunction::QuadraticInOut, - /// Duration::from_secs_f32(1.0), + /// Duration::from_secs(1), /// TransformRotationLens { /// start: Quat::IDENTITY, /// end: Quat::from_rotation_x(90.0_f32.to_radians()), @@ -333,7 +333,7 @@ impl<T> Tween<T> { /// # use std::time::Duration; /// let tween = Tween::new( /// EaseFunction::QuadraticInOut, - /// Duration::from_secs_f32(1.0), + /// Duration::from_secs(1), /// TransformPositionLens { /// start: Vec3::ZERO, /// end: Vec3::new(3.5, 0., 0.), @@ -369,7 +369,7 @@ impl<T> Tween<T> { /// let tween = Tween::new( /// // [...] /// # EaseFunction::QuadraticInOut, - /// # Duration::from_secs_f32(1.0), + /// # Duration::from_secs(1), /// # TransformPositionLens { /// # start: Vec3::ZERO, /// # end: Vec3::new(3.5, 0., 0.), @@ -887,14 +887,8 @@ mod tests { use bevy::ecs::{event::Events, system::SystemState}; - use crate::lens::*; - use super::*; - - /// Utility to compare floating-point values with a tolerance. - fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool { - (a - b).abs() < tol - } + use crate::{lens::*, test_utils::*}; #[derive(Default, Copy, Clone)] struct CallbackMonitor { @@ -1005,7 +999,7 @@ mod tests { 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_secs_f32(0.2); + 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) = @@ -1136,7 +1130,7 @@ mod tests { // Check actual values assert_eq!(tween.direction(), direction); assert_eq!(actual_state, expected_state); - assert!(abs_diff_eq(tween.progress(), progress, 1e-5)); + assert_approx_eq!(tween.progress(), progress); assert_eq!(tween.times_completed(), times_completed); assert!(transform .translation @@ -1163,7 +1157,7 @@ mod tests { // Rewind tween.rewind(); assert_eq!(tween.direction(), *tweening_direction); // does not change - assert!(abs_diff_eq(tween.progress(), 0., 1e-5)); + assert_approx_eq!(tween.progress(), 0.); assert_eq!(tween.times_completed(), 0); // Dummy tick to update target @@ -1200,26 +1194,26 @@ mod tests { // Default assert_eq!(tween.direction(), TweeningDirection::Forward); - assert!(abs_diff_eq(tween.progress(), 0.0, 1e-5)); + assert_approx_eq!(tween.progress(), 0.0); // no-op tween.set_direction(TweeningDirection::Forward); assert_eq!(tween.direction(), TweeningDirection::Forward); - assert!(abs_diff_eq(tween.progress(), 0.0, 1e-5)); + 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!(abs_diff_eq(tween.progress(), 0.0, 1e-5)); + assert_approx_eq!(tween.progress(), 0.0); // Progress-invariant tween.set_direction(TweeningDirection::Forward); tween.set_progress(0.3); - assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5)); + assert_approx_eq!(tween.progress(), 0.3); tween.set_direction(TweeningDirection::Backward); // progress is independent of direction - assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5)); + assert_approx_eq!(tween.progress(), 0.3); let (mut world, entity, mut transform) = make_test_env(); let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> = @@ -1228,14 +1222,14 @@ mod tests { // Progress always increases alongside the current direction tween.set_direction(TweeningDirection::Backward); - assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5)); + assert_approx_eq!(tween.progress(), 0.3); tween.tick( - Duration::from_secs_f32(0.1), + Duration::from_millis(100), &mut transform, entity, &mut event_writer, ); - assert!(abs_diff_eq(tween.progress(), 0.4, 1e-5)); + assert_approx_eq!(tween.progress(), 0.4); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.6), 1e-5)); } @@ -1244,7 +1238,7 @@ mod tests { fn seq_tick() { let tween1 = Tween::new( EaseMethod::Linear, - Duration::from_secs_f32(1.0), + Duration::from_secs(1), TransformPositionLens { start: Vec3::ZERO, end: Vec3::ONE, @@ -1252,7 +1246,7 @@ mod tests { ); let tween2 = Tween::new( EaseMethod::Linear, - Duration::from_secs_f32(1.0), + Duration::from_secs(1), TransformRotationLens { start: Quat::IDENTITY, end: Quat::from_rotation_x(90_f32.to_radians()), @@ -1267,7 +1261,7 @@ mod tests { for i in 1..=16 { let state = seq.tick( - Duration::from_secs_f32(0.2), + Duration::from_millis(200), &mut transform, entity, &mut event_writer, @@ -1317,9 +1311,9 @@ mod tests { // - Finish the first tween // - Start and finish the second tween // - Start the third tween - for delta in [0.5, 2.0] { + for delta_ms in [500, 2000] { seq.tick( - Duration::from_secs_f32(delta), + Duration::from_millis(delta_ms), &mut transform, entity, &mut event_writer, @@ -1335,7 +1329,7 @@ mod tests { let mut seq = Sequence::new((1..5).map(|i| { Tween::new( EaseMethod::Linear, - Duration::from_secs_f32(0.2 * i as f32), + Duration::from_millis(200 * i), TransformPositionLens { start: Vec3::ZERO, end: Vec3::ONE, @@ -1346,9 +1340,9 @@ mod tests { let mut progress = 0.; for i in 1..5 { assert_eq!(seq.index(), i - 1); - assert!((seq.progress() - progress).abs() < 1e-5); - let secs = 0.2 * i as f32; - assert_eq!(seq.current().duration(), Duration::from_secs_f32(secs)); + 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(), if i == 4 { 1 } else { 0 }); @@ -1395,7 +1389,7 @@ mod tests { }, ); let mut tracks = Tracks::new([tween1, tween2]); - assert_eq!(tracks.duration(), Duration::from_secs_f32(1.)); // max(1., 0.8) + assert_eq!(tracks.duration(), Duration::from_secs(1)); // max(1., 0.8) let (mut world, entity, mut transform) = make_test_env(); let mut system_state: SystemState<EventWriter<TweenCompleted>> = @@ -1404,7 +1398,7 @@ mod tests { for i in 1..=6 { let state = tracks.tick( - Duration::from_secs_f32(0.2), + Duration::from_millis(200), &mut transform, entity, &mut event_writer, @@ -1413,7 +1407,7 @@ mod tests { assert_eq!(state, TweenState::Active); assert_eq!(tracks.times_completed(), 0); let r = i as f32 * 0.2; - assert!((tracks.progress() - r).abs() < 1e-5); + assert_approx_eq!(tracks.progress(), r); let alpha_deg = 22.5 * i as f32; assert!(transform.translation.abs_diff_eq(Vec3::splat(r), 1e-5)); assert!(transform @@ -1422,7 +1416,7 @@ mod tests { } else { assert_eq!(state, TweenState::Completed); assert_eq!(tracks.times_completed(), 1); - assert!((tracks.progress() - 1.).abs() < 1e-5); + assert_approx_eq!(tracks.progress(), 1.); assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5)); assert!(transform .rotation @@ -1432,13 +1426,13 @@ mod tests { tracks.rewind(); assert_eq!(tracks.times_completed(), 0); - assert!(tracks.progress().abs() < 1e-5); + assert_approx_eq!(tracks.progress(), 0.); tracks.set_progress(0.9); - assert!((tracks.progress() - 0.9).abs() < 1e-5); + assert_approx_eq!(tracks.progress(), 0.9); // tick to udpate state (set_progress() does not update state) let state = tracks.tick( - Duration::from_secs_f32(0.), + Duration::ZERO, &mut transform, Entity::from_raw(0), &mut event_writer, @@ -1447,10 +1441,10 @@ mod tests { assert_eq!(tracks.times_completed(), 0); tracks.set_progress(3.2); - assert!((tracks.progress() - 1.).abs() < 1e-5); + assert_approx_eq!(tracks.progress(), 1.); // tick to udpate state (set_progress() does not update state) let state = tracks.tick( - Duration::from_secs_f32(0.), + Duration::ZERO, &mut transform, Entity::from_raw(0), &mut event_writer, @@ -1459,10 +1453,10 @@ mod tests { assert_eq!(tracks.times_completed(), 1); // no looping tracks.set_progress(-0.5); - assert!(tracks.progress().abs() < 1e-5); + assert_approx_eq!(tracks.progress(), 0.); // tick to udpate state (set_progress() does not update state) let state = tracks.tick( - Duration::from_secs_f32(0.), + Duration::ZERO, &mut transform, Entity::from_raw(0), &mut event_writer, @@ -1491,7 +1485,7 @@ mod tests { { let tweenable: &dyn Tweenable<Transform> = &delay; assert_eq!(tweenable.duration(), duration); - assert!(tweenable.progress().abs() < 1e-5); + assert_approx_eq!(tweenable.progress(), 0.); } // Dummy world and event writer @@ -1513,11 +1507,11 @@ mod tests { assert_eq!(state, TweenState::Active); assert_eq!(tweenable.times_completed(), 0); let r = i as f32 * 0.2; - assert!((tweenable.progress() - r).abs() < 1e-5); + assert_approx_eq!(tweenable.progress(), r); } else { assert_eq!(state, TweenState::Completed); assert_eq!(tweenable.times_completed(), 1); - assert!((tweenable.progress() - 1.).abs() < 1e-5); + assert_approx_eq!(tweenable.progress(), 1.); } } } @@ -1526,16 +1520,16 @@ mod tests { tweenable.rewind(); assert_eq!(tweenable.times_completed(), 0); - assert!(abs_diff_eq(tweenable.progress(), 0., 1e-5)); + assert_approx_eq!(tweenable.progress(), 0.); let state = tweenable.tick(Duration::ZERO, &mut transform, entity, &mut event_writer); assert_eq!(state, TweenState::Active); tweenable.set_progress(0.3); assert_eq!(tweenable.times_completed(), 0); - assert!(abs_diff_eq(tweenable.progress(), 0.3, 1e-5)); + assert_approx_eq!(tweenable.progress(), 0.3); tweenable.set_progress(1.); assert_eq!(tweenable.times_completed(), 1); - assert!(abs_diff_eq(tweenable.progress(), 1., 1e-5)); + assert_approx_eq!(tweenable.progress(), 1.); } #[test] @@ -1550,7 +1544,7 @@ mod tests { .with_repeat_count(RepeatCount::Finite(5)) .with_repeat_strategy(RepeatStrategy::Repeat); - assert!(abs_diff_eq(tween.progress(), 0., 1e-5)); + assert_approx_eq!(tween.progress(), 0.); let (mut world, entity, mut transform) = make_test_env(); let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> = @@ -1566,7 +1560,7 @@ mod tests { ); assert_eq!(TweenState::Active, state); assert_eq!(0, tween.times_completed()); - assert!(abs_diff_eq(tween.progress(), 0.1, 1e-5)); + assert_approx_eq!(tween.progress(), 0.1); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5)); // 130% @@ -1578,7 +1572,7 @@ mod tests { ); assert_eq!(TweenState::Active, state); assert_eq!(1, tween.times_completed()); - assert!(abs_diff_eq(tween.progress(), 0.3, 1e-5)); + assert_approx_eq!(tween.progress(), 0.3); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.3), 1e-5)); // 480% @@ -1590,7 +1584,7 @@ mod tests { ); assert_eq!(TweenState::Active, state); assert_eq!(4, tween.times_completed()); - assert!(abs_diff_eq(tween.progress(), 0.8, 1e-5)); + assert_approx_eq!(tween.progress(), 0.8); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5)); // 500% - done @@ -1602,7 +1596,7 @@ mod tests { ); assert_eq!(TweenState::Completed, state); assert_eq!(5, tween.times_completed()); - assert!(abs_diff_eq(tween.progress(), 1.0, 1e-5)); + assert_approx_eq!(tween.progress(), 1.0); assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5)); } @@ -1612,7 +1606,7 @@ mod tests { .with_repeat_count(RepeatCount::Finite(4)) .with_repeat_strategy(RepeatStrategy::MirroredRepeat); - assert!(abs_diff_eq(tween.progress(), 0., 1e-5)); + assert_approx_eq!(tween.progress(), 0.); let (mut world, entity, mut transform) = make_test_env(); let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> = @@ -1629,14 +1623,14 @@ mod tests { assert_eq!(TweenState::Active, state); assert_eq!(TweeningDirection::Forward, tween.direction()); assert_eq!(0, tween.times_completed()); - assert!(abs_diff_eq(tween.progress(), 0.1, 1e-5)); + assert_approx_eq!(tween.progress(), 0.1); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5)); // rewind tween.rewind(); assert_eq!(TweeningDirection::Forward, tween.direction()); assert_eq!(0, tween.times_completed()); - assert!(abs_diff_eq(tween.progress(), 0., 1e-5)); + assert_approx_eq!(tween.progress(), 0.); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5)); // no-op, rewind doesn't apply Lens // 120% - mirror @@ -1649,14 +1643,14 @@ mod tests { assert_eq!(TweeningDirection::Backward, tween.direction()); assert_eq!(TweenState::Active, state); assert_eq!(1, tween.times_completed()); - assert!(abs_diff_eq(tween.progress(), 0.2, 1e-5)); + assert_approx_eq!(tween.progress(), 0.2); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5)); // rewind tween.rewind(); assert_eq!(TweeningDirection::Forward, tween.direction()); // restored assert_eq!(0, tween.times_completed()); - assert!(abs_diff_eq(tween.progress(), 0., 1e-5)); + assert_approx_eq!(tween.progress(), 0.); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5)); // no-op, rewind doesn't apply Lens // 400% - done mirror (because Completed freezes the state) @@ -1669,14 +1663,14 @@ mod tests { assert_eq!(TweenState::Completed, state); assert_eq!(TweeningDirection::Backward, tween.direction()); // frozen from last loop assert_eq!(4, tween.times_completed()); - assert!(abs_diff_eq(tween.progress(), 1., 1e-5)); // Completed + assert_approx_eq!(tween.progress(), 1.); // Completed assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5)); // rewind tween.rewind(); assert_eq!(TweeningDirection::Forward, tween.direction()); // restored assert_eq!(0, tween.times_completed()); - assert!(abs_diff_eq(tween.progress(), 0., 1e-5)); + assert_approx_eq!(tween.progress(), 0.); assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5)); // no-op, rewind doesn't apply Lens } }