Skip to main content

fyrox_animation/spritesheet/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Sprite sheet animation is used to create simple key frame animation using single image with
22//! series of frames.
23
24#![warn(missing_docs)]
25
26use crate::{
27    core::{
28        algebra::Vector2,
29        math::Rect,
30        reflect::prelude::*,
31        uuid::{uuid, Uuid},
32        uuid_provider,
33        visitor::prelude::*,
34        TypeUuidProvider,
35    },
36    spritesheet::signal::Signal,
37};
38use std::collections::vec_deque::VecDeque;
39use strum_macros::{AsRefStr, EnumString, VariantNames};
40
41pub mod signal;
42
43/// Trait for anything that can be used as a texture.
44pub trait SpriteSheetTexture: PartialEq + Clone + Visit + Reflect + Default {}
45
46impl<T: PartialEq + Clone + Visit + Reflect + Default> SpriteSheetTexture for T {}
47
48/// Animation playback status.
49#[derive(
50    Visit, Reflect, Copy, Clone, Eq, PartialEq, Debug, AsRefStr, EnumString, VariantNames, Default,
51)]
52pub enum Status {
53    /// Animation is playing.
54    Playing,
55
56    /// Animation is stopped. Stopped animation is guaranteed to be either at beginning or at end frames (depending on speed).
57    /// When an animation is stopped manually via ([`SpriteSheetAnimation::stop()`], the animation will be rewound to beginning.
58    #[default]
59    Stopped,
60
61    /// Animation is paused. Playback can be resumed by [`SpriteSheetAnimation::play()`].
62    Paused,
63}
64
65uuid_provider!(Status = "74a31122-a7a8-476c-ab87-77e53cf0523c");
66
67/// Some animation event.
68#[derive(Visit, Reflect, Clone, Debug, Eq, PartialEq)]
69#[non_exhaustive]
70pub enum Event {
71    /// A signal with an id was hit.
72    Signal(u64),
73}
74
75impl Default for Event {
76    fn default() -> Self {
77        Self::Signal(0)
78    }
79}
80
81/// Container for a sprite sheet animation frames.
82#[derive(Reflect, Visit, Clone, Debug, PartialEq, Eq)]
83pub struct SpriteSheetFramesContainer<T>
84where
85    T: SpriteSheetTexture,
86{
87    size: Vector2<u32>,
88    frames: Vec<Vector2<u32>>,
89    #[visit(optional)]
90    texture: Option<T>,
91}
92
93impl<T> SpriteSheetFramesContainer<T>
94where
95    T: SpriteSheetTexture,
96{
97    /// Adds a frame to the container.
98    pub fn push(&mut self, bounds: Vector2<u32>) {
99        self.frames.push(bounds)
100    }
101
102    /// Removes a frame from the container.
103    pub fn remove(&mut self, index: usize) -> Vector2<u32> {
104        self.frames.remove(index)
105    }
106
107    /// Returns total amount of frames in the container.
108    pub fn len(&self) -> usize {
109        self.frames.len()
110    }
111
112    /// Returns `true` if the container is empty, `false` - otherwise.
113    pub fn is_empty(&self) -> bool {
114        self.frames.is_empty()
115    }
116
117    /// Tries to get a reference to a frame with given index.
118    pub fn get(&self, index: usize) -> Option<&Vector2<u32>> {
119        self.frames.get(index)
120    }
121
122    /// Sets new container size. It does not affect frames!
123    pub fn set_size(&mut self, size: Vector2<u32>) {
124        self.size = Vector2::new(size.x.max(1), size.y.max(1));
125    }
126
127    /// Returns size of the container.
128    pub fn size(&self) -> Vector2<u32> {
129        self.size
130    }
131
132    /// Sorts frames by their position. `(x,y)` will be converted to index and then used for
133    /// sorting. This method ensures that the frames will be ordered from the left top corner
134    /// to right bottom corner line-by-line.
135    pub fn sort_by_position(&mut self) {
136        self.frames.sort_by_key(|p| p.y * self.size.x + p.x)
137    }
138
139    /// Returns an iterator that yields frames position.
140    pub fn iter(&self) -> impl Iterator<Item = &Vector2<u32>> {
141        self.frames.iter()
142    }
143
144    /// Returns an iterator that yields frames position.
145    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Vector2<u32>> {
146        self.frames.iter_mut()
147    }
148
149    /// Returns current texture of the container. To set a texture use sprite sheet animation methods.
150    pub fn texture(&self) -> Option<T> {
151        self.texture.clone()
152    }
153}
154
155impl<T> Default for SpriteSheetFramesContainer<T>
156where
157    T: SpriteSheetTexture,
158{
159    fn default() -> Self {
160        Self {
161            size: Vector2::new(1, 1),
162            frames: vec![],
163            texture: None,
164        }
165    }
166}
167
168/// Sprite sheet animation is an animation based on key frames, where each key frame is packed into single image. Usually, all key
169/// frames have the same size, but this is not mandatory.
170#[derive(Visit, Reflect, Clone, Debug)]
171pub struct SpriteSheetAnimation<T>
172where
173    T: SpriteSheetTexture,
174{
175    #[visit(rename = "Frames")]
176    frames_container: SpriteSheetFramesContainer<T>,
177    current_frame: f32,
178    speed: f32,
179    status: Status,
180    looping: bool,
181    signals: Vec<Signal>,
182    #[visit(optional)]
183    #[reflect(setter = "set_texture")]
184    texture: Option<T>,
185    #[reflect(hidden)]
186    #[visit(skip)]
187    events: VecDeque<Event>,
188    #[visit(optional)]
189    max_event_capacity: usize,
190}
191
192impl<T: SpriteSheetTexture> PartialEq for SpriteSheetAnimation<T> {
193    fn eq(&self, other: &Self) -> bool {
194        self.frames_container == other.frames_container
195            && self.current_frame == other.current_frame
196            && self.speed == other.speed
197            && self.looping == other.looping
198            && self.signals == other.signals
199            && self.texture == other.texture
200    }
201}
202
203impl<T> TypeUuidProvider for SpriteSheetAnimation<T>
204where
205    T: SpriteSheetTexture,
206{
207    fn type_uuid() -> Uuid {
208        uuid!("1fa13feb-a16d-4539-acde-672aaeb0f62b")
209    }
210}
211
212impl<T> Default for SpriteSheetAnimation<T>
213where
214    T: SpriteSheetTexture,
215{
216    fn default() -> Self {
217        Self {
218            frames_container: Default::default(),
219            current_frame: 0.0,
220            speed: 10.0,
221            status: Default::default(),
222            looping: true,
223            signals: Default::default(),
224            texture: None,
225            events: Default::default(),
226            max_event_capacity: 32,
227        }
228    }
229}
230
231/// Sprite sheet source image parameters defines how to interpret an image. It defines size of each frame,
232/// total size of an image, frame range to use, etc.
233#[derive(Clone, Debug, PartialEq, Eq)]
234pub struct ImageParameters {
235    /// Width of an image in pixels.
236    pub width: u32,
237
238    /// Height of an image in pixels.
239    pub height: u32,
240
241    /// Width of every frame in an image.
242    pub frame_width: u32,
243
244    /// Height of every frame in an image.
245    pub frame_height: u32,
246
247    /// Index of a first frame at which a produced animation should start.
248    pub first_frame: u32,
249
250    /// Index of a last frame at which a produced animation should end.
251    pub last_frame: u32,
252
253    /// Defines how to interpret the image - is it pack in rows of frames or columns of frames.
254    pub column_major: bool,
255}
256
257impl<T> SpriteSheetAnimation<T>
258where
259    T: SpriteSheetTexture,
260{
261    /// Creates new empty animation.
262    pub fn new() -> Self {
263        Self::default()
264    }
265
266    /// Creates sprite sheet animation using given image parameters. The method is used to create animation
267    /// for particular range in an image. For example, you have the following sprite sheet:
268    ///
269    /// ```text
270    /// 128 pixels wide
271    /// _________________
272    /// | 0 | 1 | 2 | 3 |
273    /// |___|___|___|___|
274    /// | 4 | 5 | 6 | 7 |  128 pixels tall
275    /// |___|___|___|___|
276    /// | 8 | 9 |10 |11 |
277    /// |___|___|___|___|
278    /// ```
279    ///
280    /// Let's assume that there could be three animations:
281    /// - 0..3 - run
282    /// - 4..6 - idle
283    /// - 7..11 - attack
284    ///
285    /// and you want to extract all three animations as separate animations. In this case you could do something
286    /// like this:
287    ///
288    /// ```rust
289    /// # use fyrox_animation::{
290    /// #      spritesheet::{ImageParameters, SpriteSheetAnimation},
291    /// #      core::math::Rect,
292    /// # };
293    /// # use fyrox_core::{reflect::prelude::*, visitor::prelude::*};
294    /// #
295    /// #[derive(PartialEq, Clone, Reflect, Visit, Debug, Default)]
296    /// struct MyTexture {}
297    ///
298    /// fn extract_animations() {
299    ///     let run = SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
300    ///         width: 128,
301    ///         height: 128,
302    ///         frame_width: 32,
303    ///         frame_height: 32,
304    ///         first_frame: 0,
305    ///         last_frame: 4,
306    ///         column_major: false,
307    ///     });
308    ///
309    ///     let idle = SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
310    ///         width: 128,
311    ///         height: 128,
312    ///         frame_width: 32,
313    ///         frame_height: 32,
314    ///         first_frame: 4,
315    ///         last_frame: 7,
316    ///         column_major: false,
317    ///     });
318    ///
319    ///     let attack = SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
320    ///         width: 128,
321    ///         height: 128,
322    ///         frame_width: 32,
323    ///         frame_height: 32,
324    ///         first_frame: 7,
325    ///         last_frame: 12,
326    ///         column_major: false,
327    ///     });
328    ///  }
329    /// ```
330    ///
331    /// If frames if your sprite sheet are ordered in column-major fashion (when you count them from top-left corner to bottom-left corner and then
332    /// starting from new column, etc.), you should set `column_major` parameter to true.
333    pub fn new_from_image_parameters(params: ImageParameters) -> Self {
334        let ImageParameters {
335            width,
336            height,
337            frame_width,
338            frame_height,
339            first_frame,
340            last_frame,
341            column_major,
342        } = params;
343
344        let width_in_frames = width / frame_width;
345        let height_in_frames = height / frame_height;
346
347        let frames = (first_frame..last_frame)
348            .map(|n| {
349                let x = if column_major {
350                    n / width_in_frames
351                } else {
352                    n % width_in_frames
353                };
354                let y = if column_major {
355                    n % height_in_frames
356                } else {
357                    n / height_in_frames
358                };
359
360                Vector2::new(x, y)
361            })
362            .collect::<Vec<_>>();
363
364        Self {
365            frames_container: SpriteSheetFramesContainer {
366                frames,
367                size: Vector2::new(width_in_frames, height_in_frames),
368                texture: None,
369            },
370            ..Default::default()
371        }
372    }
373
374    /// Creates new animation with given frames container.
375    pub fn with_container(container: SpriteSheetFramesContainer<T>) -> Self {
376        Self {
377            frames_container: container,
378            ..Default::default()
379        }
380    }
381
382    /// Sets new texture for the animation.
383    pub fn set_texture(&mut self, texture: Option<T>) -> Option<T> {
384        self.frames_container.texture.clone_from(&texture);
385        std::mem::replace(&mut self.texture, texture)
386    }
387
388    /// Returns current texture of the animation.
389    pub fn texture(&self) -> Option<T> {
390        self.texture.clone()
391    }
392
393    /// Gets the maximum capacity of events.
394    pub fn get_max_event_capacity(&self) -> usize {
395        self.max_event_capacity
396    }
397
398    /// Sets the maximum capacity of events.
399    pub fn set_max_event_capacity(&mut self, max_event_capacity: usize) {
400        self.max_event_capacity = max_event_capacity;
401    }
402
403    /// Returns a shared reference to inner frames container.
404    pub fn frames(&self) -> &SpriteSheetFramesContainer<T> {
405        &self.frames_container
406    }
407
408    /// Returns a mutable reference to inner frames container.
409    pub fn frames_mut(&mut self) -> &mut SpriteSheetFramesContainer<T> {
410        &mut self.frames_container
411    }
412
413    /// Adds new frame.
414    pub fn add_frame(&mut self, frame: Vector2<u32>) {
415        self.frames_container.push(frame);
416    }
417
418    /// Remove a frame at given index.
419    pub fn remove_frame(&mut self, index: usize) -> Option<Vector2<u32>> {
420        if index < self.frames_container.len() {
421            self.current_frame = self.current_frame.min(self.frames_container.len() as f32);
422            Some(self.frames_container.remove(index))
423        } else {
424            None
425        }
426    }
427
428    /// Updates animation playback using given time step.
429    pub fn update(&mut self, dt: f32) {
430        if self.status != Status::Playing {
431            return;
432        }
433
434        if self.frames_container.is_empty() {
435            self.status = Status::Stopped;
436            return;
437        }
438
439        let next_frame = self.current_frame + self.speed * dt;
440
441        for signal in self.signals.iter_mut().filter(|s| s.enabled) {
442            let signal_frame = signal.frame as f32;
443
444            if (self.speed >= 0.0
445                && (self.current_frame < signal_frame && next_frame >= signal_frame)
446                || self.speed < 0.0
447                    && (self.current_frame > signal_frame && next_frame <= signal_frame))
448                && self.events.len() < self.max_event_capacity
449            {
450                self.events.push_back(Event::Signal(signal.id));
451            }
452        }
453
454        self.current_frame = next_frame;
455        if self.current_frame >= self.frames_container.len() as f32 {
456            if self.looping {
457                // Continue playing from beginning.
458                self.current_frame = 0.0;
459            } else {
460                // Keep on last frame and stop.
461                self.current_frame = self.frames_container.len().saturating_sub(1) as f32;
462                self.status = Status::Stopped;
463            }
464        } else if self.current_frame <= 0.0 {
465            if self.looping {
466                // Continue playing from end.
467                self.current_frame = self.frames_container.len().saturating_sub(1) as f32;
468            } else {
469                // Keep on first frame and stop.
470                self.current_frame = 0.0;
471                self.status = Status::Stopped;
472            }
473        }
474    }
475
476    /// Returns current frame index.
477    pub fn current_frame(&self) -> usize {
478        self.current_frame as usize
479    }
480
481    /// Tries to fetch UV rectangle at given frame. Returns `None` if animation is empty.
482    pub fn frame_uv_rect(&self, i: usize) -> Option<Rect<f32>> {
483        assert_ne!(self.frames_container.size.x, 0);
484        assert_ne!(self.frames_container.size.y, 0);
485
486        self.frames_container.get(i).map(|pos| Rect {
487            position: Vector2::new(
488                pos.x as f32 / self.frames_container.size.x as f32,
489                pos.y as f32 / self.frames_container.size.y as f32,
490            ),
491            size: Vector2::new(
492                1.0 / self.frames_container.size.x as f32,
493                1.0 / self.frames_container.size.y as f32,
494            ),
495        })
496    }
497
498    /// Tries to fetch UV rectangle at current frame. Returns `None` if animation is empty.
499    pub fn current_frame_uv_rect(&self) -> Option<Rect<f32>> {
500        self.frame_uv_rect(self.current_frame())
501    }
502
503    /// Sets current frame of the animation. Input value will be clamped to [0; frame_count] range.
504    pub fn set_current_frame(&mut self, current_frame: usize) {
505        self.current_frame = current_frame.min(self.frames_container.len()) as f32;
506    }
507
508    /// Returns true if the animation is looping, false - otherwise.
509    pub fn is_looping(&self) -> bool {
510        self.looping
511    }
512
513    /// Continue animation from beginning (or end in case of negative speed) when ended or stop.
514    pub fn set_looping(&mut self, looping: bool) {
515        self.looping = looping;
516    }
517
518    /// Returns playback speed in frames per second.
519    pub fn speed(&self) -> f32 {
520        self.speed
521    }
522
523    /// Sets playback speed in frames per second. The speed can be negative, in this case animation
524    /// will play in reverse.
525    pub fn set_speed(&mut self, speed: f32) {
526        self.speed = speed;
527    }
528
529    /// Sets current frame index to the first frame in the animation.
530    pub fn rewind_to_beginning(&mut self) {
531        self.current_frame = 0.0;
532    }
533
534    /// Sets current frame index to the last frame in the animation.
535    pub fn rewind_to_end(&mut self) {
536        self.current_frame = self.frames_container.len().saturating_sub(1) as f32;
537    }
538
539    /// Returns current status of the animation.
540    pub fn status(&self) -> Status {
541        self.status
542    }
543
544    /// Starts animation playback.
545    pub fn play(&mut self) {
546        self.status = Status::Playing;
547    }
548
549    /// Returns `true` if the animation is playing, `false` - otherwise.
550    pub fn is_playing(&self) -> bool {
551        self.status == Status::Playing
552    }
553
554    /// Stops animation playback, rewinds animation to the beginning.
555    pub fn stop(&mut self) {
556        self.status = Status::Stopped;
557        self.rewind_to_beginning();
558    }
559
560    /// Returns `true` if the animation is stopped, `false` - otherwise.
561    pub fn is_stopped(&self) -> bool {
562        self.status == Status::Stopped
563    }
564
565    /// Puts animation playback on pause.
566    pub fn pause(&mut self) {
567        self.status = Status::Paused;
568    }
569
570    /// Returns `true` if the animation is paused, `false` - otherwise.
571    pub fn is_paused(&self) -> bool {
572        self.status == Status::Paused
573    }
574
575    /// Adds new animation signal to the animation.
576    pub fn add_signal(&mut self, signal: Signal) {
577        self.signals.push(signal)
578    }
579
580    /// Removes animation signal by given id.
581    pub fn remove_signal(&mut self, id: u64) {
582        self.signals.retain(|s| s.id != id)
583    }
584
585    /// Pops animation event from internal queue.
586    pub fn pop_event(&mut self) -> Option<Event> {
587        self.events.pop_front()
588    }
589}
590
591#[cfg(test)]
592mod test {
593    use crate::spritesheet::{
594        signal::Signal, Event, ImageParameters, SpriteSheetAnimation, Status,
595    };
596    use fyrox_core::{algebra::Vector2, math::Rect, reflect::prelude::*, visitor::prelude::*};
597
598    #[derive(PartialEq, Clone, Reflect, Visit, Debug, Default)]
599    struct MyTexture {}
600
601    #[test]
602    fn test_sprite_sheet_one_row() {
603        let animation =
604            SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
605                width: 128,
606                height: 128,
607                frame_width: 32,
608                frame_height: 32,
609                first_frame: 0,
610                last_frame: 4,
611                column_major: false,
612            });
613        assert_eq!(
614            animation.frame_uv_rect(0),
615            Some(Rect::new(0.0, 0.0, 0.25, 0.25))
616        );
617        assert_eq!(
618            animation.frame_uv_rect(1),
619            Some(Rect::new(0.25, 0.0, 0.25, 0.25))
620        );
621        assert_eq!(
622            animation.frame_uv_rect(2),
623            Some(Rect::new(0.5, 0.0, 0.25, 0.25))
624        );
625        assert_eq!(
626            animation.frame_uv_rect(3),
627            Some(Rect::new(0.75, 0.0, 0.25, 0.25))
628        );
629    }
630
631    #[test]
632    fn test_sprite_sheet_one_column() {
633        let animation =
634            SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
635                width: 128,
636                height: 128,
637                frame_width: 32,
638                frame_height: 32,
639                first_frame: 0,
640                last_frame: 4,
641                column_major: true,
642            });
643        assert_eq!(
644            animation.frame_uv_rect(0),
645            Some(Rect::new(0.0, 0.0, 0.25, 0.25))
646        );
647        assert_eq!(
648            animation.frame_uv_rect(1),
649            Some(Rect::new(0.0, 0.25, 0.25, 0.25))
650        );
651        assert_eq!(
652            animation.frame_uv_rect(2),
653            Some(Rect::new(0.0, 0.5, 0.25, 0.25))
654        );
655        assert_eq!(
656            animation.frame_uv_rect(3),
657            Some(Rect::new(0.0, 0.75, 0.25, 0.25))
658        );
659    }
660
661    #[test]
662    fn test_sprite_sheet_row_partial() {
663        let animation =
664            SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
665                width: 128,
666                height: 128,
667                frame_width: 32,
668                frame_height: 32,
669                first_frame: 2,
670                last_frame: 6,
671                column_major: false,
672            });
673        assert_eq!(
674            animation.frame_uv_rect(0),
675            Some(Rect::new(0.5, 0.0, 0.25, 0.25))
676        );
677        assert_eq!(
678            animation.frame_uv_rect(1),
679            Some(Rect::new(0.75, 0.0, 0.25, 0.25))
680        );
681        assert_eq!(
682            animation.frame_uv_rect(2),
683            Some(Rect::new(0.0, 0.25, 0.25, 0.25))
684        );
685        assert_eq!(
686            animation.frame_uv_rect(3),
687            Some(Rect::new(0.25, 0.25, 0.25, 0.25))
688        );
689    }
690
691    #[test]
692    fn test_sprite_sheet_column_partial() {
693        let animation =
694            SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
695                width: 128,
696                height: 128,
697                frame_width: 32,
698                frame_height: 32,
699                first_frame: 2,
700                last_frame: 6,
701                column_major: true,
702            });
703        assert_eq!(
704            animation.frame_uv_rect(0),
705            Some(Rect::new(0.0, 0.5, 0.25, 0.25))
706        );
707        assert_eq!(
708            animation.frame_uv_rect(1),
709            Some(Rect::new(0.0, 0.75, 0.25, 0.25))
710        );
711        assert_eq!(
712            animation.frame_uv_rect(2),
713            Some(Rect::new(0.25, 0.0, 0.25, 0.25))
714        );
715        assert_eq!(
716            animation.frame_uv_rect(3),
717            Some(Rect::new(0.25, 0.25, 0.25, 0.25))
718        );
719    }
720
721    #[test]
722    fn test_sprite_sheet_playback() {
723        let mut animation =
724            SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
725                width: 128,
726                height: 128,
727                frame_width: 32,
728                frame_height: 32,
729                first_frame: 2,
730                last_frame: 6,
731                column_major: true,
732            });
733
734        animation.speed = 1.0; // 1 FPS
735        animation.looping = false;
736
737        assert_eq!(animation.status, Status::Stopped);
738
739        animation.play();
740
741        assert_eq!(animation.status, Status::Playing);
742
743        let expected_output = [
744            Rect::new(0.0, 0.5, 0.25, 0.25),
745            Rect::new(0.0, 0.75, 0.25, 0.25),
746            Rect::new(0.25, 0.0, 0.25, 0.25),
747            Rect::new(0.25, 0.25, 0.25, 0.25),
748        ];
749
750        for &expected_frame in &expected_output {
751            assert_eq!(animation.current_frame_uv_rect(), Some(expected_frame));
752            animation.update(1.0);
753        }
754
755        assert_eq!(animation.status, Status::Stopped);
756
757        animation.speed = -1.0; // Play in reverse.
758
759        animation.play();
760
761        for &expected_frame in expected_output.iter().rev() {
762            assert_eq!(animation.current_frame_uv_rect(), Some(expected_frame));
763            animation.update(1.0);
764        }
765    }
766
767    #[test]
768    fn test_signals() {
769        let mut animation = SpriteSheetAnimation::<MyTexture>::new();
770
771        animation.add_frame(Vector2::new(0, 0));
772        animation.add_frame(Vector2::new(1, 0));
773        animation.add_frame(Vector2::new(2, 0));
774
775        animation.set_speed(1.0);
776        animation.set_looping(false);
777        animation.play();
778
779        animation.add_signal(Signal {
780            id: 0,
781            frame: 1,
782            enabled: true,
783        });
784
785        animation.add_signal(Signal {
786            id: 1,
787            frame: 1,
788            enabled: false,
789        });
790
791        animation.add_signal(Signal {
792            id: 2,
793            frame: 2,
794            enabled: true,
795        });
796
797        for _ in 0..3 {
798            animation.update(1.0);
799        }
800
801        assert_eq!(animation.pop_event(), Some(Event::Signal(0)));
802        // Disable signals does not produce any events.
803        assert_eq!(animation.pop_event(), Some(Event::Signal(2)));
804        // Only two should appear.
805        assert_eq!(animation.pop_event(), None);
806    }
807}