• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

djeedai / bevy_tweening / 15848700322

24 Jun 2025 11:03AM UTC coverage: 79.787% (-1.8%) from 81.619%
15848700322

Pull #151

github

web-flow
Merge ff3c87617 into 8483d9404
Pull Request #151: Add a param generic to Animator to allow multiple tweens over the same component

3 of 19 new or added lines in 2 files covered. (15.79%)

44 existing lines in 2 files now uncovered.

375 of 470 relevant lines covered (79.79%)

297909.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.05
/src/tweenable.rs
1
use std::{
2
    ops::{Deref, DerefMut},
3
    time::Duration,
4
};
5

6
use bevy::{ecs::system::SystemId, prelude::*};
7

8
use crate::{EaseMethod, Lens, RepeatCount, RepeatStrategy, TweeningDirection};
9

10
/// The dynamic tweenable type.
11
///
12
/// When creating lists of tweenables, you will need to box them to create a
13
/// homogeneous array like so:
14
/// ```no_run
15
/// # use bevy::prelude::Transform;
16
/// # use bevy_tweening::{BoxedTweenable, Delay, Sequence, Tween};
17
/// #
18
/// # let delay: Delay<Transform> = unimplemented!();
19
/// # let tween: Tween<Transform> = unimplemented!();
20
///
21
/// Sequence::new([Box::new(delay) as BoxedTweenable<Transform>, tween.into()]);
22
/// ```
23
///
24
/// When using your own [`Tweenable`] types, APIs will be easier to use if you
25
/// implement [`From`]:
26
/// ```no_run
27
/// # use std::time::Duration;
28
/// # use bevy::ecs::system::Commands;
29
/// # use bevy::prelude::{Entity, Events, Mut, Transform};
30
/// # use bevy_tweening::{BoxedTweenable, Sequence, Tweenable, TweenCompleted, TweenState, Targetable, TotalDuration};
31
/// #
32
/// # struct MyTweenable;
33
/// # impl Tweenable<Transform> for MyTweenable {
34
/// #     fn duration(&self) -> Duration  { unimplemented!() }
35
/// #     fn total_duration(&self) -> TotalDuration  { unimplemented!() }
36
/// #     fn set_elapsed(&mut self, elapsed: Duration)  { unimplemented!() }
37
/// #     fn elapsed(&self) -> Duration  { unimplemented!() }
38
/// #     fn tick<'a>(&mut self, delta: Duration, target: &'a mut dyn Targetable<Transform>, entity: Entity, events: &mut Mut<Events<TweenCompleted>>, commands: &mut Commands) -> TweenState  { unimplemented!() }
39
/// #     fn rewind(&mut self) { unimplemented!() }
40
/// # }
41
///
42
/// Sequence::new([Box::new(MyTweenable) as BoxedTweenable<_>]);
43
///
44
/// // OR
45
///
46
/// Sequence::new([MyTweenable]);
47
///
48
/// impl From<MyTweenable> for BoxedTweenable<Transform> {
49
///     fn from(t: MyTweenable) -> Self {
50
///         Box::new(t)
51
///     }
52
/// }
53
/// ```
54
pub type BoxedTweenable<T> = Box<dyn Tweenable<T> + 'static>;
55

56
/// Playback state of a [`Tweenable`].
57
///
58
/// This is returned by [`Tweenable::tick()`] to allow the caller to execute
59
/// some logic based on the updated state of the tweenable, like advanding a
60
/// sequence to its next child tweenable.
61
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62
pub enum TweenState {
63
    /// The tweenable is still active, and did not reach its end state yet.
64
    Active,
65
    /// Animation reached its end state. The tweenable is idling at its latest
66
    /// time.
67
    ///
68
    /// Note that [`RepeatCount::Infinite`] tweenables never reach this state.
69
    Completed,
70
}
71

72
/// Event raised when a tween completed.
73
///
74
/// This event is raised when a tween completed. When looping, this is raised
75
/// once per iteration. In case the animation direction changes
76
/// ([`RepeatStrategy::MirroredRepeat`]), an iteration corresponds to a single
77
/// progress from one endpoint to the other, whatever the direction. Therefore a
78
/// complete cycle start -> end -> start counts as 2 iterations and raises 2
79
/// events (one when reaching the end, one when reaching back the start).
80
///
81
/// # Note
82
///
83
/// The semantic is slightly different from [`TweenState::Completed`], which
84
/// indicates that the tweenable has finished ticking and do not need to be
85
/// updated anymore, a state which is never reached for looping animation. Here
86
/// the [`TweenCompleted`] event instead marks the end of a single loop
87
/// iteration.
88
#[derive(Copy, Clone, Event)]
89
pub struct TweenCompleted {
90
    /// The [`Entity`] the tween which completed and its animator are attached
91
    /// to.
92
    pub entity: Entity,
93
    /// An opaque value set by the user when activating event raising, used to
94
    /// identify the particular tween which raised this event. The value is
95
    /// passed unmodified from a call to [`with_completed_event()`]
96
    /// or [`set_completed_event()`].
97
    ///
98
    /// [`with_completed_event()`]: Tween::with_completed_event
99
    /// [`set_completed_event()`]: Tween::set_completed_event
100
    pub user_data: u64,
101
}
102

103
/// Calculate the progress fraction in \[0:1\] of the ratio between two
104
/// [`Duration`]s.
105
fn fraction_progress(n: Duration, d: Duration) -> f32 {
300✔
106
    // TODO - Replace with div_duration_f32() once it's stable
107
    (n.as_secs_f64() / d.as_secs_f64()).fract() as f32
300✔
108
}
109

110
#[derive(Debug)]
111
struct AnimClock {
112
    elapsed: Duration,
113
    duration: Duration,
114
    total_duration: TotalDuration,
115
    strategy: RepeatStrategy,
116
}
117

118
impl AnimClock {
119
    fn new(duration: Duration) -> Self {
52✔
120
        Self {
121
            elapsed: Duration::ZERO,
122
            duration,
123
            total_duration: compute_total_duration(duration, RepeatCount::default()),
52✔
124
            strategy: RepeatStrategy::default(),
52✔
125
        }
126
    }
127

128
    fn tick(&mut self, tick: Duration) -> (TweenState, i32) {
10,000,149✔
129
        self.set_elapsed(self.elapsed.saturating_add(tick))
10,000,149✔
130
    }
131

132
    fn times_completed(&self) -> u32 {
20,000,397✔
133
        (self.elapsed.as_nanos() / self.duration.as_nanos()) as u32
20,000,397✔
134
    }
135

136
    fn set_elapsed(&mut self, elapsed: Duration) -> (TweenState, i32) {
10,000,195✔
137
        let old_times_completed = self.times_completed();
10,000,195✔
138

139
        self.elapsed = elapsed;
10,000,195✔
140

141
        let state = match self.total_duration {
20,000,390✔
142
            TotalDuration::Finite(total_duration) => {
147✔
143
                if self.elapsed >= total_duration {
147✔
144
                    self.elapsed = total_duration;
39✔
145
                    TweenState::Completed
39✔
146
                } else {
147
                    TweenState::Active
108✔
148
                }
149
            }
150
            TotalDuration::Infinite => TweenState::Active,
10,000,048✔
151
        };
152

153
        (
154
            state,
10,000,195✔
155
            self.times_completed() as i32 - old_times_completed as i32,
10,000,195✔
156
        )
157
    }
158

159
    fn elapsed(&self) -> Duration {
489✔
160
        self.elapsed
489✔
161
    }
162

163
    fn state(&self) -> TweenState {
179✔
164
        match self.total_duration {
179✔
165
            TotalDuration::Finite(total_duration) => {
129✔
166
                if self.elapsed >= total_duration {
129✔
167
                    TweenState::Completed
26✔
168
                } else {
169
                    TweenState::Active
103✔
170
                }
171
            }
172
            TotalDuration::Infinite => TweenState::Active,
50✔
173
        }
174
    }
175

176
    fn reset(&mut self) {
31✔
177
        self.elapsed = Duration::ZERO;
31✔
178
    }
179
}
180

181
/// Possibly infinite duration of an animation.
182
///
183
/// Used to measure the total duration of an animation including any looping.
184
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185
pub enum TotalDuration {
186
    /// The duration is finite, of the given value.
187
    Finite(Duration),
188
    /// The duration is infinite.
189
    Infinite,
190
}
191

192
fn compute_total_duration(duration: Duration, count: RepeatCount) -> TotalDuration {
69✔
193
    match count {
69✔
194
        RepeatCount::Finite(times) => TotalDuration::Finite(duration.saturating_mul(times)),
64✔
195
        RepeatCount::For(duration) => TotalDuration::Finite(duration),
1✔
196
        RepeatCount::Infinite => TotalDuration::Infinite,
4✔
197
    }
198
}
199

200
// TODO - Targetable et al. should be replaced with Mut->Mut from Bevy 0.9
201
// https://github.com/bevyengine/bevy/pull/6199
202

203
/// Trait to workaround the discrepancies of the change detection mechanisms of
204
/// assets and components.
205
pub trait Targetable<T> {
206
    /// Dereference the target and return a reference.
207
    fn target(&self) -> &T;
208

209
    /// Dereference the target, triggering any change detection, and return a
210
    /// mutable reference.
211
    fn target_mut(&mut self) -> &mut T;
212
}
213

214
impl<'a, T: 'a> Deref for dyn Targetable<T> + 'a {
215
    type Target = T;
216

UNCOV
217
    fn deref(&self) -> &Self::Target {
×
UNCOV
218
        self.target()
×
219
    }
220
}
221

222
impl<'a, T: 'a> DerefMut for dyn Targetable<T> + 'a {
223
    fn deref_mut(&mut self) -> &mut Self::Target {
214✔
224
        self.target_mut()
214✔
225
    }
226
}
227

228
/// Implementation of [`Targetable`] for a [`Component`].
229
pub struct ComponentTarget<'a, T: Component> {
230
    target: Mut<'a, T>,
231
}
232

233
impl<'a, T: Component> ComponentTarget<'a, T> {
234
    /// Create a new instance from a [`Component`].
235
    pub fn new(target: Mut<'a, T>) -> Self {
223✔
236
        Self { target }
237
    }
238

239
    /// Get a [`Mut`] for the current target.
240
    #[allow(dead_code)] // used mainly in tests
241
    pub fn to_mut(&mut self) -> Mut<'_, T> {
8✔
242
        self.target.reborrow()
8✔
243
    }
244
}
245

246
impl<T: Component> Targetable<T> for ComponentTarget<'_, T> {
UNCOV
247
    fn target(&self) -> &T {
×
UNCOV
248
        self.target.deref()
×
249
    }
250

251
    fn target_mut(&mut self) -> &mut T {
204✔
252
        self.target.deref_mut()
204✔
253
    }
254
}
255

256
/// Implementation of [`Targetable`] for an [`Asset`].
257
#[cfg(feature = "bevy_asset")]
258
pub struct AssetTarget<'a, T: Asset> {
259
    assets: Mut<'a, Assets<T>>,
260
    /// Handle to the asset to mutate.
261
    pub handle: Handle<T>,
262
}
263

264
#[cfg(feature = "bevy_asset")]
265
impl<'a, T: Asset> AssetTarget<'a, T> {
266
    /// Create a new instance from an [`Assets`].
267
    pub fn new(assets: Mut<'a, Assets<T>>) -> Self {
10✔
268
        Self {
269
            assets,
270
            handle: Handle::Weak(AssetId::default()),
10✔
271
        }
272
    }
273

274
    /// Check if the current target is valid given the value of [`handle`].
275
    ///
276
    /// [`handle`]: AssetTarget::handle
UNCOV
277
    pub fn is_valid(&self) -> bool {
×
UNCOV
278
        self.assets.contains(&self.handle)
×
279
    }
280
}
281

