1#[cfg(not(feature = "std"))]
25use alloc::vec::Vec;
26
27use crate::easing::Easing;
28use crate::traits::{Animatable, Update};
29
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Serialize};
32
33#[derive(Debug, Clone, PartialEq)]
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38pub enum Loop {
39 Once,
41 Times(u32),
43 Forever,
45 PingPong,
47}
48
49#[derive(Clone)]
53pub struct Keyframe<T: Animatable> {
54 pub time: f32,
56 pub value: T,
58 pub easing: Easing,
60}
61
62impl<T: Animatable + core::fmt::Debug> core::fmt::Debug for Keyframe<T> {
63 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
64 f.debug_struct("Keyframe")
65 .field("time", &self.time)
66 .field("value", &self.value)
67 .field("easing", &self.easing)
68 .finish()
69 }
70}
71
72pub struct KeyframeTrack<T: Animatable> {
76 frames: Vec<Keyframe<T>>,
77 elapsed: f32,
78 looping: Loop,
79 completed: bool,
80 loop_count: u32,
81}
82
83impl<T: Animatable + core::fmt::Debug> core::fmt::Debug for KeyframeTrack<T> {
84 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
85 f.debug_struct("KeyframeTrack")
86 .field("frames", &self.frames)
87 .field("elapsed", &self.elapsed)
88 .field("looping", &self.looping)
89 .field("completed", &self.completed)
90 .finish()
91 }
92}
93
94impl<T: Animatable> KeyframeTrack<T> {
95 pub fn new() -> Self {
97 Self {
98 frames: Vec::new(),
99 elapsed: 0.0,
100 looping: Loop::Once,
101 completed: false,
102 loop_count: 0,
103 }
104 }
105
106 pub fn push(mut self, time: f32, value: T) -> Self {
111 self.frames.push(Keyframe {
112 time,
113 value,
114 easing: Easing::Linear,
115 });
116 self.frames.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
117 self
118 }
119
120 pub fn push_with_easing(mut self, time: f32, value: T, easing: Easing) -> Self {
122 self.frames.push(Keyframe {
123 time,
124 value,
125 easing,
126 });
127 self.frames.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
128 self
129 }
130
131 pub fn looping(mut self, mode: Loop) -> Self {
133 self.looping = mode;
134 self
135 }
136
137 pub fn duration(&self) -> f32 {
139 self.frames.last().map_or(0.0, |f| f.time)
140 }
141
142 pub fn value_at(&self, t: f32) -> T {
144 if self.frames.is_empty() {
145 panic!("KeyframeTrack::value_at called on empty track");
146 }
147
148 if self.frames.len() == 1 {
149 return self.frames[0].value.clone();
150 }
151
152 let t = t.clamp(0.0, self.duration());
154
155 let idx = self
157 .frames
158 .iter()
159 .rposition(|f| f.time <= t)
160 .unwrap_or(0);
161
162 if idx >= self.frames.len() - 1 {
164 return self.frames.last().unwrap().value.clone();
165 }
166
167 let a = &self.frames[idx];
168 let b = &self.frames[idx + 1];
169 let segment_duration = b.time - a.time;
170
171 if segment_duration <= 0.0 {
172 return b.value.clone();
173 }
174
175 let local_t = ((t - a.time) / segment_duration).clamp(0.0, 1.0);
176 let curved_t = a.easing.apply(local_t);
177 a.value.lerp(&b.value, curved_t)
178 }
179
180 pub fn value(&self) -> T {
182 let t = self.effective_time();
183 self.value_at(t)
184 }
185
186 pub fn is_complete(&self) -> bool {
188 self.completed
189 }
190
191 pub fn reset(&mut self) {
193 self.elapsed = 0.0;
194 self.completed = false;
195 self.loop_count = 0;
196 }
197
198 fn effective_time(&self) -> f32 {
200 let dur = self.duration();
201 if dur <= 0.0 {
202 return 0.0;
203 }
204
205 match &self.looping {
206 Loop::Once => self.elapsed.clamp(0.0, dur),
207 Loop::Times(_) | Loop::Forever => {
208 self.elapsed % dur
209 }
210 Loop::PingPong => {
211 let cycle = 2.0 * dur;
212 let cycle_t = self.elapsed % cycle;
213 if cycle_t <= dur {
214 cycle_t
215 } else {
216 2.0 * dur - cycle_t
217 }
218 }
219 }
220 }
221}
222
223impl<T: Animatable> Update for KeyframeTrack<T> {
224 fn update(&mut self, dt: f32) -> bool {
225 if self.completed {
226 return false;
227 }
228
229 let dt = dt.max(0.0);
230 self.elapsed += dt;
231
232 let dur = self.duration();
233 if dur <= 0.0 {
234 self.completed = true;
235 return false;
236 }
237
238 match &self.looping {
239 Loop::Once => {
240 if self.elapsed >= dur {
241 self.elapsed = dur;
242 self.completed = true;
243 }
244 }
245 Loop::Times(n) => {
246 let loops_done = (self.elapsed / dur).floor() as u32;
247 if loops_done >= *n {
248 self.elapsed = dur * (*n as f32);
249 self.completed = true;
250 }
251 }
252 Loop::Forever | Loop::PingPong => {
253 }
255 }
256
257 !self.completed
258 }
259}
260
261#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn single_frame_returns_its_value() {
269 let track = KeyframeTrack::new().push(0.0, 42.0_f32);
270 assert!((track.value_at(0.0) - 42.0).abs() < 1e-6);
271 assert!((track.value_at(999.0) - 42.0).abs() < 1e-6);
272 }
273
274 #[test]
275 fn two_frames_interpolate() {
276 let track = KeyframeTrack::new()
277 .push(0.0, 0.0_f32)
278 .push(1.0, 100.0);
279 assert!((track.value_at(0.5) - 50.0).abs() < 1e-4);
280 }
281
282 #[test]
283 fn three_frames_two_segments() {
284 let track = KeyframeTrack::new()
285 .push(0.0, 0.0_f32)
286 .push(1.0, 100.0)
287 .push(2.0, 0.0);
288 assert!((track.value_at(0.5) - 50.0).abs() < 1e-4);
289 assert!((track.value_at(1.5) - 50.0).abs() < 1e-4);
290 }
291
292 #[test]
293 fn loop_once_completes() {
294 let mut track = KeyframeTrack::new()
295 .push(0.0, 0.0_f32)
296 .push(1.0, 100.0)
297 .looping(Loop::Once);
298
299 assert!(track.update(0.5));
300 assert!(!track.is_complete());
301 assert!(!track.update(0.5));
302 assert!(track.is_complete());
303 }
304
305 #[test]
306 fn loop_forever_never_completes() {
307 let mut track = KeyframeTrack::new()
308 .push(0.0, 0.0_f32)
309 .push(1.0, 100.0)
310 .looping(Loop::Forever);
311
312 for _ in 0..100 {
313 assert!(track.update(0.5));
314 }
315 assert!(!track.is_complete());
316 }
317
318 #[test]
319 fn ping_pong_reverses() {
320 let track = KeyframeTrack::new()
321 .push(0.0, 0.0_f32)
322 .push(1.0, 100.0)
323 .looping(Loop::PingPong);
324
325 assert!((track.value_at(0.5) - 50.0).abs() < 1e-4);
327 }
328
329 #[test]
330 fn loop_times_completes_after_n() {
331 let mut track = KeyframeTrack::new()
332 .push(0.0, 0.0_f32)
333 .push(1.0, 100.0)
334 .looping(Loop::Times(2));
335
336 assert!(track.update(1.0)); assert!(!track.update(1.0)); assert!(track.is_complete());
339 }
340
341 #[test]
342 fn out_of_bounds_clamps() {
343 let track = KeyframeTrack::new()
344 .push(0.0, 0.0_f32)
345 .push(1.0, 100.0);
346 assert!((track.value_at(-5.0) - 0.0).abs() < 1e-6);
347 assert!((track.value_at(99.0) - 100.0).abs() < 1e-6);
348 }
349
350 #[test]
351 fn with_easing() {
352 let track = KeyframeTrack::new()
353 .push_with_easing(0.0, 0.0_f32, Easing::EaseInQuad)
354 .push(1.0, 100.0);
355 assert!((track.value_at(0.5) - 25.0).abs() < 1e-4);
357 }
358
359 #[test]
360 fn update_advances_value() {
361 let mut track = KeyframeTrack::new()
362 .push(0.0, 0.0_f32)
363 .push(1.0, 100.0);
364 track.update(0.5);
365 assert!((track.value() - 50.0).abs() < 1e-4);
366 }
367}