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

djeedai / bevy_tweening / 7739125011

01 Feb 2024 09:36AM UTC coverage: 92.057% (-0.2%) from 92.248%
7739125011

Pull #117

github

web-flow
Merge a0b964e77 into 5e6214b70
Pull Request #117: Updates for Bevy 0.13

13 of 16 new or added lines in 2 files covered. (81.25%)

7 existing lines in 1 file now uncovered.

1298 of 1410 relevant lines covered (92.06%)

1.39 hits per line

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

81.21
/src/lib.rs
1
#![deny(
2
    warnings,
3
    missing_copy_implementations,
4
    trivial_casts,
5
    trivial_numeric_casts,
6
    unsafe_code,
7
    unstable_features,
8
    unused_import_braces,
9
    unused_qualifications,
10
    missing_docs
11
)]
12

13
//! Tweening animation plugin for the Bevy game engine
14
//!
15
//! 🍃 Bevy Tweening provides interpolation-based animation between ("tweening")
16
//! two values, for Bevy components and assets. Each field of a component or
17
//! asset can be animated via a collection or predefined easing functions,
18
//! or providing a custom animation curve. Custom components and assets are also
19
//! supported.
20
//!
21
//! # Example
22
//!
23
//! Add the tweening plugin to your app:
24
//!
25
//! ```no_run
26
//! use bevy::prelude::*;
27
//! use bevy_tweening::*;
28
//!
29
//! App::default()
30
//!     .add_plugins(DefaultPlugins)
31
//!     .add_plugins(TweeningPlugin)
32
//!     .run();
33
//! ```
34
//!
35
//! Animate the position ([`Transform::translation`]) of an [`Entity`]:
36
//!
37
//! ```
38
//! # use bevy::prelude::*;
39
//! # use bevy_tweening::{lens::*, *};
40
//! # use std::time::Duration;
41
//! # fn system(mut commands: Commands) {
42
//! # let size = 16.;
43
//! // Create a single animation (tween) to move an entity.
44
//! let tween = Tween::new(
45
//!     // Use a quadratic easing on both endpoints.
46
//!     EaseFunction::QuadraticInOut,
47
//!     // Animation time.
48
//!     Duration::from_secs(1),
49
//!     // The lens gives access to the Transform component of the Entity,
50
//!     // for the Animator to animate it. It also contains the start and
51
//!     // end values respectively associated with the progress ratios 0. and 1.
52
//!     TransformPositionLens {
53
//!         start: Vec3::ZERO,
54
//!         end: Vec3::new(1., 2., -4.),
55
//!     },
56
//! );
57
//!
58
//! commands.spawn((
59
//!     // Spawn an entity to animate the position of.
60
//!     TransformBundle::default(),
61
//!     // Add an Animator component to control and execute the animation.
62
//!     Animator::new(tween),
63
//! ));
64
//! # }
65
//! ```
66
//!
67
//! Note that this example leverages the fact [`TweeningPlugin`] automatically
68
//! adds the necessary system to animate [`Transform`] components. However, for
69
//! most other components and assets, you need to manually add those systems to
70
//! your `App`.
71
//!
72
//! # System setup
73
//!
74
//! Adding the [`TweeningPlugin`] to your app provides the basic setup for using
75
//! 🍃 Bevy Tweening. However, additional setup is required depending on the
76
//! components and assets you want to animate:
77
//!
78
//! - To ensure a component `C` is animated, the
79
//!   [`component_animator_system::<C>`] system must run each frame, in addition
80
//!   of adding an [`Animator::<C>`] component to the same Entity as `C`.
81
//!
82
//! - To ensure an asset `A` is animated, the [`asset_animator_system::<A>`]
83
//!   system must run each frame, in addition of adding an [`AssetAnimator<A>`]
84
//!   component to any Entity. Animating assets also requires the `bevy_asset`
85
//!   feature (enabled by default).
86
//!
87
//! By default, 🍃 Bevy Tweening adopts a minimalist approach, and the
88
//! [`TweeningPlugin`] will only add systems to animate components and assets
89
//! for which a [`Lens`] is provided by 🍃 Bevy Tweening itself. This means that
90
//! any other Bevy component or asset (either built-in from Bevy itself, or
91
//! custom) requires manually scheduling the appropriate system.
92
//!
93
//! | Component or Asset | Animation system added by `TweeningPlugin`? |
94
//! |---|---|
95
//! | [`Transform`]          | Yes                           |
96
//! | [`Sprite`]             | Only if `bevy_sprite` feature |
97
//! | [`ColorMaterial`]      | Only if `bevy_sprite` feature |
98
//! | [`Style`]              | Only if `bevy_ui` feature     |
99
//! | [`Text`]               | Only if `bevy_text` feature   |
100
//! | All other components   | No                            |
101
//!
102
//! To add a system for a component `C`, use:
103
//!
104
//! ```
105
//! # use bevy::prelude::*;
106
//! # use bevy_tweening::*;
107
//! # let mut app = App::default();
108
//! # #[derive(Component)] struct C;
109
//! app.add_systems(Update,
110
//!     component_animator_system::<C>
111
//!         .in_set(AnimationSystem::AnimationUpdate));
112
//! ```
113
//!
114
//! Similarly for an asset `A`, use the `asset_animator_system`. This is only
115
//! available with the `bevy_asset` feature.
116
//!
117
//! # Tweenables
118
//!
119
//! 🍃 Bevy Tweening supports several types of _tweenables_, building blocks
120
//! that can be combined to form complex animations. A tweenable is a type
121
//! implementing the [`Tweenable`] trait.
122
//!
123
//! - [`Tween`] - A simple tween (easing) animation between two values.
124
//! - [`Sequence`] - A series of tweenables executing in series, one after the
125
//!   other.
126
//! - [`Tracks`] - A collection of tweenables executing in parallel.
127
//! - [`Delay`] - A time delay. This doesn't animate anything.
128
//!
129
//! ## Chaining animations
130
//!
131
//! Most tweenables can be chained with the `then()` operator to produce a
132
//! [`Sequence`] tweenable:
133
//!
134
//! ```
135
//! # use bevy::prelude::*;
136
//! # use bevy_tweening::{lens::*, *};
137
//! # use std::time::Duration;
138
//! let tween1 = Tween::new(
139
//!     // [...]
140
//! #    EaseFunction::BounceOut,
141
//! #    Duration::from_secs(2),
142
//! #    TransformScaleLens {
143
//! #        start: Vec3::ZERO,
144
//! #        end: Vec3::ONE,
145
//! #    },
146
//! );
147
//! let tween2 = Tween::new(
148
//!     // [...]
149
//! #    EaseFunction::QuadraticInOut,
150
//! #    Duration::from_secs(1),
151
//! #    TransformPositionLens {
152
//! #        start: Vec3::ZERO,
153
//! #        end: Vec3::new(3.5, 0., 0.),
154
//! #    },
155
//! );
156
//! // Produce a Sequence executing first 'tween1' then 'tween2'
157
//! let seq = tween1.then(tween2);
158
//! ```
159
//!
160
//! # Animators and lenses
161
//!
162
//! Bevy components and assets are animated with tweening _animator_ components,
163
//! which take a tweenable and apply it to another component on the same
164
//! [`Entity`]. Those animators determine that other component and its fields to
165
//! animate using a _lens_.
166
//!
167
//! ## Components animation
168
//!
169
//! Components are animated with the [`Animator`] component, which is generic
170
//! over the type of component it animates. This is a restriction imposed by
171
//! Bevy, to access the animated component as a mutable reference via a
172
//! [`Query`] and comply with the ECS rules.
173
//!
174
//! The [`Animator`] itself is not generic over the subset of fields of the
175
//! components it animates. This limits the proliferation of generic types when
176
//! animating e.g. both the position and rotation of an entity.
177
//!
178
//! ## Assets animation
179
//!
180
//! Assets are animated in a similar way to component, via the [`AssetAnimator`]
181
//! component. This requires the `bevy_asset` feature (enabled by default).
182
//!
183
//! Because assets are typically shared, and the animation applies to the asset
184
//! itself, all users of the asset see the animation. For example, animating the
185
//! color of a [`ColorMaterial`] will change the color of all the
186
//! 2D meshes using that material.
187
//!
188
//! ## Lenses
189
//!
190
//! Both [`Animator`] and [`AssetAnimator`] access the field(s) to animate via a
191
//! lens, a type that implements the [`Lens`] trait.
192
//!
193
//! Several predefined lenses are provided in the [`lens`] module for the most
194
//! commonly animated fields, like the components of a [`Transform`]. A custom
195
//! lens can also be created by implementing the trait, allowing to animate
196
//! virtually any field of any Bevy component or asset.
197
//!
198
//! [`Transform::translation`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html#structfield.translation
199
//! [`Entity`]: https://docs.rs/bevy/0.12.0/bevy/ecs/entity/struct.Entity.html
200
//! [`Query`]: https://docs.rs/bevy/0.12.0/bevy/ecs/system/struct.Query.html
201
//! [`ColorMaterial`]: https://docs.rs/bevy/0.12.0/bevy/sprite/struct.ColorMaterial.html
202
//! [`Sprite`]: https://docs.rs/bevy/0.12.0/bevy/sprite/struct.Sprite.html
203
//! [`Style`]: https://docs.rs/bevy/0.12.0/bevy/ui/struct.Style.html
204
//! [`Text`]: https://docs.rs/bevy/0.12.0/bevy/text/struct.Text.html
205
//! [`Transform`]: https://docs.rs/bevy/0.12.0/bevy/transform/components/struct.Transform.html
206