282
#[cfg(feature = "bevy_asset")]
283
impl<T: Asset> Targetable<T> for AssetTarget<'_, T> {
UNCOV
284
    fn target(&self) -> &T {
×
UNCOV
285
        self.assets.get(&self.handle).unwrap()
×
286
    }
287

288
    fn target_mut(&mut self) -> &mut T {
10✔
289
        self.assets.get_mut(&self.handle).unwrap()
10✔
290
    }
291
}
292

293
/// An animatable entity, either a single [`Tween`] or a collection of them.
294
pub trait Tweenable<T>: Send + Sync {
295
    /// Get the duration of a single iteration of the animation.
296
    ///
297
    /// Note that for [`RepeatStrategy::MirroredRepeat`], this is the duration
298
    /// of a single way, either from start to end or back from end to start.
299
    /// The total "loop" duration start -> end -> start to reach back the
300
    /// same state in this case is the double of the returned value.
301
    fn duration(&self) -> Duration;
302

303
    /// Get the total duration of the entire animation, including looping.
304
    ///
305
    /// For [`TotalDuration::Finite`], this is the number of repeats times the
306
    /// duration of a single iteration ([`duration()`]).
307
    ///
308
    /// [`duration()`]: Tweenable::duration
309
    fn total_duration(&self) -> TotalDuration;
310

311
    /// Set the current animation playback elapsed time.
312
    ///
313
    /// See [`elapsed()`] for details on the meaning. If `elapsed` is greater
314
    /// than or equal to [`duration()`], then the animation completes.
315
    ///
316
    /// Setting the elapsed time seeks the animation to a new position, but does
317
    /// not apply that change to the underlying component being animated. To
318
    /// force the change to apply, call [`tick()`] with a `delta` of
319
    /// `Duration::ZERO`.
320
    ///
321
    /// [`elapsed()`]: Tweenable::elapsed
322
    /// [`duration()`]: Tweenable::duration
323
    /// [`tick()`]: Tweenable::tick
324
    fn set_elapsed(&mut self, elapsed: Duration);
325

326
    /// Get the current elapsed duration.
327
    ///
328
    /// While looping, the exact value returned by [`duration()`] is never
329
    /// reached, since the tweenable loops over to zero immediately when it
330
    /// changes direction at either endpoint. Upon completion, the tweenable
331
    /// always reports the same value as [`duration()`].
332
    ///
333
    /// [`duration()`]: Tweenable::duration
334
    fn elapsed(&self) -> Duration;
335

336
    /// Tick the animation, advancing it by the given delta time and mutating
337
    /// the given target component or asset.
338
    ///
339
    /// This returns [`TweenState::Active`] if the tweenable didn't reach its
340
    /// final state yet (progress < `1.0`), or [`TweenState::Completed`] if
341
    /// the tweenable completed this tick. Only non-looping tweenables return
342
    /// a completed state, since looping ones continue forever.
343
    ///
344
    /// Calling this method with a duration of [`Duration::ZERO`] is valid, and
345
    /// updates the target to the current state of the tweenable without
346
    /// actually modifying the tweenable state. This is useful after certain
347
    /// operations like [`rewind()`] or [`set_progress()`] whose effect is
348
    /// otherwise only visible on target on next frame.
349
    ///
350
    /// [`rewind()`]: Tweenable::rewind
351
    /// [`set_progress()`]: Tweenable::set_progress
352
    fn tick(
353
        &mut self,
354
        delta: Duration,
355
        target: &mut dyn Targetable<T>,
356
        entity: Entity,
357
        events: &mut Mut<Events<TweenCompleted>>,
358
        commands: &mut Commands,
359
    ) -> TweenState;
360

361
    /// Rewind the animation to its starting state.
362
    ///
363
    /// Note that the starting state depends on the current direction. For
364
    /// [`TweeningDirection::Forward`] this is the start point of the lens,
365
    /// whereas for [`TweeningDirection::Backward`] this is the end one.
366
    fn rewind(&mut self);
367

368
    /// Set the current animation playback progress.
369
    ///
370
    /// See [`progress()`] for details on the meaning.
371
    ///
372
    /// Setting the progress seeks the animation to a new position, but does not
373
    /// apply that change to the underlying component being animated. To
374
    /// force the change to apply, call [`tick()`] with a `delta` of
375
    /// `Duration::ZERO`.
376
    ///
377
    /// [`progress()`]: Tweenable::progress
378
    /// [`tick()`]: Tweenable::tick
379
    fn set_progress(&mut self, progress: f32) {
14✔
380
        self.set_elapsed(self.duration().mul_f32(progress.max(0.)));
14✔
381
    }
382

383
    /// Get the current progress in \[0:1\] of the animation.
384
    ///
385
    /// While looping, the exact value `1.0` is never reached, since the
386
    /// tweenable loops over to `0.0` immediately when it changes direction at
387
    /// either endpoint. Upon completion, the tweenable always reports exactly
388
    /// `1.0`.
389
    fn progress(&self) -> f32 {
332✔
390
        let elapsed = self.elapsed();
332✔
391
        if let TotalDuration::Finite(total_duration) = self.total_duration() {
568✔
UNCOV
392
            if elapsed >= total_duration {
×
393
                return 1.;
32✔
394
            }
395
        }
396
        fraction_progress(elapsed, self.duration())
300✔
397
    }
398

399
    /// Get the number of times this tweenable completed.
400
    ///
401
    /// For looping animations, this returns the number of times a single
402
    /// playback was completed. In the case of
403
    /// [`RepeatStrategy::MirroredRepeat`] this corresponds to a playback in
404
    /// a single direction, so tweening from start to end and back to start
405
    /// counts as two completed times (one forward, one backward).
406
    fn times_completed(&self) -> u32 {
204✔
407
        (self.elapsed().as_nanos() / self.duration().as_nanos()) as u32
204✔
408
    }
409
}
410

411
macro_rules! impl_boxed {
412
    ($tweenable:ty) => {
413
        impl<T: 'static> From<$tweenable> for BoxedTweenable<T> {
414
            fn from(t: $tweenable) -> Self {
13✔
415
                Box::new(t)
13✔
416
            }
417
        }
418
    };
419
}
420

421
impl_boxed!(Tween<T>);
422
impl_boxed!(Sequence<T>);
423
impl_boxed!(Tracks<T>);
424
impl_boxed!(Delay<T>);
425

426
/// Type of a callback invoked when a [`Tween`] or [`Delay`] has completed.
427
///
428
/// See [`Tween::set_completed()`] or [`Delay::set_completed()`] for usage.
429
pub type CompletedCallback<T> = dyn Fn(Entity, &T) + Send + Sync + 'static;
430

431
/// Single tweening animation instance.
432
pub struct Tween<T> {
433
    ease_function: EaseMethod,
434
    clock: AnimClock,
435
    direction: TweeningDirection,
436
    lens: Box<dyn Lens<T> + Send + Sync + 'static>,
437
    on_completed: Option<Box<CompletedCallback<Tween<T>>>>,
438
    event_data: Option<u64>,
439
    system_id: Option<SystemId>,
440
}
441

442
impl<T: 'static> Tween<T> {
443
    /// Chain another [`Tweenable`] after this tween, making a [`Sequence`] with
444
    /// the two.
445
    ///
446
    /// # Example
447
    /// ```
448
    /// # use bevy_tweening::{lens::*, *};
449
    /// # use bevy::math::{*,curve::EaseFunction};
450
    /// # use std::time::Duration;
451
    /// let tween1 = Tween::new(
452
    ///     EaseFunction::QuadraticInOut,
453
    ///     Duration::from_secs(1),
454
    ///     TransformPositionLens {
455
    ///         start: Vec3::ZERO,
456
    ///         end: Vec3::new(3.5, 0., 0.),
457
    ///     },
458
    /// );
459
    /// let tween2 = Tween::new(
460
    ///     EaseFunction::QuadraticInOut,
461
    ///     Duration::from_secs(1),
462
    ///     TransformRotationLens {
463
    ///         start: Quat::IDENTITY,
464
    ///         end: Quat::from_rotation_x(90.0_f32.to_radians()),
465
    ///     },
466
    /// );
467
    /// let seq = tween1.then(tween2);
468
    /// ```
469
    #[must_use]
470
    pub fn then(self, tween: impl Tweenable<T> + 'static) -> Sequence<T> {
1✔
471
        Sequence::with_capacity(2).then(self).then(tween)
1✔
472
    }
473
}
474

