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