207
use std::time::Duration;
208

209
#[cfg(feature = "bevy_asset")]
210
use bevy::asset::Asset;
211
use bevy::prelude::*;
212
use interpolation::Ease as IEase;
213
pub use interpolation::{EaseFunction, Lerp};
214

215
pub use lens::Lens;
216
#[cfg(feature = "bevy_asset")]
217
pub use plugin::asset_animator_system;
218
pub use plugin::{component_animator_system, AnimationSystem, TweeningPlugin};
219
pub use tweenable::{
220
    BoxedTweenable, Delay, Sequence, Targetable, TotalDuration, Tracks, Tween, TweenCompleted,
221
    TweenState, Tweenable,
222
};
223

224
mod color_vec4_ext;
225
pub mod lens;
226
mod plugin;
227
mod tweenable;
228

229
#[cfg(test)]
×
230
mod test_utils;
231

232
/// How many times to repeat a tween animation. See also: [`RepeatStrategy`].
233
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234
pub enum RepeatCount {
235
    /// Run the animation N times.
236
    Finite(u32),
237
    /// Run the animation for some amount of time.
238
    For(Duration),
239
    /// Loop the animation indefinitely.
240
    Infinite,
241
}
242

UNCOV
243
impl Default for RepeatCount {
×
244
    fn default() -> Self {
3✔
245
        Self::Finite(1)
1✔
246
    }
247
}
248