475
impl<T> Tween<T> {
476
    /// Create a new tween animation.
477
    ///
478
    /// # Example
479
    /// ```
480
    /// # use bevy_tweening::{lens::*, *};
481
    /// # use bevy::math::{Vec3, curve::EaseFunction};
482
    /// # use std::time::Duration;
483
    /// let tween = Tween::new(
484
    ///     EaseFunction::QuadraticInOut,
485
    ///     Duration::from_secs(1),
486
    ///     TransformPositionLens {
487
    ///         start: Vec3::ZERO,
488
    ///         end: Vec3::new(3.5, 0., 0.),
489
    ///     },
490
    /// );
491
    /// ```
492
    #[must_use]
493
    pub fn new<L>(ease_function: impl Into<EaseMethod>, duration: Duration, lens: L) -> Self
51✔
494
    where
495
        L: Lens<T> + Send + Sync + 'static,
496
    {
497
        Self {
498
            ease_function: ease_function.into(),
51✔
499
            clock: AnimClock::new(duration),
51✔
500
            direction: TweeningDirection::Forward,
501
            lens: Box::new(lens),
51✔
502
            on_completed: None,
503
            event_data: None,
504
            system_id: None,
505
        }
506
    }
507

508
    /// Enable raising a completed event.
509
    ///
510
    /// If enabled, the tween will raise a [`TweenCompleted`] event when the
511
    /// animation completed. This is similar to the [`with_completed()`]
512
    /// callback, but uses Bevy events instead.
513
    ///
514
    /// # Example
515
    ///
516
    /// ```
517
    /// # use bevy_tweening::{lens::*, *};
518
    /// # use bevy::{ecs::event::EventReader, math::{Vec3, curve::EaseFunction}};
519
    /// # use std::time::Duration;
520
    /// let tween = Tween::new(
521
    ///     // [...]
522
    /// #    EaseFunction::QuadraticInOut,
523
    /// #    Duration::from_secs(1),
524
    /// #    TransformPositionLens {
525
    /// #        start: Vec3::ZERO,
526
    /// #        end: Vec3::new(3.5, 0., 0.),
527
    /// #    },
528
    /// )
529
    /// .with_completed_event(42);
530
    ///
531
    /// fn my_system(mut reader: EventReader<TweenCompleted>) {
532
    ///   for ev in reader.read() {
533
    ///     assert_eq!(ev.user_data, 42);
534
    ///     println!("Entity {:?} raised TweenCompleted!", ev.entity);
535
    ///   }
536
    /// }
537
    /// ```
538
    ///
539
    /// [`with_completed()`]: Tween::with_completed
540
    #[must_use]
541
    pub fn with_completed_event(mut self, user_data: u64) -> Self {
3✔
542
        self.event_data = Some(user_data);
3✔
543
        self
3✔
544
    }
545

546
    /// Set a callback invoked when the delay completes.
547
    ///
548
    /// The callback when invoked receives as parameters the [`Entity`] on which
549
    /// the target and the animator are, as well as a reference to the
550
    /// current [`Tween`]. This is similar to [`with_completed_event()`], but
551
    /// with a callback instead.
552
    ///
553
    /// Only non-looping tweenables can complete.
554
    ///
555
    /// # Example
556
    ///
557
    /// ```
558
    /// # use bevy_tweening::{lens::*, *};
559
    /// # use bevy::{ecs::event::EventReader, math::{Vec3, curve::EaseFunction}};
560
    /// # use std::time::Duration;
561
    /// let tween = Tween::new(
562
    ///     // [...]
563
    /// #    EaseFunction::QuadraticInOut,
564
    /// #    Duration::from_secs(1),
565
    /// #    TransformPositionLens {
566
    /// #        start: Vec3::ZERO,
567
    /// #        end: Vec3::new(3.5, 0., 0.),
568
    /// #    },
569
    /// )
570
    /// .with_completed(|entity, _tween| {
571
    ///   println!("Tween completed on entity {:?}", entity);
572
    /// });
573
    /// ```
574
    ///
575
    /// [`with_completed_event()`]: Tween::with_completed_event
UNCOV
576
    pub fn with_completed<C>(mut self, callback: C) -> Self
×
577
    where
578
        C: Fn(Entity, &Self) + Send + Sync + 'static,
579
    {
UNCOV
580
        self.on_completed = Some(Box::new(callback));
×
UNCOV
581
        self
×
582
    }
583

584
    /// Enable running a one-shot system upon completion.
585
    ///
586
    /// If enabled, the tween will run a system via a provided [`SystemId`] when
587
    /// the animation completes. This is similar to the
588
    /// [`with_completed()`], but uses a system registered by
589
    /// [`register_system()`] instead of a callback.
590
    ///
591
    /// # Example
592
    ///
593
    /// ```
594
    /// # use bevy_tweening::{lens::*, *};
595
    /// # use bevy::{ecs::event::EventReader, math::{Vec3, curve::EaseFunction}, ecs::world::World, ecs::system::Query, ecs::entity::Entity, ecs::query::With};
596
    /// # use std::time::Duration;
597
    /// let mut world = World::new();
598
    /// let test_system_system_id = world.register_system(test_system);
599
    /// let tween = Tween::new(
600
    ///     // [...]
601
    /// #    EaseFunction::QuadraticInOut,
602
    /// #    Duration::from_secs(1),
603
    /// #    TransformPositionLens {
604
    /// #        start: Vec3::ZERO,
605
    /// #        end: Vec3::new(3.5, 0., 0.),
606
    /// #    },
607
    /// )
608
    /// .with_completed_system(test_system_system_id);
609
    ///
610
    /// fn test_system(query: Query<Entity>) {
611
    ///    for entity in query.iter() {
612
    ///       println!("Found an entity!");
613
    ///    }
614
    /// }
615
    /// ```
616
    /// [`with_completed()`]: Tween::with_completed
617
    /// [`register_system()`]: bevy::ecs::world::World::register_system
618
    #[must_use]
UNCOV
619
    pub fn with_completed_system(mut self, system_id: SystemId) -> Self {
×
UNCOV
620
        self.system_id = Some(system_id);
×
UNCOV
621
        self
×
622
    }
623

624
    /// Set the playback direction of the tween.
625
    ///
626
    /// The playback direction influences the mapping of the progress ratio (in
627
    /// \[0:1\]) to the actual ratio passed to the lens.
628
    /// [`TweeningDirection::Forward`] maps the `0` value of progress to the
629
    /// `0` value of the lens ratio. Conversely, [`TweeningDirection::Backward`]
630
    /// reverses the mapping, which effectively makes the tween play reversed,
631
    /// going from end to start.
632
    ///
633
    /// Changing the direction doesn't change any target state, nor any progress
634
    /// of the tween. Only the direction of animation from this moment
635
    /// potentially changes. To force a target state change, call
636
    /// [`Tweenable::tick()`] with a zero delta (`Duration::ZERO`).
637
    pub fn set_direction(&mut self, direction: TweeningDirection) {
5✔
638
        self.direction = direction;
5✔
639
    }
640

641
    /// Set the playback direction of the tween.
642
    ///
643
    /// See [`Tween::set_direction()`].
644
    #[must_use]
645
    pub fn with_direction(mut self, direction: TweeningDirection) -> Self {
10✔
646
        self.direction = direction;
10✔
647
        self
10✔
648
    }
649

650
    /// The current animation direction.
651
    ///
652
    /// See [`TweeningDirection`] for details.
653
    #[must_use]
654
    pub fn direction(&self) -> TweeningDirection {
139✔
655
        self.direction
139✔
656
    }
657

658
    /// Set the number of times to repeat the animation.
659
    #[must_use]
660
    pub fn with_repeat_count(mut self, count: impl Into<RepeatCount>) -> Self {
17✔
661
        self.clock.total_duration = compute_total_duration(self.clock.duration, count.into());
17✔
662
        self
17✔
663
    }
664

665
    /// Choose how the animation behaves upon a repetition.
666
    #[must_use]
667
    pub fn with_repeat_strategy(mut self, strategy: RepeatStrategy) -> Self {
12✔
668
        self.clock.strategy = strategy;
12✔
669
        self
12✔
670
    }
671

672
    /// Set a callback invoked when the animation completes.
673
    ///
674
    /// The callback when invoked receives as parameters the [`Entity`] on which
675
    /// the target and the animator are, as well as a reference to the
676
    /// current [`Tween`].
677
    ///
678
    /// Only non-looping tweenables can complete.
679
    pub fn set_completed<C>(&mut self, callback: C)
10✔
680
    where
681
        C: Fn(Entity, &Self) + Send + Sync + 'static,
682
    {
683
        self.on_completed = Some(Box::new(callback));
10✔
684
    }
685

686
    /// Clear the callback invoked when the animation completes.
687
    ///
688
    /// See also [`set_completed()`].
689
    ///
690
    /// [`set_completed()`]: Tween::set_completed
691
    pub fn clear_completed(&mut self) {
10✔
692
        self.on_completed = None;
10✔
693
    }
694

695
    /// Enable or disable raising a completed event.
696
    ///
697
    /// If enabled, the tween will raise a [`TweenCompleted`] event when the
698
    /// animation completed. This is similar to the [`set_completed()`]
699
    /// callback, but uses Bevy events instead.
700
    ///
701
    /// See [`with_completed_event()`] for details.
702
    ///
703
    /// [`set_completed()`]: Tween::set_completed
704
    /// [`with_completed_event()`]: Tween::with_completed_event
705
    pub fn set_completed_event(&mut self, user_data: u64) {
10✔
706
        self.event_data = Some(user_data);
10✔
707
    }
708

709
    /// Clear the event sent when the animation completes.
710
    ///
711
    /// See also [`set_completed_event()`].
712
    ///
713
    /// [`set_completed_event()`]: Tween::set_completed_event
714
    pub fn clear_completed_event(&mut self) {
10✔
715
        self.event_data = None;
10✔
716
    }
717

718
    /// Enable running a one-shot system upon completion.
719
    ///
720
    /// If enabled, the tween will run a system via a provided [`SystemId`] when
721
    /// the animation completes. This is similar to the
722
    /// [`with_completed()`], but uses a system registered by
723
    /// [`register_system()`] instead of a callback.
724
    ///
725
    /// [`with_completed()`]: Tween::with_completed
726
    /// [`register_system()`]: bevy::ecs::world::World::register_system
727
    pub fn set_completed_system(&mut self, user_data: SystemId) {
10✔
728
        self.system_id = Some(user_data);
10✔
729
    }
730

731
    /// Clear the system that will execute when the animation completes.
732
    ///
733
    /// See also [`set_completed_system()`].
734
    ///
735
    /// [`set_completed_system()`]: Tween::set_completed_system
736
    pub fn clear_completed_system(&mut self) {
10✔
737
        self.system_id = None;
10✔
738
    }
739
}
740

741
impl<T> Tweenable<T> for Tween<T> {
742
    fn duration(&self) -> Duration {
521✔
743
        self.clock.duration
521✔
744
    }
745

746
    fn total_duration(&self) -> TotalDuration {
311✔
747
        self.clock.total_duration
311✔
748
    }
749

750
    fn set_elapsed(&mut self, elapsed: Duration) {
46✔
751
        self.clock.set_elapsed(elapsed);
46✔
752
    }
753

754
    fn elapsed(&self) -> Duration {
489✔
755
        self.clock.elapsed()
489✔
756
    }
757

758
    fn tick(
172✔
759
        &mut self,
760
        delta: Duration,
761
        target: &mut dyn Targetable<T>,
762
        entity: Entity,
763
        events: &mut Mut<Events<TweenCompleted>>,
764
        commands: &mut Commands,
765
    ) -> TweenState {
766
        if self.clock.state() == TweenState::Completed {
172✔
767
            return TweenState::Completed;
23✔
768
        }
769

770
        // Tick the animation clock
771
        let (state, times_completed) = self.clock.tick(delta);
149✔
UNCOV
772
        let (progress, times_completed_for_direction) = match state {
×
773
            TweenState::Active => (self.progress(), times_completed),
134✔
774
            TweenState::Completed => (1., times_completed.max(1) - 1), // ignore last
15✔
775
        };
776
        if self.clock.strategy == RepeatStrategy::MirroredRepeat
×
777
            && times_completed_for_direction & 1 != 0
49✔
778
        {
779
            self.direction = !self.direction;
8✔
780
        }
781

782
        // Apply the lens, even if the animation finished, to ensure the state is
783
        // consistent
UNCOV
784
        let mut factor = progress;
×
785
        if self.direction.is_backward() {
55✔
786
            factor = 1. - factor;
55✔
787
        }
UNCOV
788
        let factor = self.ease_function.sample(factor);
×
UNCOV
789
        self.lens.lerp(target, factor);
×
790

791
        // If completed at least once this frame, notify the user
792
        if times_completed > 0 {
×
793
            if let Some(user_data) = &self.event_data {
49✔
794
                let event = TweenCompleted {
795
                    entity,
UNCOV
796
                    user_data: *user_data,
×
797
                };
798

799
                // send regular event
800
                events.send(event);
×
801

802
                // trigger all entity-scoped observers
UNCOV
803
                commands.trigger_targets(event, entity);
×
804
            }
805
            if let Some(cb) = &self.on_completed {
48✔
UNCOV
806
                cb(entity, self);
×
807
            }
808
            if let Some(system_id) = &self.system_id {
48✔
UNCOV
809
                commands.run_system(*system_id);
×
810
            }
811
        }
812

UNCOV
813
        state
×
814
    }
815

816
    fn rewind(&mut self) {
31✔
817
        if self.clock.strategy == RepeatStrategy::MirroredRepeat {
31✔
818
            // In mirrored mode, direction alternates each loop. To reset to the original
819
            // direction on Tween creation, we count the number of completions, ignoring the
820
            // last one if the Tween is currently in TweenState::Completed because that one
821
            // freezes all parameters.
822
            let mut times_completed = self.clock.times_completed();
7✔
823
            if self.clock.state() == TweenState::Completed {
7✔
824
                debug_assert!(times_completed > 0);
6✔
825
                times_completed -= 1;
3✔
826
            }
827
            if times_completed & 1 != 0 {
11✔
828
                self.direction = !self.direction;
4✔
829
            }
830
        }
831
        self.clock.reset();
31✔
832
    }
833
}
834

835
/// A sequence of tweens played back in order one after the other.
836
pub struct Sequence<T> {
837
    tweens: Vec<BoxedTweenable<T>>,
838
    index: usize,
839
    duration: Duration,
840
    elapsed: Duration,
841
}
842

