swamp_render/
anim.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use monotonic_time_rs::{Millis, MillisDuration};
6use std::ops::{Div, Sub};
7
8pub trait AnimationLookup {
9    fn frame(&self) -> u16;
10}
11
12#[derive(Debug, Copy, Clone)]
13pub struct Tick(u64);
14
15impl Tick {
16    #[inline]
17    #[must_use]
18    pub const fn inner(&self) -> u64 {
19        self.0
20    }
21}
22
23impl Sub for Tick {
24    type Output = Self;
25    fn sub(self, rhs: Self) -> Self::Output {
26        Self(self.0 - rhs.0)
27    }
28}
29
30pub type NumberOfTicks = u64;
31
32impl Div<NumberOfTicks> for Tick {
33    type Output = u64;
34
35    fn div(self, rhs: NumberOfTicks) -> Self::Output {
36        self.0 / rhs
37    }
38}
39
40pub type Fps = u16;
41
42#[derive(Debug, Copy, Clone)]
43pub struct FrameAnimationConfig {
44    start_frame: u16,
45    count: u8,
46    frame_duration: MillisDuration,
47}
48
49impl FrameAnimationConfig {
50    #[must_use]
51    pub fn new(start_frame: u16, count: u8, fps: Fps) -> Self {
52        Self {
53            start_frame,
54            count,
55            frame_duration: MillisDuration::from_millis(1000) / (fps as u32),
56        }
57    }
58}
59
60#[derive(Debug)]
61pub enum PlayMode {
62    Once,
63    Repeat,
64}
65
66#[derive(Debug)]
67pub struct FrameAnimation {
68    started_at_time: Millis,
69    is_playing: bool,
70    config: FrameAnimationConfig,
71    relative_frame: u8,
72    play_mode: PlayMode,
73}
74
75impl FrameAnimation {
76    #[must_use]
77    pub fn new(config: FrameAnimationConfig) -> Self {
78        Self {
79            started_at_time: Millis::new(0),
80            is_playing: false,
81            config,
82            relative_frame: 0,
83            play_mode: PlayMode::Once,
84        }
85    }
86
87    /// # Panics
88    ///
89    pub fn update(&mut self, now: Millis) {
90        if !self.is_playing {
91            return;
92        }
93
94        assert!(now >= self.started_at_time);
95
96        let elapsed_ticks = now - self.started_at_time;
97        let frames_since_start = elapsed_ticks.as_millis() / self.config.frame_duration.as_millis();
98
99        match self.play_mode {
100            PlayMode::Once => {
101                if frames_since_start >= self.config.count as u64 {
102                    self.is_playing = false;
103                    self.relative_frame = (self.config.count as u16 - 1) as u8;
104                } else {
105                    self.relative_frame = frames_since_start as u8;
106                }
107            }
108            PlayMode::Repeat => {
109                self.relative_frame = (frames_since_start % self.config.count as u64) as u8;
110            }
111        }
112    }
113
114    #[must_use]
115    pub const fn is_done(&self) -> bool {
116        !self.is_playing
117    }
118
119    #[must_use]
120    pub const fn is_playing(&self) -> bool {
121        self.is_playing
122    }
123
124    #[must_use]
125    pub const fn absolute_frame(&self) -> u16 {
126        self.relative_frame as u16 + self.config.start_frame
127    }
128
129    #[must_use]
130    pub const fn relative_frame(&self) -> u16 {
131        self.relative_frame as u16
132    }
133
134    pub fn play(&mut self, now: Millis) {
135        self.is_playing = true;
136        self.play_mode = PlayMode::Once;
137        self.started_at_time = now;
138    }
139
140    pub fn play_repeat(&mut self, now: Millis) {
141        self.is_playing = true;
142        self.play_mode = PlayMode::Repeat;
143        self.started_at_time = now;
144    }
145}
146
147impl AnimationLookup for FrameAnimation {
148    fn frame(&self) -> u16 {
149        self.absolute_frame()
150    }
151}