249
impl From<u32> for RepeatCount {
×
250
    fn from(value: u32) -> Self {
1✔
251
        Self::Finite(value)
1✔
252
    }
253
}
254

255
impl From<Duration> for RepeatCount {
×
256
    fn from(value: Duration) -> Self {
1✔
257
        Self::For(value)
1✔
258
    }
259
}
260

261
/// What to do when a tween animation needs to be repeated. See also
262
/// [`RepeatCount`].
263
///
264
/// Only applicable when [`RepeatCount`] is greater than the animation duration.
265
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266
pub enum RepeatStrategy {
267
    /// Reset the animation back to its starting position.
268
    Repeat,
269
    /// Follow a ping-pong pattern, changing the direction each time an endpoint
270
    /// is reached.
271
    ///
272
    /// A complete cycle start -> end -> start always counts as 2 loop
273
    /// iterations for the various operations where looping matters. That
274
    /// is, a 1 second animation will take 2 seconds to end up back where it
275
    /// started.
276
    MirroredRepeat,
277
}
278

UNCOV
279
impl Default for RepeatStrategy {
×
280
    fn default() -> Self {
×
281
        Self::Repeat
1✔
282
    }
283
}
284

285
/// Playback state of an animator.
286
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
287
pub enum AnimatorState {
288
    /// The animation is playing. This is the default state.
289
    Playing,
290
    /// The animation is paused in its current state.
291
    Paused,
292
}
293

294
impl Default for AnimatorState {
×
295
    fn default() -> Self {
2✔
296
        Self::Playing
1✔
297
    }
298
}
299

300
impl std::ops::Not for AnimatorState {
×
301
    type Output = Self;
×
302

303
    fn not(self) -> Self::Output {
1✔
304
        match self {
3✔
305
            Self::Paused => Self::Playing,
1✔
306
            Self::Playing => Self::Paused,
3✔
307
        }
308
    }
309
}
310

311
/// Describe how eased value should be computed.
312
#[derive(Clone, Copy)]
313
pub enum EaseMethod {
314
    /// Follow `EaseFunction`.
315
    EaseFunction(EaseFunction),
316
    /// Linear interpolation, with no function.
317
    Linear,
318
    /// Discrete interpolation, eased value will jump from start to end when
319
    /// stepping over the discrete limit.
320
    Discrete(f32),
321
    /// Use a custom function to interpolate the value.
322
    CustomFunction(fn(f32) -> f32),
323
}
324

UNCOV
325
impl EaseMethod {
×
326
    #[must_use]
×
327
    fn sample(self, x: f32) -> f32 {
1✔
328
        match self {
2✔
329
            Self::EaseFunction(function) => x.calc(function),
1✔
330
            Self::Linear => x,
1✔
331
            Self::Discrete(limit) => {
1✔
332
                if x > limit {
3✔
333
                    1.
2✔
334
                } else {
335
                    0.
1✔
336
                }
337
            }
338
            Self::CustomFunction(function) => function(x),
1✔
339
        }
340
    }
341
}
342