843
impl<T> Sequence<T> {
844
    /// Create a new sequence of tweens.
845
    ///
846
    /// This method panics if the input collection is empty.
847
    #[must_use]
848
    pub fn new(items: impl IntoIterator<Item = impl Into<BoxedTweenable<T>>>) -> Self {
3✔
849
        let tweens: Vec<_> = items.into_iter().map(Into::into).collect();
3✔
850
        assert!(!tweens.is_empty());
3✔
851

852
        let duration = tweens
3✔
853
            .iter()
854
            .map(|tween| match tween.total_duration() {
14✔
855
                TotalDuration::Finite(duration) => duration,
11✔
UNCOV
856
                TotalDuration::Infinite => {
×
UNCOV
857
                    unimplemented!("Infinite durations are not supported in Sequence")
×
858
                }
859
            })
860
            .sum();
861

862
        Self {
863
            tweens,
864
            index: 0,
865
            duration,
866
            elapsed: Duration::ZERO,
867
        }
868
    }
869

870
    /// Create a new sequence containing a single tween.
871
    #[must_use]
872
    pub fn from_single(tween: impl Tweenable<T> + 'static) -> Self {
1✔
873
        let duration = tween.duration();
1✔
874
        let boxed: BoxedTweenable<T> = Box::new(tween);
1✔
875
        Self {
876
            tweens: vec![boxed],
1✔
877
            index: 0,
878
            duration,
879
            elapsed: Duration::ZERO,
880
        }
881
    }
882

883
    /// Create a new sequence with the specified capacity.
884
    #[must_use]
885
    pub fn with_capacity(capacity: usize) -> Self {
2✔
886
        Self {
887
            tweens: Vec::with_capacity(capacity),
2✔
888
            index: 0,
889
            duration: Duration::ZERO,
890
            elapsed: Duration::ZERO,
891
        }
892
    }
893

894
    /// Append a [`Tweenable`] to this sequence.
895
    #[must_use]
896
    pub fn then(mut self, tween: impl Tweenable<T> + 'static) -> Self {
4✔
897
        self.duration += tween.duration();
4✔
898
        self.tweens.push(Box::new(tween));
4✔
899
        self
4✔
900
    }
901

902
    /// Index of the current active tween in the sequence.
903
    #[must_use]
904
    pub fn index(&self) -> usize {
17✔
905
        self.index.min(self.tweens.len() - 1)
17✔
906
    }
907

908
    /// Get the current active tween in the sequence.
909
    #[must_use]
910
    pub fn current(&self) -> &dyn Tweenable<T> {
8✔
911
        self.tweens[self.index()].as_ref()
8✔
912
    }
913
}
914

915
impl<T> Tweenable<T> for Sequence<T> {
916
    fn duration(&self) -> Duration {
20✔
917
        self.duration
20✔
918
    }
919

920
    fn total_duration(&self) -> TotalDuration {
5✔
921
        TotalDuration::Finite(self.duration)
5✔
922
    }
923

924
    fn set_elapsed(&mut self, elapsed: Duration) {
8✔
925
        // Set the total sequence progress
926
        self.elapsed = elapsed;
8✔
927

928
        // Find which tween is active in the sequence
929
        let mut accum_duration = Duration::ZERO;
8✔
930
        for index in 0..self.tweens.len() {
34✔
931
            let tween = &mut self.tweens[index];
26✔
932
            let tween_duration = tween.duration();
26✔
933
            if elapsed < accum_duration + tween_duration {
26✔
934
                self.index = index;
6✔
935
                let local_duration = elapsed - accum_duration;
6✔
936
                tween.set_elapsed(local_duration);
6✔
937
                // TODO?? set progress of other tweens after that one to 0. ??
938
                return;
6✔
939
            }
940
            tween.set_elapsed(tween.duration()); // ?? to prepare for next loop/rewind?
20✔
941
            accum_duration += tween_duration;
20✔
942
        }
943

944
        // None found; sequence ended
945
        self.index = self.tweens.len();
2✔
946
    }
947

948
    fn elapsed(&self) -> Duration {
18✔
949
        self.elapsed
18✔
950
    }
951

952
    fn tick(
18✔
953
        &mut self,
954
        mut delta: Duration,
955
        target: &mut dyn Targetable<T>,
956
        entity: Entity,
957
        events: &mut Mut<Events<TweenCompleted>>,
958
        commands: &mut Commands,
959
    ) -> TweenState {
960
        self.elapsed = self.elapsed.saturating_add(delta).min(self.duration);
18✔
961
        while self.index < self.tweens.len() {
22✔
962
            let tween = &mut self.tweens[self.index];
15✔
963

964
            let tween_remaining = tween.duration().saturating_sub(tween.elapsed());
15✔
965

966
            if let TweenState::Active = tween.tick(delta, target, entity, events, commands) {
15✔
967
                return TweenState::Active;
11✔
968
            }
969

970
            tween.rewind();
4✔
971
            delta -= tween_remaining;
4✔
972
            self.index += 1;
4✔
973
        }
974

975
        TweenState::Completed
7✔
976
    }
977

978
    fn rewind(&mut self) {
1✔
979
        self.elapsed = Duration::ZERO;
1✔
980
        self.index = 0;
1✔
981
        for tween in &mut self.tweens {
9✔
982
            // or only first?
UNCOV
983
            tween.rewind();
×
984
        }
985
    }
986
}
987

988
/// A collection of [`Tweenable`] executing in parallel.
989
pub struct Tracks<T> {
990
    tracks: Vec<BoxedTweenable<T>>,
991
    duration: Duration,
992
    elapsed: Duration,
993
}
994

995
impl<T> Tracks<T> {
996
    /// Create a new [`Tracks`] from an iterator over a collection of
997
    /// [`Tweenable`].
998
    #[must_use]
999
    pub fn new(items: impl IntoIterator<Item = impl Into<BoxedTweenable<T>>>) -> Self {
1✔
1000
        let tracks: Vec<_> = items.into_iter().map(Into::into).collect();
1✔
1001
        let duration = tracks
1✔
1002
            .iter()
1003
            .map(AsRef::as_ref)
1✔
1004
            .map(Tweenable::duration)
1✔
1005
            .max()
1006
            .unwrap();
1007
        Self {
1008
            tracks,
1009
            duration,
1010
            elapsed: Duration::ZERO,
1011
        }
1012
    }
1013
}
1014

1015
impl<T> Tweenable<T> for Tracks<T> {
1016
    fn duration(&self) -> Duration {
21✔
1017
        self.duration
21✔
1018
    }
1019

1020
    fn total_duration(&self) -> TotalDuration {
10✔
1021
        TotalDuration::Finite(self.duration)
10✔
1022
    }
1023

1024
    fn set_elapsed(&mut self, elapsed: Duration) {
3✔
1025
        self.elapsed = elapsed;
3✔
1026

1027
        for tweenable in &mut self.tracks {
15✔
UNCOV
1028
            tweenable.set_elapsed(elapsed);
×
1029
        }
1030
    }
1031

1032
    fn elapsed(&self) -> Duration {
20✔
1033
        self.elapsed
20✔
1034
    }
1035

1036
    fn tick(
9✔
1037
        &mut self,
1038
        delta: Duration,
1039
        target: &mut dyn Targetable<T>,
1040
        entity: Entity,
1041
        events: &mut Mut<Events<TweenCompleted>>,
1042
        commands: &mut Commands,
1043
    ) -> TweenState {
1044
        self.elapsed = self.elapsed.saturating_add(delta).min(self.duration);
9✔
1045
        let mut any_active = false;
9✔
1046
        for tweenable in &mut self.tracks {
45✔
UNCOV
1047
            let state = tweenable.tick(delta, target, entity, events, commands);
×
1048
            any_active = any_active || (state == TweenState::Active);
12✔
1049
        }
1050
        if any_active {
9✔
1051
            TweenState::Active
6✔
1052
        } else {
1053
            TweenState::Completed
3✔
1054
        }
1055
    }
1056

1057
    fn rewind(&mut self) {
1✔
1058
        self.elapsed = Duration::ZERO;
1✔
1059
        for tween in &mut self.tracks {
5✔
UNCOV
1060
            tween.rewind();
×
1061
        }
1062
    }
1063
}
1064

1065
/// A time delay that doesn't animate anything.
1066
///
1067
/// This is generally useful for combining with other tweenables into sequences
1068
/// and tracks, for example to delay the start of a tween in a track relative to
1069
/// another track. The `menu` example (`examples/menu.rs`) uses this technique
1070
/// to delay the animation of its buttons.
1071
pub struct Delay<T> {
1072
    timer: Timer,
1073
    on_completed: Option<Box<CompletedCallback<Delay<T>>>>,
1074
    event_data: Option<u64>,
1075
    system_id: Option<SystemId>,
1076
}
1077

1078
impl<T: 'static> Delay<T> {
1079
    /// Chain another [`Tweenable`] after this tween, making a [`Sequence`] with
1080
    /// the two.
1081
    #[must_use]
1082
    pub fn then(self, tween: impl Tweenable<T> + 'static) -> Sequence<T> {
1✔
1083
        Sequence::with_capacity(2).then(self).then(tween)
1✔
1084
    }
1085
}
1086

