1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//! Functions and types relating to animations.

use std::time::Duration;

use crate::graphics::texture::Texture;
use crate::graphics::{DrawParams, Drawable, Rectangle};
use crate::time;
use crate::Context;

/// An animation, cycling between regions of a texture at a regular interval.
///
/// Calling `advance` within your `draw` method will drive the animation, switching the texture
/// region once the specified time has passed.
#[derive(Debug, Clone)]
pub struct Animation {
    texture: Texture,
    frames: Vec<Rectangle>,
    frame_length: Duration,

    current_frame: usize,
    timer: Duration,
    repeating: bool,
}

impl Animation {
    /// Creates a new looping animation.
    pub fn new(texture: Texture, frames: Vec<Rectangle>, frame_length: Duration) -> Animation {
        Animation {
            texture,
            frames,
            frame_length,

            current_frame: 0,
            timer: Duration::from_secs(0),
            repeating: true,
        }
    }

    /// Creates a new animation that does not repeat once all of the frames have been displayed.
    pub fn once(texture: Texture, frames: Vec<Rectangle>, frame_length: Duration) -> Animation {
        Animation {
            texture,
            frames,
            frame_length,

            current_frame: 0,
            timer: Duration::from_secs(0),
            repeating: false,
        }
    }

    /// Advances the animation's timer, switching the texture region if required.
    pub fn advance(&mut self, ctx: &Context) {
        self.advance_by(time::get_delta_time(ctx));
    }

    /// Advances the animation's timer by a specified amount, switching the texture region if required.
    pub fn advance_by(&mut self, duration: Duration) {
        self.timer += duration;

        let frames_remaining = self.current_frame < self.frames.len() - 1;

        if frames_remaining || self.repeating {
            while self.timer >= self.frame_length {
                self.current_frame = (self.current_frame + 1) % self.frames.len();
                self.timer -= self.frame_length;
            }
        } else if self.timer > self.frame_length {
            self.timer = self.frame_length;
        }
    }

    /// Restarts the animation from the first frame.
    pub fn restart(&mut self) {
        self.current_frame = 0;
        self.timer = Duration::from_secs(0);
    }

    /// Returns a reference to the texture currently being used by the animation.
    pub fn texture(&self) -> &Texture {
        &self.texture
    }

    /// Sets the texture that will be used by the animation.
    ///
    /// This method will not change the frame definitions or current state of the animation,
    /// so it can be used for e.g. swapping spritesheets. If you need to change the slicing
    /// for the new texture, call `set_frames`.
    pub fn set_texture(&mut self, texture: Texture) {
        self.texture = texture;
    }

    /// Gets the sections of the texture being displayed for each frame of the animation.
    pub fn frames(&self) -> &[Rectangle] {
        &self.frames
    }

    /// Sets the sections of the texture being displayed for each frame of the animation.
    ///
    /// This method will reset the animation back to frame zero.
    pub fn set_frames(&mut self, new_frames: Vec<Rectangle>) {
        self.frames = new_frames;

        self.restart();
    }

    /// Gets the amount of time that each frame of the animation lasts for.
    pub fn frame_length(&self) -> Duration {
        self.frame_length
    }

    /// Sets the amount of time that each frame of the animation lasts for.
    pub fn set_frame_length(&mut self, new_frame_length: Duration) {
        self.frame_length = new_frame_length;
    }

    /// Gets whether or not the animation is currently set to repeat when it reaches the end
    /// of the frames.
    pub fn repeating(&self) -> bool {
        self.repeating
    }

    /// Sets whether or not the animation should repeat when it reaches the end of the frames.
    pub fn set_repeating(&mut self, repeating: bool) {
        self.repeating = repeating;
    }

    /// Gets the index of the frame that is currently being displayed.
    ///
    /// This index is zero-based, and can be used in combination with the [`frames`](#method.frames)
    /// method in order to track the progress of the animation.
    pub fn current_frame_index(&self) -> usize {
        self.current_frame
    }

    /// Sets which frame of the animation should be displayed.
    ///
    /// Usually you will want to control the animation by calling [`advance`](#method.advance)
    /// or [`advance_by`](#method.advance), but this method can be useful for more fine-grained
    /// control.
    ///
    /// The index is zero-based, and must be within the bounds of the animation's
    /// [`frames`](#method.frames), otherwise this method will panic.
    pub fn set_current_frame_index(&mut self, index: usize) {
        // Without this check, the code would panic in `Drawable::draw` because `self.frames[self.current_frame]`
        // is invalid, but the developer would have no clue where it was set.
        assert!(index < self.frames.len());

        self.current_frame = index;
    }

    /// Gets the duration that the current frame has been visible.
    ///
    /// This can be used in combination with the [`frame_length`](#method.frame_length) method
    /// in order to track the progress of the animation.
    pub fn current_frame_time(&self) -> Duration {
        self.timer
    }

    /// Sets the duration that the current frame has been visible.
    ///
    /// Usually you will want to control the animation by calling [`advance`](#method.advance)
    /// or [`advance_by`](#method.advance), but this method can be useful for more fine-grained
    /// control.
    ///
    /// The animation will not advance past the end of the current frame until the next call
    /// to [`advance`](#method.advance) or [`advance_by`](#method.advance_by). If a value is
    /// given that is larger than [`frame_length`](#method.frame_length), this animation may skip frames.
    pub fn set_current_frame_time(&mut self, duration: Duration) {
        self.timer = duration;
    }
}

impl Drawable for Animation {
    fn draw<P>(&self, ctx: &mut Context, params: P)
    where
        P: Into<DrawParams>,
    {
        let frame_clip = self.frames[self.current_frame];

        let mut params = params.into();

        params.clip = match params.clip {
            Some(mut clip) => {
                clip.x += frame_clip.x;
                clip.y += frame_clip.y;
                clip.width += frame_clip.width;
                clip.height += frame_clip.height;

                Some(clip)
            }
            None => Some(frame_clip),
        };

        self.texture.draw(ctx, params)
    }
}