343
impl Default for EaseMethod {
×
344
    fn default() -> Self {
2✔
345
        Self::Linear
2✔
346
    }
347
}
348

349
impl From<EaseFunction> for EaseMethod {
×
350
    fn from(ease_function: EaseFunction) -> Self {
3✔
351
        Self::EaseFunction(ease_function)
3✔
352
    }
353
}
354

355
/// Direction a tweening animation is playing.
356
///
357
/// When playing a tweenable forward, the progress values `0` and `1` are
358
/// respectively mapped to the start and end bounds of the lens(es) being used.
359
/// Conversely, when playing backward, this mapping is reversed, such that a
360
/// progress value of `0` corresponds to the state of the target at the end
361
/// bound of the lens, while a progress value of `1` corresponds to the state of
362
/// that target at the start bound of the lens, effectively making the animation
363
/// play backward.
364
///
365
/// For all but [`RepeatStrategy::MirroredRepeat`] this is always
366
/// [`TweeningDirection::Forward`], unless manually configured with
367
/// [`Tween::set_direction()`] in which case the value is constant equal to the
368
/// value set. When using [`RepeatStrategy::MirroredRepeat`], this is either
369
/// forward (from start to end; ping) or backward (from end to start; pong),
370
/// depending on the current iteration of the loop.
371
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372
pub enum TweeningDirection {
373
    /// Animation playing from start to end.
374
    Forward,
375
    /// Animation playing from end to start, in reverse.
376
    Backward,
377
}
378

379
impl TweeningDirection {
×
380
    /// Is the direction equal to [`TweeningDirection::Forward`]?
381
    #[must_use]
×
382
    pub fn is_forward(&self) -> bool {
1✔
383
        *self == Self::Forward
1✔
384
    }
385

386
    /// Is the direction equal to [`TweeningDirection::Backward`]?
387
    #[must_use]
×
388
    pub fn is_backward(&self) -> bool {
1✔
389
        *self == Self::Backward
1✔
390
    }
391
}
392

393
impl Default for TweeningDirection {
×
394
    fn default() -> Self {
×
395
        Self::Forward
1✔
396
    }
397
}
398

399
impl std::ops::Not for TweeningDirection {
2✔
UNCOV
400
    type Output = Self;
×
401

402
    fn not(self) -> Self::Output {
2✔
403
        match self {
2✔
404
            Self::Forward => Self::Backward,
1✔
405
            Self::Backward => Self::Forward,
1✔
406
        }
407
    }
408
}
409

410
macro_rules! animator_impl {
×
411
    () => {
×
412
        /// Set the initial playback state of the animator.
413
        #[must_use]
×
414
        pub fn with_state(mut self, state: AnimatorState) -> Self {
2✔
415
            self.state = state;
2✔
416
            self
2✔
417
        }
418

419
        /// Set the initial speed of the animator. See [`Animator::set_speed`] for
420
        /// details.
421
        #[must_use]
×
422
        pub fn with_speed(mut self, speed: f32) -> Self {
3✔
423
            self.speed = speed;
3✔
424
            self
3✔
425
        }
426

427
        /// Set the animation speed. Defaults to 1.
428
        ///
429
        /// A speed of 2 means the animation will run twice as fast while a speed of 0.1
430
        /// will result in a 10x slowed animation.
431
        pub fn set_speed(&mut self, speed: f32) {
2✔
432
            self.speed = speed;
2✔
433
        }
434

435
        /// Get the animation speed.
436
        ///
437
        /// See [`set_speed()`] for a definition of what the animation speed is.
438
        ///
439
        /// [`set_speed()`]: Animator::speed
440
        pub fn speed(&self) -> f32 {
3✔
441
            self.speed
3✔
442
        }
443

444
        /// Set the top-level tweenable item this animator controls.
445
        pub fn set_tweenable(&mut self, tween: impl Tweenable<T> + 'static) {
2✔
446
            self.tweenable = Box::new(tween);
4✔
447
        }
448

449
        /// Get the top-level tweenable this animator is currently controlling.
450
        #[must_use]
×
451
        pub fn tweenable(&self) -> &dyn Tweenable<T> {
4✔
452
            self.tweenable.as_ref()
5✔
453
        }
454

455
        /// Get the top-level mutable tweenable this animator is currently controlling.
456
        #[must_use]
×
457
        pub fn tweenable_mut(&mut self) -> &mut dyn Tweenable<T> {
3✔
458
            self.tweenable.as_mut()
3✔
459
        }
460

461
        /// Stop animation playback and rewind the animation.
462
        ///
463
        /// This changes the animator state to [`AnimatorState::Paused`] and rewind its
464
        /// tweenable.
465
        pub fn stop(&mut self) {
2✔
466
            self.state = AnimatorState::Paused;
2✔
467
            self.tweenable_mut().rewind();
2✔
468
        }
469
    };
470
}
471

