Skip to content
Snippets Groups Projects
tweenable.rs 71.9 KiB
Newer Older
    #[must_use]
    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())
    }

Jerome Humbert's avatar
Jerome Humbert committed
    fn set_elapsed(&mut self, elapsed: Duration) {
        // need to reset() to clear finished() unfortunately
        self.timer.reset();
Jerome Humbert's avatar
Jerome Humbert committed
        self.timer.set_elapsed(elapsed);
        // set_elapsed() does not update finished() etc. which we rely on
        self.timer.tick(Duration::ZERO);
    }

Jerome Humbert's avatar
Jerome Humbert committed
    fn elapsed(&self) -> Duration {
        self.timer.elapsed()
    }

        &mut self,
        delta: Duration,
        _target: &'a mut dyn Targetable<T>,
        entity: Entity,
        events: &mut Mut<Events<TweenCompleted>>,
    ) -> TweenState {
        let was_completed = self.is_completed();

        self.timer.tick(delta);

        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);
            }
        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;
Jerome Humbert's avatar
Jerome Humbert committed
            (total_duration.as_secs_f64() / duration.as_secs_f64()) as i32,
    /// 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),
Jerome Humbert's avatar
Jerome Humbert committed
                println!(
                    "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,
                                    )
                                }
                                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);
                        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());
        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));
    }

Jerome Humbert's avatar
Jerome Humbert committed
    #[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);
Jerome Humbert's avatar
Jerome Humbert committed
            assert_eq!(tween.times_completed(), times_completed);
        }
    }

    /// Test ticking a sequence of tweens.
    #[test]
    fn seq_tick() {
        let tween1 = Tween::new(
            EaseMethod::Linear,
            Duration::from_secs(1),
            TransformPositionLens {
                start: Vec3::ZERO,
                end: Vec3::ONE,
            },
        );
        let tween2 = Tween::new(
            EaseMethod::Linear,
            Duration::from_secs(1),
            TransformRotationLens {
                start: Quat::IDENTITY,
Jerome Humbert's avatar
Jerome Humbert committed
                end: Quat::from_rotation_x(90_f32.to_radians()),
            },
        );
        let mut seq = tween1.then(tween2);
        let (mut world, entity) = make_test_env();
        for i in 1..=16 {
            let state =
                manual_tick_component(Duration::from_millis(200), &mut seq, &mut world, entity);
            let transform = world.entity(entity).get::<Transform>().unwrap();
Jerome Humbert's avatar
Jerome Humbert committed
            if i < 5 {
                assert_eq!(state, TweenState::Active);
                let r = i as f32 * 0.2;
                assert_eq!(*transform, Transform::from_translation(Vec3::splat(r)));
Jerome Humbert's avatar
Jerome Humbert committed
            } else if i < 10 {
                assert_eq!(state, TweenState::Active);
Jerome Humbert's avatar
Jerome Humbert committed
                let alpha_deg = (18 * (i - 5)) as f32;
                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));
                assert!(transform
                    .rotation
Jerome Humbert's avatar
Jerome Humbert committed
                    .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),
        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);
    }

Jerome Humbert's avatar
Jerome Humbert committed
    /// 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));
    }

Jerome Humbert's avatar
Jerome Humbert committed
    #[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,
Jerome Humbert's avatar
Jerome Humbert committed
                end: Quat::from_rotation_x(90_f32.to_radians()),
            },
        );
        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();
        for i in 1..=6 {
            let state =
                manual_tick_component(Duration::from_millis(200), &mut tracks, &mut world, entity);
            let transform = world.entity(entity).get::<Transform>().unwrap();
Jerome Humbert's avatar
Jerome Humbert committed
            if i < 5 {
                assert_eq!(state, TweenState::Active);
                assert_eq!(tracks.times_completed(), 0);
                let r = i as f32 * 0.2;
                assert_approx_eq!(tracks.progress(), r);
Jerome Humbert's avatar
Jerome Humbert committed
                let alpha_deg = 22.5 * i as f32;
                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));
                assert!(transform
                    .rotation
Jerome Humbert's avatar
Jerome Humbert committed
                    .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.);
        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
    }

Jerome Humbert's avatar
Jerome Humbert committed
    /// 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() {
Jerome Humbert's avatar
Jerome Humbert committed
        let duration = Duration::from_secs(1);

        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);
        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);
            let state = manual_tick_component::<Transform>(
Jerome Humbert's avatar
Jerome Humbert committed
                Duration::from_millis(200),

            // 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());
Jerome Humbert's avatar
Jerome Humbert committed
                    assert_eq!(tweenable.times_completed(), 0);
                    assert_approx_eq!(tweenable.progress(), r);
                } else {
                    assert_eq!(state, TweenState::Completed);
                    assert!(delay.is_completed());
Jerome Humbert's avatar
Jerome Humbert committed
                    assert_eq!(tweenable.times_completed(), 1);
                    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);
Jerome Humbert's avatar
Jerome Humbert committed
        assert_eq!(state, TweenState::Active);

        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());
Jerome Humbert's avatar
Jerome Humbert committed
    #[test]
    fn delay_elapsed() {
        let mut delay: Delay<f32> = Delay::new(Duration::from_secs(1));
        let duration = delay.duration();
Jerome Humbert's avatar
Jerome Humbert committed
        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);
Jerome Humbert's avatar
Jerome Humbert committed

            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));