1087
impl<T> Delay<T> {
1088
    /// Create a new [`Delay`] with a given duration.
1089
    ///
1090
    /// # Panics
1091
    ///
1092
    /// Panics if the duration is zero.
1093
    #[must_use]
1094
    pub fn new(duration: Duration) -> Self {
5✔
1095
        assert!(!duration.is_zero());
5✔
1096
        Self {
1097
            timer: Timer::new(duration, TimerMode::Once),
4✔
1098
            on_completed: None,
1099
            event_data: None,
1100
            system_id: None,
1101
        }
1102
    }
1103

1104
    /// Enable raising a completed event.
1105
    ///
1106
    /// If enabled, the tweenable will raise a [`TweenCompleted`] event when it
1107
    /// completed. This is similar to the [`set_completed()`] callback, but
1108
    /// uses Bevy events instead.
1109
    ///
1110
    /// # Example
1111
    ///
1112
    /// ```
1113
    /// # use bevy_tweening::{lens::*, *};
1114
    /// # use bevy::{ecs::event::EventReader, math::Vec3, transform::components::Transform};
1115
    /// # use std::time::Duration;
1116
    /// let delay: Delay<Transform> = Delay::new(Duration::from_secs(5))
1117
    ///   .with_completed_event(42);
1118
    ///
1119
    /// fn my_system(mut reader: EventReader<TweenCompleted>) {
1120
    ///   for ev in reader.read() {
1121
    ///     assert_eq!(ev.user_data, 42);
1122
    ///     println!("Entity {:?} raised TweenCompleted!", ev.entity);
1123
    ///   }
1124
    /// }
1125
    /// ```
1126
    ///
1127
    /// [`set_completed()`]: Delay::set_completed
1128
    #[must_use]
1129
    pub fn with_completed_event(mut self, user_data: u64) -> Self {
1✔
1130
        self.event_data = Some(user_data);
1✔
1131
        self
1✔
1132
    }
1133

1134
    /// Set a callback invoked when the delay completes.
1135
    ///
1136
    /// The callback when invoked receives as parameters the [`Entity`] on which
1137
    /// the target and the animator are, as well as a reference to the
1138
    /// current [`Delay`]. This is similar to [`with_completed_event()`], but
1139
    /// with a callback instead.
1140
    ///
1141
    /// Only non-looping tweenables can complete.
1142
    ///
1143
    /// # Example
1144
    ///
1145
    /// ```
1146
    /// # use bevy_tweening::{lens::*, *};
1147
    /// # use bevy::{ecs::event::EventReader, math::{Vec3, curve::EaseFunction}};
1148
    /// # use std::time::Duration;
1149
    /// let tween = Tween::new(
1150
    ///     // [...]
1151
    /// #    EaseFunction::QuadraticInOut,
1152
    /// #    Duration::from_secs(1),
1153
    /// #    TransformPositionLens {
1154
    /// #        start: Vec3::ZERO,
1155
    /// #        end: Vec3::new(3.5, 0., 0.),
1156
    /// #    },
1157
    /// )
1158
    /// .with_completed(|entity, delay| {
1159
    ///   println!("Delay of {} seconds elapsed on entity {:?}",
1160
    ///     delay.duration().as_secs(), entity);
1161
    /// });
1162
    /// ```
1163
    ///
1164
    /// [`with_completed_event()`]: Delay::with_completed_event
UNCOV
1165
    pub fn with_completed<C>(mut self, callback: C) -> Self
×
1166
    where
1167
        C: Fn(Entity, &Self) + Send + Sync + 'static,
1168
    {
UNCOV
1169
        self.on_completed = Some(Box::new(callback));
×
UNCOV
1170
        self
×
1171
    }
1172

1173
    /// Enable running a one-shot system upon completion.
1174
    ///
1175
    /// If enabled, the tween will run a system via a provided [`SystemId`] when
1176
    /// the animation completes. This is similar to the
1177
    /// [`with_completed()`], but uses a system registered by
1178
    /// [`register_system()`] instead.
1179
    ///
1180
    /// # Example
1181
    ///
1182
    /// ```
1183
    /// # use bevy_tweening::{lens::*, *};
1184
    /// # use bevy::{ecs::event::EventReader, math::{Vec3, curve::EaseFunction}, ecs::world::World, ecs::system::Query, ecs::entity::Entity};
1185
    /// # use std::time::Duration;
1186
    /// let mut world = World::new();
1187
    /// let test_system_system_id = world.register_system(test_system);
1188
    /// let tween = Tween::new(
1189
    ///     // [...]
1190
    /// #    EaseFunction::QuadraticInOut,
1191
    /// #    Duration::from_secs(1),
1192
    /// #    TransformPositionLens {
1193
    /// #        start: Vec3::ZERO,
1194
    /// #        end: Vec3::new(3.5, 0., 0.),
1195
    /// #    },
1196
    /// )
1197
    /// .with_completed_system(test_system_system_id);
1198
    ///
1199
    /// fn test_system(query: Query<Entity>) {
1200
    ///    for entity in query.iter() {
1201
    ///       println!("Found an Entity!");
1202
    ///    }
1203
    /// }
1204
    /// ```
1205
    ///
1206
    /// [`with_completed()`]: Delay::with_completed
1207
    /// [`register_system()`]: bevy::ecs::world::World::register_system
1208
    #[must_use]
1209
    pub fn with_completed_system(mut self, system_id: SystemId) -> Self {
1✔
1210
        self.system_id = Some(system_id);
1✔
1211
        self
1✔
1212
    }
1213

1214
    /// Check if the delay completed.
1215
    pub fn is_completed(&self) -> bool {
44✔
1216
        self.timer.finished()
44✔
1217
    }
1218

1219
    /// Get the current tweenable state.
1220
    pub fn state(&self) -> TweenState {
22✔
1221
        if self.is_completed() {
22✔
1222
            TweenState::Completed
5✔
1223
        } else {
1224
            TweenState::Active
17✔
1225
        }
1226
    }
1227

1228
    /// Set a callback invoked when the animation completes.
1229
    ///
1230
    /// The callback when invoked receives as parameters the [`Entity`] on which
1231
    /// the target and the animator are, as well as a reference to the
1232
    /// current [`Tween`].
1233
    ///
1234
    /// Only non-looping tweenables can complete.
1235
    pub fn set_completed<C>(&mut self, callback: C)
1✔
1236
    where
1237
        C: Fn(Entity, &Self) + Send + Sync + 'static,
1238
    {
1239
        self.on_completed = Some(Box::new(callback));
1✔
1240
    }
1241

1242
    /// Clear the callback invoked when the animation completes.
1243
    ///
1244
    /// See also [`set_completed()`].
1245
    ///
1246
    /// [`set_completed()`]: Delay::set_completed
1247
    pub fn clear_completed(&mut self) {
1✔
1248
        self.on_completed = None;
1✔
1249
    }
1250

1251
    /// Enable or disable raising a completed event.
1252
    ///
1253
    /// If enabled, the tween will raise a [`TweenCompleted`] event when the
1254
    /// animation completed. This is similar to the [`set_completed()`]
1255
    /// callback, but uses Bevy events instead.
1256
    ///
1257
    /// See [`with_completed_event()`] for details.
1258
    ///
1259
    /// [`set_completed()`]: Delay::set_completed
1260
    /// [`with_completed_event()`]: Delay::with_completed_event
1261
    pub fn set_completed_event(&mut self, user_data: u64) {
1✔
1262
        self.event_data = Some(user_data);
1✔
1263
    }
1264

1265
    /// Clear the event sent when the animation completes.
1266
    ///
1267
    /// See also [`set_completed_event()`].
1268
    ///
1269
    /// [`set_completed_event()`]: Delay::set_completed_event
1270
    pub fn clear_completed_event(&mut self) {
2✔
1271
        self.event_data = None;
2✔
1272
    }
1273

1274
    /// Enable running a one-shot system upon completion.
1275
    ///
1276
    /// If enabled, the tween will run a system via a provided [`SystemId`] when
1277
    /// the animation completes. This is similar to the
1278
    /// [`with_completed()`], but uses a system registered by
1279
    /// [`register_system()`] instead of a callback.
1280
    ///
1281
    /// See [`with_completed_system()`] for details.
1282
    ///
1283
    /// [`with_completed()`]: Delay::with_completed
1284
    /// [`with_completed_system()`]: Delay::with_completed_system
1285
    /// [`register_system()`]: bevy::ecs::world::World::register_system
1286
    pub fn set_completed_system(&mut self, system_id: SystemId) {
1✔
1287
        self.system_id = Some(system_id);
1✔
1288
    }
1289

1290
    /// Clear the system that will execute when the animation completes.
1291
    ///
1292
    /// See also [`set_completed_system()`].
1293
    ///
1294
    /// [`set_completed_system()`]: Delay::set_completed_system
1295
    pub fn clear_completed_system(&mut self) {
1✔
1296
        self.system_id = None;
1✔
1297
    }
1298
}
1299

1300
impl<T> Tweenable<T> for Delay<T> {
1301
    fn duration(&self) -> Duration {
61✔
1302
        self.timer.duration()
61✔
1303
    }
1304

1305
    fn total_duration(&self) -> TotalDuration {
19✔
1306
        TotalDuration::Finite(self.duration())
19✔
1307
    }
1308

1309
    fn set_elapsed(&mut self, elapsed: Duration) {
11✔
1310
        // need to reset() to clear finished() unfortunately
1311
        self.timer.reset();
11✔
1312
        self.timer.set_elapsed(elapsed);
11✔
1313
        // set_elapsed() does not update finished() etc. which we rely on
1314
        self.timer.tick(Duration::ZERO);
11✔
1315
    }
1316

1317
    fn elapsed(&self) -> Duration {
48✔
1318
        self.timer.elapsed()
48✔
1319
    }
1320

1321
    fn tick(
7✔
1322
        &mut self,
1323
        delta: Duration,
1324
        _target: &mut dyn Targetable<T>,
1325
        entity: Entity,
1326
        events: &mut Mut<Events<TweenCompleted>>,
1327
        commands: &mut Commands,
1328
    ) -> TweenState {
1329
        let was_completed = self.is_completed();
7✔
1330

1331
        self.timer.tick(delta);
7✔
1332

1333
        let state = self.state();
7✔
1334

1335
        // If completed this frame, notify the user
1336
        if (state == TweenState::Completed) && !was_completed {
9✔
1337
            if let Some(user_data) = &self.event_data {
2✔
1338
                let event = TweenCompleted {
1339
                    entity,
UNCOV
1340
                    user_data: *user_data,
×
1341
                };
1342

1343
                // send regular event
UNCOV
1344
                events.send(event);
×
1345

1346
                // trigger all entity-scoped observers
UNCOV
1347
                commands.trigger_targets(event, entity);
×
1348
            }
1349
            if let Some(cb) = &self.on_completed {
2✔
UNCOV
1350
                cb(entity, self);
×
1351
            }
1352
            if let Some(system_id) = &self.system_id {
2✔
UNCOV
1353
                commands.run_system(*system_id);
×
1354
            }
1355
        }
1356

1357
        state
7✔
1358
    }
1359

1360
    fn rewind(&mut self) {
1✔
1361
        self.timer.reset();
1✔
1362
    }
1363
}
1364