472
/// Component to control the animation of another component.
473
///
474
/// The animated component is the component located on the same entity as the
475
/// [`Animator<T>`] itself.
476
#[derive(Component)]
477
pub struct Animator<T: Component> {
478
    /// Control if this animation is played or not.
479
    pub state: AnimatorState,
480
    tweenable: BoxedTweenable<T>,
481
    speed: f32,
482
}
483

484
impl<T: Component + std::fmt::Debug> std::fmt::Debug for Animator<T> {
×
485
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1✔
486
        f.debug_struct("Animator")
2✔
487
            .field("state", &self.state)
1✔
488
            .finish()
489
    }
490
}
491

492
impl<T: Component> Animator<T> {
×
493
    /// Create a new animator component from a single tweenable.
494
    #[must_use]
×
495
    pub fn new(tween: impl Tweenable<T> + 'static) -> Self {
4✔
496
        Self {
497
            state: default(),
4✔
498
            tweenable: Box::new(tween),
4✔
499
            speed: 1.,
500
        }
501
    }
502

503
    animator_impl!();
×
504
}
505

506
/// Component to control the animation of an asset.
507
///
508
/// The animated asset is the asset referenced by a [`Handle<T>`] component
509
/// located on the same entity as the [`AssetAnimator<T>`] itself.
510
#[cfg(feature = "bevy_asset")]
511
#[derive(Component)]
512
pub struct AssetAnimator<T: Asset> {
513
    /// Control if this animation is played or not.
514
    pub state: AnimatorState,
515
    tweenable: BoxedTweenable<T>,
516
    speed: f32,
517
}
518

519
#[cfg(feature = "bevy_asset")]
520
impl<T: Asset + std::fmt::Debug> std::fmt::Debug for AssetAnimator<T> {
×
521
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1✔
522
        f.debug_struct("AssetAnimator")
2✔
523
            .field("state", &self.state)
1✔
524
            .finish()
525
    }
526
}
527

528
#[cfg(feature = "bevy_asset")]
529
impl<T: Asset> AssetAnimator<T> {
×
530
    /// Create a new asset animator component from a single tweenable.
531
    #[must_use]
×
532
    pub fn new(tween: impl Tweenable<T> + 'static) -> Self {
3✔
533
        Self {
534
            state: default(),
3✔
535
            tweenable: Box::new(tween),
3✔
536
            speed: 1.,
537
        }
538
    }
539

540
    animator_impl!();
×
541
}
542

543
#[cfg(test)]
×
544
mod tests {
545
    #[cfg(feature = "bevy_asset")]
546
    use bevy::reflect::TypeUuid;
547

548
    use super::*;
549
    use crate::test_utils::*;
550

551
    struct DummyLens {
552
        start: f32,
553
        end: f32,
554
    }
555

556
    #[derive(Debug, Default, Component)]
557
    struct DummyComponent {
558
        value: f32,
559
    }
560

561
    #[cfg(feature = "bevy_asset")]
562
    #[derive(Asset, Debug, Default, Reflect, TypeUuid)]
563
    #[uuid = "a33abc11-264e-4bbb-82e8-b87226bb4383"]
564
    struct DummyAsset {
565
        value: f32,
566
    }
567

568
    impl Lens<DummyComponent> for DummyLens {
×
569
        fn lerp(&mut self, target: &mut DummyComponent, ratio: f32) {
1✔
570
            target.value = self.start.lerp(&self.end, &ratio);
2✔
571
        }
572
    }
573

574
    #[test]
×
575
    fn dummy_lens_component() {
3✔
576
        let mut c = DummyComponent::default();
1✔
577
        let mut l = DummyLens { start: 0., end: 1. };
1✔
578
        for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] {
2✔
579
            l.lerp(&mut c, r);
1✔
580
            assert_approx_eq!(c.value, r);
1✔
581
        }
582
    }
583

584
    #[cfg(feature = "bevy_asset")]
585
    impl Lens<DummyAsset> for DummyLens {
×
586
        fn lerp(&mut self, target: &mut DummyAsset, ratio: f32) {
1✔
587
            target.value = self.start.lerp(&self.end, &ratio);
2✔
588
        }
589
    }
