1use 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 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}