1365
#[cfg(test)]
1366
mod tests {
1367
    use std::sync::{Arc, Mutex};
1368

1369
    use bevy::ecs::{
1370
        change_detection::MaybeLocation,
1371
        component::{Mutable, Tick},
1372
        event::Events,
1373
        system::SystemState,
1374
        world::CommandQueue,
1375
    };
1376

1377
    use super::*;
1378
    use crate::{lens::*, test_utils::*};
1379

1380
    #[derive(Default, Copy, Clone)]
1381
    struct CallbackMonitor {
1382
        invoke_count: u64,
1383
        last_reported_count: u32,
1384
    }
1385

1386
    /// Utility to create a tween for testing.
1387
    fn make_test_tween() -> Tween<Transform> {
1388
        Tween::new(
1389
            EaseMethod::default(),
1390
            Duration::from_secs(1),
1391
            TransformPositionLens {
1392
                start: Vec3::ZERO,
1393
                end: Vec3::ONE,
1394
            },
1395
        )
1396
    }
1397

1398
    /// Utility to create a test environment to tick a tween.
1399
    fn make_test_env() -> (World, Entity, SystemId) {
1400
        let mut world = World::new();
1401
        world.init_resource::<Events<TweenCompleted>>();
1402
        let entity = world.spawn(Transform::default()).id();
1403
        let system_id = world.register_system(oneshot_test);
1404
        (world, entity, system_id)
1405
    }
1406

1407
    /// one-shot system to be used for testing
1408
    fn oneshot_test() {}
1409

1410
    /// Manually tick a test tweenable targeting a component.
1411
    fn manual_tick_component<T: Component<Mutability = Mutable>>(
1412
        duration: Duration,
1413
        tween: &mut dyn Tweenable<T>,
1414
        world: &mut World,
1415
        entity: Entity,
1416
    ) -> TweenState {
1417
        world.resource_scope(
1418
            |world: &mut World, mut events: Mut<Events<TweenCompleted>>| {
1419
                let transform = world.get_mut::<T>(entity).unwrap();
1420
                let mut target = ComponentTarget::new(transform);
1421
                // let command_queue = &mut CommandQueue::default(); // todo
1422
                // let mut asd = Commands::new(command_queue, world);
1423
                tween.tick(
1424
                    duration,
1425
                    &mut target,
1426
                    entity,
1427
                    &mut events,
1428
                    // passing dummy values to let things compile
1429
                    &mut Commands::new(&mut CommandQueue::default(), &World::default()),
1430
                )
1431
            },
1432
        )
1433
    }
1434

1435
    #[derive(Debug, Default, Clone, Copy, Component)]
1436
    struct DummyComponent {
1437
        _value: f32,
1438
    }
1439

1440
    #[test]
1441
    fn targetable_change_detect() {
1442
        let mut c = DummyComponent::default();
1443
        let mut added = Tick::new(0);
1444
        let mut last_changed = Tick::new(0);
1445
        let mut caller = MaybeLocation::caller();
1446
        let mut target = ComponentTarget::new(Mut::new(
1447
            &mut c,
1448
            &mut added,
1449
            &mut last_changed,
1450
            Tick::new(0),
1451
            Tick::new(1),
1452
            caller.as_mut(),
1453
        ));
1454
        let mut target = target.to_mut();
1455

1456
        // No-op at start
1457
        assert!(!target.is_added());
1458
        assert!(!target.is_changed());
1459

1460
        // Immutable deref doesn't trigger change detection
1461
        let _ = target.deref();
1462
        assert!(!target.is_added());
1463
        assert!(!target.is_changed());
1464

1465
        // Mutable deref triggers change detection
1466
        let _ = target.deref_mut();
1467
        assert!(!target.is_added());
1468
        assert!(target.is_changed());
1469
    }
1470

1471
    #[test]
1472
    fn anim_clock_precision() {
1473
        let duration = Duration::from_millis(1);
1474
        let mut clock = AnimClock::new(duration);
1475
        clock.total_duration = TotalDuration::Infinite;
1476

1477
        let test_ticks = [
1478
            Duration::from_micros(123),
1479
            Duration::from_millis(1),
1480
            Duration::from_secs_f32(1. / 24.),
1481
            Duration::from_secs_f32(1. / 30.),
1482
            Duration::from_secs_f32(1. / 60.),
1483
            Duration::from_secs_f32(1. / 120.),
1484
            Duration::from_secs_f32(1. / 144.),
1485
            Duration::from_secs_f32(1. / 240.),
1486
        ];
1487

1488
        let mut times_completed = 0;
1489
        let mut total_duration = Duration::ZERO;
1490
        for i in 0..10_000_000 {
1491
            let tick = test_ticks[i % test_ticks.len()];
1492
            times_completed += clock.tick(tick).1;
1493
            total_duration += tick;
1494
        }
1495

1496
        assert_eq!(
1497
            (total_duration.as_secs_f64() / duration.as_secs_f64()) as i32,
1498
            times_completed
1499
        );
1500
    }
1501

1502
    #[test]
1503
    fn into_repeat_count() {
1504
        let tween = Tween::new(
1505
            EaseMethod::default(),
1506
            Duration::from_secs(1),
1507
            TransformPositionLens {
1508
                start: Vec3::ZERO,
1509
                end: Vec3::ONE,
1510
            },
1511
        )
1512
        .with_repeat_count(5);
1513
        assert_eq!(
1514
            tween.total_duration(),
1515
            TotalDuration::Finite(Duration::from_secs(5))
1516
        );
1517

1518
        let tween = Tween::new(
1519
            EaseMethod::default(),
1520
            Duration::from_secs(1),
1521
            TransformPositionLens {
1522
                start: Vec3::ZERO,
1523
                end: Vec3::ONE,
1524
            },
1525
        )
1526
        .with_repeat_count(Duration::from_secs(3));
1527
        assert_eq!(
1528
            tween.total_duration(),
1529
            TotalDuration::Finite(Duration::from_secs(3))
1530
        );
1531
    }
1532

1533
    /// Test ticking of a single tween in isolation.
1534
    #[test]
1535
    fn tween_tick() {
1536
        for tweening_direction in &[TweeningDirection::Forward, TweeningDirection::Backward] {
1537
            for (count, strategy) in &[
1538
                (RepeatCount::Finite(1), RepeatStrategy::default()),
1539
                (RepeatCount::Infinite, RepeatStrategy::Repeat),
1540
                (RepeatCount::Finite(2), RepeatStrategy::Repeat),
1541
                (RepeatCount::Infinite, RepeatStrategy::MirroredRepeat),
1542
                (RepeatCount::Finite(2), RepeatStrategy::MirroredRepeat),
1543
            ] {
1544
                println!(
1545
                    "TweeningType: count={count:?} strategy={strategy:?} dir={tweening_direction:?}",
1546
                );
1547

1548
                // Create a linear tween over 1 second
1549
                let mut tween = make_test_tween()
1550
                    .with_direction(*tweening_direction)
1551
                    .with_repeat_count(*count)
1552
                    .with_repeat_strategy(*strategy);
1553
                assert_eq!(tween.direction(), *tweening_direction);
1554
                assert!(tween.on_completed.is_none());
1555
                assert!(tween.event_data.is_none());
1556

1557
                let (mut world, entity, system_id) = make_test_env();
1558
                let mut event_reader_system_state: SystemState<EventReader<TweenCompleted>> =
1559
                    SystemState::new(&mut world);
1560

1561
                // Register callbacks to count started/ended events
1562
                let callback_monitor = Arc::new(Mutex::new(CallbackMonitor::default()));
1563
                let cb_mon_ptr = Arc::clone(&callback_monitor);
1564
                let reference_entity = entity;
1565
                tween.set_completed(move |completed_entity, tween| {
1566
                    assert_eq!(completed_entity, reference_entity);
1567
                    let mut cb_mon = cb_mon_ptr.lock().unwrap();
1568
                    cb_mon.invoke_count += 1;
1569
                    cb_mon.last_reported_count = tween.times_completed();
1570
                });
1571
                assert!(tween.on_completed.is_some());
1572
                assert!(tween.event_data.is_none());
1573
                assert_eq!(callback_monitor.lock().unwrap().invoke_count, 0);
1574

1575
                // Activate event sending
1576
                const USER_DATA: u64 = 54789; // dummy
1577
                tween.set_completed_event(USER_DATA);
1578
                assert!(tween.event_data.is_some());
1579
                assert_eq!(tween.event_data.unwrap(), USER_DATA);
1580

1581
                // Activate oneshot system
1582
                tween.set_completed_system(system_id);
1583
                assert!(tween.system_id.is_some());
1584
                assert_eq!(tween.system_id.unwrap(), system_id);
1585

1586
                // Loop over 2.2 seconds, so greater than one ping-pong loop
1587
                let tick_duration = Duration::from_millis(200);
1588
                for i in 1..=11 {
1589
                    // Calculate expected values
1590
                    let (progress, times_completed, mut direction, expected_state, just_completed) =
1591
                        match count {
1592
                            RepeatCount::Finite(1) => {
1593
                                let progress = (i as f32 * 0.2).min(1.0);
1594
                                let times_completed = u32::from(i >= 5);
1595
                                let state = if i < 5 {
1596
                                    TweenState::Active
1597
                                } else {
1598
                                    TweenState::Completed
1599
                                };
1600
                                let just_completed = i == 5;
1601
                                (
1602
                                    progress,
1603
                                    times_completed,
1604
                                    TweeningDirection::Forward,
1605
                                    state,
1606
                                    just_completed,
1607
                                )
1608
                            }
1609
                            RepeatCount::Finite(count) => {
1610
                                let total_progress = i as f32 * 0.2;
1611
                                let progress = if total_progress >= *count as f32 {
1612
                                    1.
1613
                                } else {
1614
                                    total_progress.fract()
1615
                                };
1616
                                if *strategy == RepeatStrategy::Repeat {
1617
                                    let times_completed = i / 5;
1618
                                    let just_completed = i % 5 == 0;
1619
                                    (
1620
                                        progress,
1621
                                        times_completed,
1622
                                        TweeningDirection::Forward,
1623
                                        if i < 10 {
1624
                                            TweenState::Active
1625
                                        } else {
1626
                                            TweenState::Completed
1627
                                        },
1628
                                        just_completed,
1629
                                    )
1630
                                } else {
1631
                                    let i5 = i % 5;
1632
                                    let times_completed = i / 5;
1633
                                    // Once Completed, the direction doesn't change
1634
                                    let direction = if i >= 5 {
1635
                                        TweeningDirection::Backward
1636
                                    } else {
1637
                                        TweeningDirection::Forward
1638
                                    };
1639
                                    let just_completed = i5 == 0;
1640
                                    (
1641
                                        progress,
1642
                                        times_completed,
1643
                                        direction,
1644
                                        if i < 10 {
1645
                                            TweenState::Active
1646
                                        } else {
1647
                                            TweenState::Completed
1648
                                        },
1649
                                        just_completed,
1650
                                    )
1651
                                }
1652
                            }
1653
                            RepeatCount::Infinite => {
1654
                                let progress = (i as f32 * 0.2).fract();
1655
                                if *strategy == RepeatStrategy::Repeat {
1656
                                    let times_completed = i / 5;
1657
                                    let just_completed = i % 5 == 0;
1658
                                    (
1659
                                        progress,
1660
                                        times_completed,
1661
                                        TweeningDirection::Forward,
1662
                                        TweenState::Active,
1663
                                        just_completed,
1664
                                    )
1665
                                } else {
1666
                                    let i5 = i % 5;
1667
                                    let times_completed = i / 5;
1668
                                    let i10 = i % 10;
1669
                                    let direction = if i10 >= 5 {
1670
                                        TweeningDirection::Backward
1671
                                    } else {
1672
                                        TweeningDirection::Forward
1673
                                    };
1674
                                    let just_completed = i5 == 0;
1675
                                    (
1676
                                        progress,
1677
                                        times_completed,
1678
                                        direction,
1679
                                        TweenState::Active,
1680
                                        just_completed,
1681
                                    )
1682
                                }
1683
                            }
1684
                            RepeatCount::For(_) => panic!("Untested"),
1685
                        };
1686
                    let factor = if tweening_direction.is_backward() {
1687
                        direction = !direction;
1688
                        1. - progress
1689
                    } else {
1690
                        progress
1691
                    };
1692
                    let expected_translation = if direction.is_forward() {
1693
                        Vec3::splat(progress)
1694
                    } else {
1695
                        Vec3::splat(1. - progress)
1696
                    };
1697
                    println!(
1698
                        "Expected: progress={} factor={} times_completed={} direction={:?} state={:?} just_completed={} translation={:?}",
1699
                        progress, factor, times_completed, direction, expected_state, just_completed, expected_translation
1700
                    );
1701

1702
                    // Tick the tween
1703
                    let actual_state =
1704
                        manual_tick_component(tick_duration, &mut tween, &mut world, entity);
1705

1706
                    // Propagate events
1707
                    {
1708
                        let mut events = world.resource_mut::<Events<TweenCompleted>>();
1709
                        events.update();
1710
                    }
1711

1712
                    // Check actual values
1713
                    assert_eq!(tween.direction(), direction);
1714
                    assert_eq!(actual_state, expected_state);
1715
                    assert_approx_eq!(tween.progress(), progress);
1716
                    assert_eq!(tween.times_completed(), times_completed);
1717
                    let transform = world.entity(entity).get::<Transform>().unwrap();
1718
                    assert!(transform
1719
                        .translation
1720
                        .abs_diff_eq(expected_translation, 1e-5));
1721
                    assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1722
                    let cb_mon = callback_monitor.lock().unwrap();
1723
                    assert_eq!(cb_mon.invoke_count, times_completed as u64);
1724
                    assert_eq!(cb_mon.last_reported_count, times_completed);
1725
                    {
1726
                        let mut event_reader = event_reader_system_state.get_mut(&mut world);
1727
                        let event = event_reader.read().next();
1728
                        if just_completed {
1729
                            assert!(event.is_some());
1730
                            if let Some(event) = event {
1731
                                assert_eq!(event.entity, entity);
1732
                                assert_eq!(event.user_data, USER_DATA);
1733
                            }
1734
                        } else {
1735
                            assert!(event.is_none());
1736
                        }
1737
                    }
1738
                }
1739

1740
                // Rewind
1741
                tween.rewind();
1742
                assert_eq!(tween.direction(), *tweening_direction); // does not change
1743
                assert_approx_eq!(tween.progress(), 0.);
1744
                assert_eq!(tween.times_completed(), 0);
1745

1746
                // Dummy tick to update target
1747
                let actual_state =
1748
                    manual_tick_component(Duration::ZERO, &mut tween, &mut world, entity);
1749
                assert_eq!(actual_state, TweenState::Active);
1750
                let expected_translation = if tweening_direction.is_backward() {
1751
                    Vec3::ONE
1752
                } else {
1753
                    Vec3::ZERO
1754
                };
1755
                let transform = world.entity(entity).get::<Transform>().unwrap();
1756
                assert!(transform
1757
                    .translation
1758
                    .abs_diff_eq(expected_translation, 1e-5));
1759
                assert!(transform.rotation.abs_diff_eq(Quat::IDENTITY, 1e-5));
1760

1761
                // Clear callback
1762
                tween.clear_completed();
1763
                assert!(tween.on_completed.is_none());
1764

1765
                // Clear event sending
1766
                tween.clear_completed_event();
1767
                assert!(tween.event_data.is_none());
1768

1769
                // Clear oneshot system
1770
                tween.clear_completed_system();
1771
                assert!(tween.system_id.is_none());
1772
            }
1773
        }
1774
    }
1775

1776
    #[test]
1777
    fn tween_dir() {
1778
        let mut tween = make_test_tween();
1779

1780
        // Default
1781
        assert_eq!(tween.direction(), TweeningDirection::Forward);
1782
        assert_approx_eq!(tween.progress(), 0.0);
1783

1784
        // no-op
1785
        tween.set_direction(TweeningDirection::Forward);
1786
        assert_eq!(tween.direction(), TweeningDirection::Forward);
1787
        assert_approx_eq!(tween.progress(), 0.0);
1788

1789
        // Backward
1790
        tween.set_direction(TweeningDirection::Backward);
1791
        assert_eq!(tween.direction(), TweeningDirection::Backward);
1792
        // progress is independent of direction
1793
        assert_approx_eq!(tween.progress(), 0.0);
1794

1795
        // Progress-invariant
1796
        tween.set_direction(TweeningDirection::Forward);
1797
        tween.set_progress(0.3);
1798
        assert_approx_eq!(tween.progress(), 0.3);
1799
        tween.set_direction(TweeningDirection::Backward);
1800
        // progress is independent of direction
1801
        assert_approx_eq!(tween.progress(), 0.3);
1802

1803
        let (mut world, entity, _system_id) = make_test_env();
1804

1805
        // Progress always increases alongside the current direction
1806
        tween.set_direction(TweeningDirection::Backward);
1807
        assert_approx_eq!(tween.progress(), 0.3);
1808
        manual_tick_component(Duration::from_millis(100), &mut tween, &mut world, entity);
1809
        assert_approx_eq!(tween.progress(), 0.4);
1810
        let transform = world.entity(entity).get::<Transform>().unwrap();
1811
        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.6), 1e-5));