590

591
    #[cfg(feature = "bevy_asset")]
×
592
    #[test]
×
593
    fn dummy_lens_asset() {
3✔
594
        let mut a = DummyAsset::default();
1✔
595
        let mut l = DummyLens { start: 0., end: 1. };
1✔
596
        for r in [0_f32, 0.01, 0.3, 0.5, 0.9, 0.999, 1.] {
2✔
597
            l.lerp(&mut a, r);
1✔
598
            assert_approx_eq!(a.value, r);
1✔
599
        }
600
    }
601

602
    #[test]
×
603
    fn repeat_count() {
3✔
604
        let count = RepeatCount::default();
1✔
605
        assert_eq!(count, RepeatCount::Finite(1));
7✔
606
    }
607

608
    #[test]
×
609
    fn repeat_strategy() {
3✔
610
        let strategy = RepeatStrategy::default();
1✔
611
        assert_eq!(strategy, RepeatStrategy::Repeat);
1✔
612
    }
613

614
    #[test]
×
615
    fn tweening_direction() {
3✔
616
        let tweening_direction = TweeningDirection::default();
1✔
617
        assert_eq!(tweening_direction, TweeningDirection::Forward);
1✔
618
    }
619

620
    #[test]
×
621
    fn animator_state() {
3✔
622
        let mut state = AnimatorState::default();
9✔
623
        assert_eq!(state, AnimatorState::Playing);
1✔
624
        state = !state;
1✔
625
        assert_eq!(state, AnimatorState::Paused);
1✔
626
        state = !state;
1✔
627
        assert_eq!(state, AnimatorState::Playing);
1✔
628
    }
629

UNCOV
630
    #[test]
×
631
    fn ease_method() {
3✔
632
        let ease = EaseMethod::default();
1✔
633
        assert!(matches!(ease, EaseMethod::Linear));
3✔
634

635
        let ease = EaseMethod::EaseFunction(EaseFunction::QuadraticIn);
1✔
636
        assert_eq!(0., ease.sample(0.));
1✔
637
        assert_eq!(0.25, ease.sample(0.5));
1✔
638
        assert_eq!(1., ease.sample(1.));
1✔
639

640
        let ease = EaseMethod::Linear;
1✔
641
        assert_eq!(0., ease.sample(0.));
1✔
642
        assert_eq!(0.5, ease.sample(0.5));
1✔
643
        assert_eq!(1., ease.sample(1.));
1✔
644

645
        let ease = EaseMethod::Discrete(0.3);
1✔
646
        assert_eq!(0., ease.sample(0.));
1✔
647
        assert_eq!(1., ease.sample(0.5));
1✔
648
        assert_eq!(1., ease.sample(1.));
1✔
649

650
        let ease = EaseMethod::CustomFunction(|f| 1. - f);
3✔
651
        assert_eq!(0., ease.sample(1.));
1✔
652
        assert_eq!(0.5, ease.sample(0.5));
1✔
653
        assert_eq!(1., ease.sample(0.));
1✔
654
    }
655

656
    #[test]
×
657
    fn animator_new() {
3✔
658
        let tween = Tween::new(
659
            EaseFunction::QuadraticInOut,
1✔
660
            Duration::from_secs(1),
1✔
661
            DummyLens { start: 0., end: 1. },
1✔
662
        );
663
        let animator = Animator::<DummyComponent>::new(tween);
1✔
664
        assert_eq!(animator.state, AnimatorState::default());
2✔
665
        assert_eq!(animator.tweenable().progress(), 0.);
1✔
666
    }
667

668
    #[test]
×
669
    fn animator_with_state() {
7✔
670
        for state in [AnimatorState::Playing, AnimatorState::Paused] {
2✔
671
            let tween = Tween::<DummyComponent>::new(
672
                EaseFunction::QuadraticInOut,
3✔
673
                Duration::from_secs(1),
1✔
674
                DummyLens { start: 0., end: 1. },
3✔
675
            );
676
            let animator = Animator::new(tween).with_state(state);
3✔
677
            assert_eq!(animator.state, state);
2✔
678

UNCOV
679
            // impl Debug
×
680
            let debug_string = format!("{:?}", animator);
1✔
681
            assert_eq!(
1✔
682
                debug_string,
×
683
                format!("Animator {{ state: {:?} }}", animator.state)
2✔
684
            );
685
        }
686
    }
687

688
    #[test]
2✔
689
    fn animator_controls() {
3✔
690
        let tween = Tween::<DummyComponent>::new(
691
            EaseFunction::QuadraticInOut,
1✔
692
            Duration::from_secs(1),
1✔
693
            DummyLens { start: 0., end: 1. },
1✔
694
        );
695
        let mut animator = Animator::new(tween);
1✔
696
        assert_eq!(animator.state, AnimatorState::Playing);
2✔
697
        assert_approx_eq!(animator.tweenable().progress(), 0.);
1✔
698

699
        animator.stop();
1✔
700
        assert_eq!(animator.state, AnimatorState::Paused);
1✔
701
        assert_approx_eq!(animator.tweenable().progress(), 0.);
1✔
702

703
        animator.tweenable_mut().set_progress(0.5);
2✔
704
        assert_eq!(animator.state, AnimatorState::Paused);
1✔
705
        assert_approx_eq!(animator.tweenable().progress(), 0.5);
1✔
706

707
        animator.tweenable_mut().rewind();
2✔
708
        assert_eq!(animator.state, AnimatorState::Paused);
1✔
709
        assert_approx_eq!(animator.tweenable().progress(), 0.);
1✔
710

711
        animator.tweenable_mut().set_progress(0.5);
2✔
712
        animator.state = AnimatorState::Playing;
1✔
713
        assert_eq!(animator.state, AnimatorState::Playing);
1✔
714
        assert_approx_eq!(animator.tweenable().progress(), 0.5);
1✔
715

716
        animator.tweenable_mut().rewind();
2✔
717
        assert_eq!(animator.state, AnimatorState::Playing);
1✔
718
        assert_approx_eq!(animator.tweenable().progress(), 0.);
1✔
719

720
        animator.stop();
1✔
721
        assert_eq!(animator.state, AnimatorState::Paused);
1✔
722
        assert_approx_eq!(animator.tweenable().progress(), 0.);
1✔
723
    }
724

725
    #[test]
×
726
    fn animator_speed() {
3✔
727
        let tween = Tween::<DummyComponent>::new(
728
            EaseFunction::QuadraticInOut,
1✔
729
            Duration::from_secs(1),
1✔
730
            DummyLens { start: 0., end: 1. },
1✔
731
        );
732

733
        let mut animator = Animator::new(tween);
1✔
734
        assert_approx_eq!(animator.speed(), 1.); // default speed
2✔
735

736
        animator.set_speed(2.4);
1✔
737
        assert_approx_eq!(animator.speed(), 2.4);
3✔
738

739
        let tween = Tween::<DummyComponent>::new(
740
            EaseFunction::QuadraticInOut,
1✔
741
            Duration::from_secs(1),
5✔
742
            DummyLens { start: 0., end: 1. },
1✔
743
        );
744

745
        let animator = Animator::new(tween).with_speed(3.5);
1✔
746
        assert_approx_eq!(animator.speed(), 3.5);
2✔
747
    }
748

749
    #[test]
×
750
    fn animator_set_tweenable() {
3✔
751
        let tween = Tween::<DummyComponent>::new(
752
            EaseFunction::QuadraticInOut,
1✔
753
            Duration::from_secs(1),
1✔
754
            DummyLens { start: 0., end: 1. },
1✔
755
        );
756
        let mut animator = Animator::new(tween);
3✔
757

758
        let tween2 = Tween::<DummyComponent>::new(
759
            EaseFunction::QuadraticInOut,
3✔
760
            Duration::from_secs(2),
3✔
761
            DummyLens { start: 0., end: 1. },
3✔
762
        );
763
        animator.set_tweenable(tween2);
11✔
764

765
        assert_eq!(animator.tweenable().duration(), Duration::from_secs(2));
5✔
766
    }
767

UNCOV
768
    #[cfg(feature = "bevy_asset")]
×
769
    #[test]
×
770
    fn asset_animator_new() {
3✔
771
        let tween = Tween::<DummyAsset>::new(
772
            EaseFunction::QuadraticInOut,
1✔
773
            Duration::from_secs(1),
3✔
774
            DummyLens { start: 0., end: 1. },
3✔
775
        );
776
        let animator = AssetAnimator::new(tween);
1✔
777
        assert_eq!(animator.state, AnimatorState::default());
2✔
778
        let tween = animator;
1✔
779
        assert_eq!(tween.tweenable().progress(), 0.);
4✔
780
    }
781

782
    #[cfg(feature = "bevy_asset")]
×
783
    #[test]