1812
    }
1813

1814
    #[test]
1815
    fn tween_elapsed() {
1816
        let mut tween = make_test_tween();
1817

1818
        let duration = tween.duration();
1819
        let elapsed = tween.elapsed();
1820

1821
        assert_eq!(elapsed, Duration::ZERO);
1822
        assert_eq!(duration, Duration::from_secs(1));
1823

1824
        for ms in [0, 1, 500, 100, 300, 999, 847, 1000, 900] {
1825
            let elapsed = Duration::from_millis(ms);
1826
            tween.set_elapsed(elapsed);
1827
            assert_eq!(tween.elapsed(), elapsed);
1828

1829
            let progress = (elapsed.as_secs_f64() / duration.as_secs_f64()) as f32;
1830
            assert_approx_eq!(tween.progress(), progress);
1831

1832
            let times_completed = u32::from(ms == 1000);
1833
            assert_eq!(tween.times_completed(), times_completed);
1834
        }
1835
    }
1836

1837
    /// Test ticking a sequence of tweens.
1838
    #[test]
1839
    fn seq_tick() {
1840
        let tween1 = Tween::new(
1841
            EaseMethod::default(),
1842
            Duration::from_secs(1),
1843
            TransformPositionLens {
1844
                start: Vec3::ZERO,
1845
                end: Vec3::ONE,
1846
            },
1847
        );
1848
        let tween2 = Tween::new(
1849
            EaseMethod::default(),
1850
            Duration::from_secs(1),
1851
            TransformRotationLens {
1852
                start: Quat::IDENTITY,
1853
                end: Quat::from_rotation_x(90_f32.to_radians()),
1854
            },
1855
        );
1856
        let mut seq = tween1.then(tween2);
1857

1858
        let (mut world, entity, _system_id) = make_test_env();
1859

1860
        for i in 1..=16 {
1861
            let state =
1862
                manual_tick_component(Duration::from_millis(200), &mut seq, &mut world, entity);
1863
            let transform = world.entity(entity).get::<Transform>().unwrap();
1864
            if i < 5 {
1865
                assert_eq!(state, TweenState::Active);
1866
                let r = i as f32 * 0.2;
1867
                assert_eq!(*transform, Transform::from_translation(Vec3::splat(r)));
1868
            } else if i < 10 {
1869
                assert_eq!(state, TweenState::Active);
1870
                let alpha_deg = (18 * (i - 5)) as f32;
1871
                assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
1872
                assert!(transform
1873
                    .rotation
1874
                    .abs_diff_eq(Quat::from_rotation_x(alpha_deg.to_radians()), 1e-5));
1875
            } else {
1876
                assert_eq!(state, TweenState::Completed);
1877
                assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
1878
                assert!(transform
1879
                    .rotation
1880
                    .abs_diff_eq(Quat::from_rotation_x(90_f32.to_radians()), 1e-5));
1881
            }
1882
        }
1883
    }
1884

1885
    /// Test crossing tween boundaries in one tick.
1886
    #[test]
1887
    fn seq_tick_boundaries() {
1888
        let mut seq = Sequence::new((0..3).map(|i| {
1889
            Tween::new(
1890
                EaseMethod::default(),
1891
                Duration::from_secs(1),
1892
                TransformPositionLens {
1893
                    start: Vec3::splat(i as f32),
1894
                    end: Vec3::splat((i + 1) as f32),
1895
                },
1896
            )
1897
            .with_repeat_count(RepeatCount::Finite(1))
1898
        }));
1899

1900
        let (mut world, entity, _system_id) = make_test_env();
1901

1902
        // Tick halfway through the first tween, then in one tick:
1903
        // - Finish the first tween
1904
        // - Start and finish the second tween
1905
        // - Start the third tween
1906
        for delta_ms in [500, 2000] {
1907
            manual_tick_component(
1908
                Duration::from_millis(delta_ms),
1909
                &mut seq,
1910
                &mut world,
1911
                entity,
1912
            );
1913
        }
1914
        assert_eq!(seq.index(), 2);
1915
        let transform = world.entity(entity).get::<Transform>().unwrap();
1916
        assert!(transform.translation.abs_diff_eq(Vec3::splat(2.5), 1e-5));
1917
    }
1918

1919
    /// Sequence::new() and various Sequence-specific methods
1920
    #[test]
1921
    fn seq_iter() {
1922
        let mut seq = Sequence::new((1..5).map(|i| {
1923
            Tween::new(
1924
                EaseMethod::default(),
1925
                Duration::from_millis(200 * i),
1926
                TransformPositionLens {
1927
                    start: Vec3::ZERO,
1928
                    end: Vec3::ONE,
1929
                },
1930
            )
1931
        }));
1932

1933
        let mut progress = 0.;
1934
        for i in 1..5 {
1935
            assert_eq!(seq.index(), i - 1);
1936
            assert_approx_eq!(seq.progress(), progress);
1937
            let duration = Duration::from_millis(200 * i as u64);
1938
            assert_eq!(seq.current().duration(), duration);
1939
            progress += 0.25;
1940
            seq.set_progress(progress);
1941
            assert_eq!(seq.times_completed(), u32::from(i == 4));
1942
        }
1943

1944
        seq.rewind();
1945
        assert_eq!(seq.progress(), 0.);
1946
        assert_eq!(seq.times_completed(), 0);
1947
    }
1948

1949
    /// Sequence::from_single()
1950
    #[test]
1951
    fn seq_from_single() {
1952
        let tween = Tween::new(
1953
            EaseMethod::default(),
1954
            Duration::from_secs(1),
1955
            TransformPositionLens {
1956
                start: Vec3::ZERO,
1957
                end: Vec3::ONE,
1958
            },
1959
        );
1960
        let seq = Sequence::from_single(tween);
1961

1962
        assert_eq!(seq.duration(), Duration::from_secs(1));
1963
    }
1964

1965
    #[test]
1966
    fn seq_elapsed() {
1967
        let mut seq = Sequence::new((1..5).map(|i| {
1968
            Tween::new(
1969
                EaseMethod::default(),
1970
                Duration::from_millis(200 * i),
1971
                TransformPositionLens {
1972
                    start: Vec3::ZERO,
1973
                    end: Vec3::ONE,
1974
                },
1975
            )
1976
        }));
1977

1978
        let mut elapsed = Duration::ZERO;
1979
        for i in 1..5 {
1980
            assert_eq!(seq.index(), i - 1);
1981
            assert_eq!(seq.elapsed(), elapsed);
1982
            let duration = Duration::from_millis(200 * i as u64);
1983
            assert_eq!(seq.current().duration(), duration);
1984
            elapsed += duration;
1985
            seq.set_elapsed(elapsed);
1986
            assert_eq!(seq.times_completed(), u32::from(i == 4));
1987
        }
1988
    }
1989

1990
    /// Test ticking parallel tracks of tweens.
1991
    #[test]
1992
    fn tracks_tick() {
1993
        let tween1 = Tween::new(
1994
            EaseMethod::default(),
1995
            Duration::from_millis(1000),
1996
            TransformPositionLens {
1997
                start: Vec3::ZERO,
1998
                end: Vec3::ONE,
1999
            },
2000
        );
2001
        let tween2 = Tween::new(
2002
            EaseMethod::default(),
2003
            Duration::from_millis(800), // shorter
2004
            TransformRotationLens {
2005
                start: Quat::IDENTITY,
2006
                end: Quat::from_rotation_x(90_f32.to_radians()),
2007
            },
2008
        );
2009
        let mut tracks = Tracks::new([tween1, tween2]);
2010
        assert_eq!(tracks.duration(), Duration::from_secs(1)); // max(1., 0.8)
2011

2012
        let (mut world, entity, _system_id) = make_test_env();
2013

2014
        for i in 1..=6 {
2015
            let state =
2016
                manual_tick_component(Duration::from_millis(200), &mut tracks, &mut world, entity);
2017
            let transform = world.entity(entity).get::<Transform>().unwrap();
2018
            if i < 5 {
2019
                assert_eq!(state, TweenState::Active);
2020
                assert_eq!(tracks.times_completed(), 0);
2021
                let r = i as f32 * 0.2;
2022
                assert_approx_eq!(tracks.progress(), r);
2023
                let alpha_deg = 22.5 * i as f32;
2024
                assert!(transform.translation.abs_diff_eq(Vec3::splat(r), 1e-5));
2025
                assert!(transform
2026
                    .rotation
2027
                    .abs_diff_eq(Quat::from_rotation_x(alpha_deg.to_radians()), 1e-5));
2028
            } else {
2029
                assert_eq!(state, TweenState::Completed);
2030
                assert_eq!(tracks.times_completed(), 1);
2031
                assert_approx_eq!(tracks.progress(), 1.);
2032
                assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
2033
                assert!(transform
2034
                    .rotation
2035
                    .abs_diff_eq(Quat::from_rotation_x(90_f32.to_radians()), 1e-5));
2036
            }
2037
        }
2038

2039
        tracks.rewind();
2040
        assert_eq!(tracks.times_completed(), 0);
2041
        assert_approx_eq!(tracks.progress(), 0.);
2042

2043
        tracks.set_progress(0.9);
2044
        assert_approx_eq!(tracks.progress(), 0.9);
2045
        // tick to udpate state (set_progress() does not update state)
2046
        let state = manual_tick_component(Duration::ZERO, &mut tracks, &mut world, entity);
2047
        assert_eq!(state, TweenState::Active);
2048
        assert_eq!(tracks.times_completed(), 0);
2049

2050
        tracks.set_progress(3.2);
2051
        assert_approx_eq!(tracks.progress(), 1.);
2052
        // tick to udpate state (set_progress() does not update state)
2053
        let state = manual_tick_component(Duration::ZERO, &mut tracks, &mut world, entity);
2054
        assert_eq!(state, TweenState::Completed);
2055
        assert_eq!(tracks.times_completed(), 1); // no looping
2056

2057
        tracks.set_progress(-0.5);
2058
        assert_approx_eq!(tracks.progress(), 0.);
2059
        // tick to udpate state (set_progress() does not update state)
2060
        let state = manual_tick_component(Duration::ZERO, &mut tracks, &mut world, entity);
2061
        assert_eq!(state, TweenState::Active);
2062
        assert_eq!(tracks.times_completed(), 0); // no looping
2063
    }
2064

2065
    /// Delay::then()
2066
    #[test]
2067
    fn delay_then() {
2068
        let seq: Sequence<Transform> =
2069
            Delay::new(Duration::from_secs(1)).then(Delay::new(Duration::from_secs(2)));
2070
        assert_eq!(seq.duration(), Duration::from_secs(3));
2071
        assert_eq!(seq.tweens.len(), 2);
2072
        for (i, t) in seq.tweens.iter().enumerate() {
2073
            assert_eq!(t.duration(), Duration::from_secs(i as u64 + 1));
2074
        }
2075
    }
2076