2✔
784
    fn asset_animator_with_state() {
5✔
785
        for state in [AnimatorState::Playing, AnimatorState::Paused] {
2✔
786
            let tween = Tween::<DummyAsset>::new(
787
                EaseFunction::QuadraticInOut,
1✔
788
                Duration::from_secs(1),
1✔
789
                DummyLens { start: 0., end: 1. },
1✔
790
            );
791
            let animator = AssetAnimator::new(tween).with_state(state);
1✔
792
            assert_eq!(animator.state, state);
4✔
793

794
            // impl Debug
×
795
            let debug_string = format!("{:?}", animator);
1✔
796
            assert_eq!(
3✔
797
                debug_string,
2✔
798
                format!("AssetAnimator {{ state: {:?} }}", animator.state)
8✔
799
            );
800
        }
801
    }
802

803
    #[cfg(feature = "bevy_asset")]
×
804
    #[test]
2✔
805
    fn asset_animator_controls() {
3✔
806
        let tween: Tween<DummyAsset> = Tween::new(
807
            EaseFunction::QuadraticInOut,
1✔
808
            Duration::from_secs(1),
1✔
809
            DummyLens { start: 0., end: 1. },
1✔
810
        );
811
        let mut animator = AssetAnimator::new(tween);
1✔
812
        assert_eq!(animator.state, AnimatorState::Playing);
4✔
813
        assert_approx_eq!(animator.tweenable().progress(), 0.);
3✔
814

815
        animator.stop();
1✔
816
        assert_eq!(animator.state, AnimatorState::Paused);
1✔
817
        assert_approx_eq!(animator.tweenable().progress(), 0.);
1✔
818

819
        animator.tweenable_mut().set_progress(0.5);
2✔
820
        assert_eq!(animator.state, AnimatorState::Paused);
1✔
821
        assert_approx_eq!(animator.tweenable().progress(), 0.5);
1✔
822

823
        animator.tweenable_mut().rewind();
2✔
824
        assert_eq!(animator.state, AnimatorState::Paused);
1✔
825
        assert_approx_eq!(animator.tweenable().progress(), 0.);
1✔
826

827
        animator.tweenable_mut().set_progress(0.5);
2✔
828
        animator.state = AnimatorState::Playing;
1✔
829
        assert_eq!(animator.state, AnimatorState::Playing);
1✔
830
        assert_approx_eq!(animator.tweenable().progress(), 0.5);
1✔
831

832
        animator.tweenable_mut().rewind();
2✔
833
        assert_eq!(animator.state, AnimatorState::Playing);
1✔
834
        assert_approx_eq!(animator.tweenable().progress(), 0.);
1✔
835

836
        animator.stop();
1✔
837
        assert_eq!(animator.state, AnimatorState::Paused);
1✔
838
        assert_approx_eq!(animator.tweenable().progress(), 0.);
1✔
839
    }
840

841
    #[cfg(feature = "bevy_asset")]
×
842
    #[test]
×
843
    fn asset_animator_speed() {
3✔
844
        let tween: Tween<DummyAsset> = Tween::new(
845
            EaseFunction::QuadraticInOut,
1✔
846
            Duration::from_secs(1),
1✔
847
            DummyLens { start: 0., end: 1. },
1✔
848
        );
849

850
        let mut animator = AssetAnimator::new(tween);
1✔
851
        assert_approx_eq!(animator.speed(), 1.); // default speed
4✔
852

853
        animator.set_speed(2.4);
1✔
854
        assert_approx_eq!(animator.speed(), 2.4);
1✔
855

856
        let tween: Tween<DummyAsset> = Tween::new(
857
            EaseFunction::QuadraticInOut,
1✔
858
            Duration::from_secs(1),
1✔
859
            DummyLens { start: 0., end: 1. },
3✔
860
        );
861

862
        let animator = AssetAnimator::new(tween).with_speed(3.5);
1✔
863
        assert_approx_eq!(animator.speed(), 3.5);
2✔
864
    }
865

866
    #[cfg(feature = "bevy_asset")]
×
867
    #[test]
×
868
    fn asset_animator_set_tweenable() {
3✔
869
        let tween: Tween<DummyAsset> = Tween::new(
870
            EaseFunction::QuadraticInOut,
1✔
871
            Duration::from_secs(1),
1✔
872
            DummyLens { start: 0., end: 1. },
1✔
873
        );
874
        let mut animator = AssetAnimator::new(tween);
1✔
875

876
        let tween2 = Tween::new(
877
            EaseFunction::QuadraticInOut,
3✔
878
            Duration::from_secs(2),
3✔
879
            DummyLens { start: 0., end: 1. },
3✔
880
        );
881
        animator.set_tweenable(tween2);
3✔
882

883
        assert_eq!(animator.tweenable().duration(), Duration::from_secs(2));
1✔
884
    }
885
}
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

© 2025 Coveralls, Inc