2077
    /// Test ticking a delay.
2078
    #[test]
2079
    fn delay_tick() {
2080
        let duration = Duration::from_secs(1);
2081
        // Dummy world and registered oneshot system ID
2082
        let (mut world, entity, system_id) = make_test_env();
2083

2084
        const USER_DATA: u64 = 42;
2085
        let mut delay = Delay::new(duration)
2086
            .with_completed_event(USER_DATA)
2087
            .with_completed_system(system_id);
2088

2089
        assert!(delay.event_data.is_some());
2090
        assert_eq!(delay.event_data.unwrap(), USER_DATA);
2091

2092
        assert!(delay.system_id.is_some());
2093
        assert_eq!(delay.system_id.unwrap(), system_id);
2094

2095
        delay.clear_completed_event();
2096
        assert!(delay.event_data.is_none());
2097

2098
        delay.set_completed_event(USER_DATA);
2099
        assert!(delay.event_data.is_some());
2100
        assert_eq!(delay.event_data.unwrap(), USER_DATA);
2101

2102
        delay.clear_completed_system();
2103
        assert!(delay.system_id.is_none());
2104

2105
        delay.set_completed_system(system_id);
2106
        assert!(delay.system_id.is_some());
2107
        assert_eq!(delay.system_id.unwrap(), system_id);
2108

2109
        {
2110
            let tweenable: &dyn Tweenable<Transform> = &delay;
2111
            assert_eq!(tweenable.duration(), duration);
2112
            assert_approx_eq!(tweenable.progress(), 0.);
2113
            assert_eq!(tweenable.elapsed(), Duration::ZERO);
2114
        }
2115

2116
        // Dummy event writer
2117
        let mut event_reader_system_state: SystemState<EventReader<TweenCompleted>> =
2118
            SystemState::new(&mut world);
2119

2120
        // Register callbacks to count completed events
2121
        let callback_monitor = Arc::new(Mutex::new(CallbackMonitor::default()));
2122
        let cb_mon_ptr = Arc::clone(&callback_monitor);
2123
        let reference_entity = entity;
2124
        assert!(delay.on_completed.is_none());
2125
        delay.set_completed(move |completed_entity, delay| {
2126
            assert_eq!(completed_entity, reference_entity);
2127
            let mut cb_mon = cb_mon_ptr.lock().unwrap();
2128
            cb_mon.invoke_count += 1;
2129
            cb_mon.last_reported_count = delay.times_completed();
2130
        });
2131
        assert!(delay.on_completed.is_some());
2132
        assert_eq!(callback_monitor.lock().unwrap().invoke_count, 0);
2133

2134
        for i in 1..=6 {
2135
            let state = manual_tick_component::<Transform>(
2136
                Duration::from_millis(200),
2137
                &mut delay,
2138
                &mut world,
2139
                entity,
2140
            );
2141

2142
            // Propagate events
2143
            {
2144
                let mut events = world.resource_mut::<Events<TweenCompleted>>();
2145
                events.update();
2146
            }
2147

2148
            // Check state
2149
            {
2150
                assert_eq!(state, delay.state());
2151

2152
                let tweenable: &dyn Tweenable<Transform> = &delay;
2153

2154
                {
2155
                    let mut event_reader = event_reader_system_state.get_mut(&mut world);
2156
                    let event = event_reader.read().next();
2157
                    if i == 5 {
2158
                        assert!(event.is_some());
2159
                        let event = event.unwrap();
2160
                        assert_eq!(event.entity, entity);
2161
                        assert_eq!(event.user_data, USER_DATA);
2162
                    } else {
2163
                        assert!(event.is_none());
2164
                    }
2165
                }
2166

2167
                let times_completed = if i < 5 {
2168
                    assert_eq!(state, TweenState::Active);
2169
                    assert!(!delay.is_completed());
2170
                    assert_eq!(tweenable.times_completed(), 0);
2171
                    let r = i as f32 * 0.2;
2172
                    assert_approx_eq!(tweenable.progress(), r);
2173
                    0
2174
                } else {
2175
                    assert_eq!(state, TweenState::Completed);
2176
                    assert!(delay.is_completed());
2177
                    assert_eq!(tweenable.times_completed(), 1);
2178
                    assert_approx_eq!(tweenable.progress(), 1.);
2179
                    1
2180
                };
2181

2182
                let cb_mon = callback_monitor.lock().unwrap();
2183
                assert_eq!(cb_mon.invoke_count, times_completed as u64);
2184
                assert_eq!(cb_mon.last_reported_count, times_completed);
2185
            }
2186
        }
2187

2188
        delay.rewind();
2189
        assert_eq!(delay.times_completed(), 0);
2190
        assert_approx_eq!(delay.progress(), 0.);
2191
        let state = manual_tick_component(Duration::ZERO, &mut delay, &mut world, entity);
2192
        assert_eq!(state, TweenState::Active);
2193

2194
        delay.set_progress(0.3);
2195
        assert_eq!(delay.times_completed(), 0);
2196
        assert_approx_eq!(delay.progress(), 0.3);
2197
        delay.set_progress(1.);
2198
        assert_eq!(delay.times_completed(), 1);
2199
        assert_approx_eq!(delay.progress(), 1.);
2200

2201
        // Clear callback
2202
        delay.clear_completed();
2203
        assert!(delay.on_completed.is_none());
2204

2205
        // Clear event sending
2206
        delay.clear_completed_event();
2207
        assert!(delay.event_data.is_none());
2208
    }
2209

2210
    #[test]
2211
    fn delay_elapsed() {
2212
        let mut delay: Delay<f32> = Delay::new(Duration::from_secs(1));
2213
        let duration = delay.duration();
2214
        for ms in [0, 1, 500, 100, 300, 999, 847, 1000, 900] {
2215
            let elapsed = Duration::from_millis(ms);
2216
            delay.set_elapsed(elapsed);
2217
            assert_eq!(delay.elapsed(), elapsed);
2218

2219
            let progress = (elapsed.as_secs_f64() / duration.as_secs_f64()) as f32;
2220
            assert_approx_eq!(delay.progress(), progress);
2221

2222
            let times_completed = u32::from(ms == 1000);
2223
            assert_eq!(delay.times_completed(), times_completed);
2224

2225
            assert_eq!(delay.is_completed(), ms >= 1000);
2226
            assert_eq!(
2227
                delay.state(),
2228
                if ms >= 1000 {
2229
                    TweenState::Completed
2230
                } else {
2231
                    TweenState::Active
2232
                }
2233
            );
2234
        }
2235
    }
2236

2237
    #[test]
2238
    #[should_panic]
2239
    fn delay_zero_duration_panics() {
2240
        let _: Delay<f32> = Delay::new(Duration::ZERO);
2241
    }
2242

2243
    #[test]
2244
    fn tween_repeat() {
2245
        let mut tween = make_test_tween()
2246
            .with_repeat_count(RepeatCount::Finite(5))
2247
            .with_repeat_strategy(RepeatStrategy::Repeat);
2248

2249
        assert_approx_eq!(tween.progress(), 0.);
2250

2251
        let (mut world, entity, _system_id) = make_test_env();
2252

2253
        // 10%
2254
        let state =
2255
            manual_tick_component(Duration::from_millis(100), &mut tween, &mut world, entity);
2256
        assert_eq!(TweenState::Active, state);
2257
        assert_eq!(0, tween.times_completed());
2258
        assert_approx_eq!(tween.progress(), 0.1);
2259
        let transform = world.entity(entity).get::<Transform>().unwrap();
2260
        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5));
2261

2262
        // 130%
2263
        let state =
2264
            manual_tick_component(Duration::from_millis(1200), &mut tween, &mut world, entity);
2265
        assert_eq!(TweenState::Active, state);
2266
        assert_eq!(1, tween.times_completed());
2267
        assert_approx_eq!(tween.progress(), 0.3);
2268
        let transform = world.entity(entity).get::<Transform>().unwrap();
2269
        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.3), 1e-5));
2270

2271
        // 480%
2272
        let state =
2273
            manual_tick_component(Duration::from_millis(3500), &mut tween, &mut world, entity);
2274
        assert_eq!(TweenState::Active, state);
2275
        assert_eq!(4, tween.times_completed());
2276
        assert_approx_eq!(tween.progress(), 0.8);
2277
        let transform = world.entity(entity).get::<Transform>().unwrap();
2278
        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5));
2279

2280
        // 500% - done
2281
        let state =
2282
            manual_tick_component(Duration::from_millis(200), &mut tween, &mut world, entity);
2283
        assert_eq!(TweenState::Completed, state);
2284
        assert_eq!(5, tween.times_completed());
2285
        assert_approx_eq!(tween.progress(), 1.0);
2286
        let transform = world.entity(entity).get::<Transform>().unwrap();
2287
        assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5));
2288
    }
2289

2290
    #[test]
2291
    fn tween_mirrored_rewind() {
2292
        let mut tween = make_test_tween()
2293
            .with_repeat_count(RepeatCount::Finite(4))
2294
            .with_repeat_strategy(RepeatStrategy::MirroredRepeat);
2295

2296
        assert_approx_eq!(tween.progress(), 0.);
2297

2298
        let (mut world, entity, _system_id) = make_test_env();
2299

2300
        // 10%
2301
        let state =
2302
            manual_tick_component(Duration::from_millis(100), &mut tween, &mut world, entity);
2303
        assert_eq!(TweenState::Active, state);
2304
        assert_eq!(TweeningDirection::Forward, tween.direction());
2305
        assert_eq!(0, tween.times_completed());
2306
        assert_approx_eq!(tween.progress(), 0.1);
2307
        let transform = world.entity(entity).get::<Transform>().unwrap();
2308
        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5));
2309

2310
        // rewind
2311
        tween.rewind();
2312
        assert_eq!(TweeningDirection::Forward, tween.direction());
2313
        assert_eq!(0, tween.times_completed());
2314
        assert_approx_eq!(tween.progress(), 0.);
2315
        let transform = world.entity(entity).get::<Transform>().unwrap();
2316
        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5)); // no-op, rewind doesn't apply Lens
2317

2318
        // 120% - mirror
2319
        let state =
2320
            manual_tick_component(Duration::from_millis(1200), &mut tween, &mut world, entity);
2321
        assert_eq!(TweeningDirection::Backward, tween.direction());
2322
        assert_eq!(TweenState::Active, state);
2323
        assert_eq!(1, tween.times_completed());
2324
        assert_approx_eq!(tween.progress(), 0.2);
2325
        let transform = world.entity(entity).get::<Transform>().unwrap();
2326
        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5));
2327

2328
        // rewind
2329
        tween.rewind();
2330
        assert_eq!(TweeningDirection::Forward, tween.direction()); // restored
2331
        assert_eq!(0, tween.times_completed());
2332
        assert_approx_eq!(tween.progress(), 0.);
2333
        let transform = world.entity(entity).get::<Transform>().unwrap();
2334
        assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5)); // no-op, rewind doesn't apply Lens
2335

2336
        // 400% - done mirror (because Completed freezes the state)
2337
        let state =
2338
            manual_tick_component(Duration::from_millis(4000), &mut tween, &mut world, entity);
2339
        assert_eq!(TweenState::Completed, state);
2340
        assert_eq!(TweeningDirection::Backward, tween.direction()); // frozen from last loop
2341
        assert_eq!(4, tween.times_completed());
2342
        assert_approx_eq!(tween.progress(), 1.); // Completed
2343
        let transform = world.entity(entity).get::<Transform>().unwrap();
2344
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5));
2345

2346
        // rewind
2347
        tween.rewind();
2348
        assert_eq!(TweeningDirection::Forward, tween.direction()); // restored
2349
        assert_eq!(0, tween.times_completed());
2350
        assert_approx_eq!(tween.progress(), 0.);
2351
        let transform = world.entity(entity).get::<Transform>().unwrap();
2352
        assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5)); // no-op, rewind doesn't apply Lens
2353
    }
2354
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc