1use crate::duration::TimeStamp;
10
11#[derive(Clone, Debug)]
13pub struct Animation {
14 pub property: AnimatedProperty,
15 pub keyframes: Vec<Keyframe>,
16 pub easing: Easing,
17 pub repeat: Repeat,
18}
19
20impl Animation {
21 pub fn new(
24 property: AnimatedProperty,
25 mut keyframes: Vec<Keyframe>,
26 easing: Easing,
27 repeat: Repeat,
28 ) -> Self {
29 keyframes.sort_by_key(|k| k.time);
30 Animation {
31 property,
32 keyframes,
33 easing,
34 repeat,
35 }
36 }
37
38 pub fn sample(&self, t: TimeStamp) -> Option<KeyframeValue> {
43 if self.keyframes.is_empty() {
44 return None;
45 }
46 let t = match self.repeat {
47 Repeat::Once => t,
48 Repeat::Loop => {
49 let span = self.span()?;
50 if span <= 0 {
51 t
52 } else {
53 self.keyframes[0].time + ((t - self.keyframes[0].time).rem_euclid(span))
54 }
55 }
56 Repeat::PingPong => {
57 let span = self.span()?;
58 if span <= 0 {
59 t
60 } else {
61 let offset = (t - self.keyframes[0].time).rem_euclid(span * 2);
62 if offset < span {
63 self.keyframes[0].time + offset
64 } else {
65 self.keyframes[0].time + span * 2 - offset
66 }
67 }
68 }
69 };
70
71 if t <= self.keyframes[0].time {
72 return Some(self.keyframes[0].value.clone());
73 }
74 if t >= self.keyframes[self.keyframes.len() - 1].time {
75 return Some(self.keyframes[self.keyframes.len() - 1].value.clone());
76 }
77
78 let idx = self
80 .keyframes
81 .binary_search_by_key(&t, |k| k.time)
82 .unwrap_or_else(|i| i.saturating_sub(1));
83 let (a, b) = (&self.keyframes[idx], &self.keyframes[idx + 1]);
84 let span = (b.time - a.time) as f32;
85 let raw = if span <= 0.0 {
86 0.0
87 } else {
88 (t - a.time) as f32 / span
89 };
90 let segment_easing = b.easing.unwrap_or(self.easing);
91 let f = segment_easing.apply(raw);
92 Some(KeyframeValue::interpolate(&a.value, &b.value, f))
93 }
94
95 fn span(&self) -> Option<TimeStamp> {
96 let first = self.keyframes.first()?.time;
97 let last = self.keyframes.last()?.time;
98 Some(last - first)
99 }
100}
101
102#[non_exhaustive]
104#[derive(Clone, Debug, PartialEq, Eq)]
105pub enum AnimatedProperty {
106 Position,
107 Scale,
108 Rotation,
109 Opacity,
110 Skew,
111 Anchor,
112 Volume,
113 EffectParam {
116 effect_idx: usize,
117 param: &'static str,
118 },
119 Custom(String),
122}
123
124#[derive(Clone, Debug)]
126pub struct Keyframe {
127 pub time: TimeStamp,
128 pub value: KeyframeValue,
129 pub easing: Option<Easing>,
132}
133
134#[non_exhaustive]
137#[derive(Clone, Debug, PartialEq)]
138pub enum KeyframeValue {
139 Scalar(f32),
140 Vec2(f32, f32),
141 Color(u32),
143 Discrete(String),
146}
147
148impl KeyframeValue {
149 pub fn interpolate(a: &KeyframeValue, b: &KeyframeValue, t: f32) -> KeyframeValue {
150 match (a, b) {
151 (KeyframeValue::Scalar(x), KeyframeValue::Scalar(y)) => {
152 KeyframeValue::Scalar(x + (y - x) * t)
153 }
154 (KeyframeValue::Vec2(x1, y1), KeyframeValue::Vec2(x2, y2)) => {
155 KeyframeValue::Vec2(x1 + (x2 - x1) * t, y1 + (y2 - y1) * t)
156 }
157 (KeyframeValue::Color(a), KeyframeValue::Color(b)) => {
158 KeyframeValue::Color(lerp_color(*a, *b, t))
159 }
160 (KeyframeValue::Discrete(a), KeyframeValue::Discrete(b)) => {
161 KeyframeValue::Discrete(if t < 1.0 { a.clone() } else { b.clone() })
162 }
163 _ => a.clone(),
165 }
166 }
167}
168
169fn lerp_color(a: u32, b: u32, t: f32) -> u32 {
170 let ac = [
171 ((a >> 24) & 0xff) as f32,
172 ((a >> 16) & 0xff) as f32,
173 ((a >> 8) & 0xff) as f32,
174 (a & 0xff) as f32,
175 ];
176 let bc = [
177 ((b >> 24) & 0xff) as f32,
178 ((b >> 16) & 0xff) as f32,
179 ((b >> 8) & 0xff) as f32,
180 (b & 0xff) as f32,
181 ];
182 let mut out = 0u32;
183 for i in 0..4 {
184 let v = (ac[i] + (bc[i] - ac[i]) * t).clamp(0.0, 255.0) as u32;
185 out |= v << ((3 - i) * 8);
186 }
187 out
188}
189
190#[non_exhaustive]
192#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
193pub enum Repeat {
194 #[default]
196 Once,
197 Loop,
199 PingPong,
201}
202
203#[non_exhaustive]
205#[derive(Clone, Copy, Debug, Default, PartialEq)]
206pub enum Easing {
207 #[default]
208 Linear,
209 EaseIn,
210 EaseOut,
211 EaseInOut,
212 CubicBezier(f32, f32, f32, f32),
215 Step(u32),
217 Hold,
219}
220
221impl Easing {
222 pub fn apply(&self, t: f32) -> f32 {
224 let t = t.clamp(0.0, 1.0);
225 match self {
226 Easing::Linear => t,
227 Easing::EaseIn => t * t,
228 Easing::EaseOut => 1.0 - (1.0 - t) * (1.0 - t),
229 Easing::EaseInOut => {
230 if t < 0.5 {
231 2.0 * t * t
232 } else {
233 1.0 - 2.0 * (1.0 - t) * (1.0 - t)
234 }
235 }
236 Easing::CubicBezier(x1, y1, x2, y2) => cubic_bezier_eval(t, *x1, *y1, *x2, *y2),
237 Easing::Step(n) if *n > 0 => {
238 let n = *n as f32;
239 (t * n).floor() / n
240 }
241 Easing::Step(_) => 0.0,
242 Easing::Hold => {
243 if t >= 1.0 {
244 1.0
245 } else {
246 0.0
247 }
248 }
249 }
250 }
251}
252
253fn cubic_bezier_eval(t: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
259 fn b(t: f32, p1: f32, p2: f32) -> f32 {
261 let u = 1.0 - t;
262 3.0 * u * u * t * p1 + 3.0 * u * t * t * p2 + t * t * t
263 }
264 fn db(t: f32, p1: f32, p2: f32) -> f32 {
266 let u = 1.0 - t;
267 3.0 * u * u * p1 + 6.0 * u * t * (p2 - p1) + 3.0 * t * t * (1.0 - p2)
268 }
269 let mut guess = t;
271 for _ in 0..4 {
272 let d = db(guess, x1, x2);
273 if d.abs() < 1e-6 {
274 break;
275 }
276 let x = b(guess, x1, x2) - t;
277 guess -= x / d;
278 guess = guess.clamp(0.0, 1.0);
279 }
280 let mut lo = 0.0;
282 let mut hi = 1.0;
283 for _ in 0..16 {
284 let x = b(guess, x1, x2);
285 if (x - t).abs() < 1e-5 {
286 break;
287 }
288 if x < t {
289 lo = guess;
290 } else {
291 hi = guess;
292 }
293 guess = 0.5 * (lo + hi);
294 }
295 b(guess, y1, y2)
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn sample_before_first_keyframe_clamps() {
304 let anim = Animation::new(
305 AnimatedProperty::Opacity,
306 vec![
307 Keyframe {
308 time: 10,
309 value: KeyframeValue::Scalar(0.0),
310 easing: None,
311 },
312 Keyframe {
313 time: 20,
314 value: KeyframeValue::Scalar(1.0),
315 easing: None,
316 },
317 ],
318 Easing::Linear,
319 Repeat::Once,
320 );
321 assert_eq!(anim.sample(0), Some(KeyframeValue::Scalar(0.0)));
322 assert_eq!(anim.sample(10), Some(KeyframeValue::Scalar(0.0)));
323 }
324
325 #[test]
326 fn sample_linear_midpoint() {
327 let anim = Animation::new(
328 AnimatedProperty::Opacity,
329 vec![
330 Keyframe {
331 time: 0,
332 value: KeyframeValue::Scalar(0.0),
333 easing: None,
334 },
335 Keyframe {
336 time: 10,
337 value: KeyframeValue::Scalar(10.0),
338 easing: None,
339 },
340 ],
341 Easing::Linear,
342 Repeat::Once,
343 );
344 match anim.sample(5).unwrap() {
345 KeyframeValue::Scalar(v) => assert!((v - 5.0).abs() < 1e-3),
346 _ => panic!("wrong variant"),
347 }
348 }
349
350 #[test]
351 fn easing_in_out_crosses_half_at_half() {
352 assert!((Easing::EaseInOut.apply(0.5) - 0.5).abs() < 1e-5);
353 }
354
355 #[test]
356 fn cubic_bezier_endpoints() {
357 let e = Easing::CubicBezier(0.25, 0.1, 0.25, 1.0);
359 assert!((e.apply(0.0) - 0.0).abs() < 1e-3);
360 assert!((e.apply(1.0) - 1.0).abs() < 1e-3);
361 }
362
363 #[test]
364 fn repeat_loop_wraps() {
365 let anim = Animation::new(
366 AnimatedProperty::Opacity,
367 vec![
368 Keyframe {
369 time: 0,
370 value: KeyframeValue::Scalar(0.0),
371 easing: None,
372 },
373 Keyframe {
374 time: 10,
375 value: KeyframeValue::Scalar(10.0),
376 easing: None,
377 },
378 ],
379 Easing::Linear,
380 Repeat::Loop,
381 );
382 match anim.sample(15).unwrap() {
384 KeyframeValue::Scalar(v) => assert!((v - 5.0).abs() < 1e-3),
385 _ => panic!("wrong variant"),
386 }
387 }
388
389 #[test]
390 fn color_interpolation_halfway() {
391 let c = KeyframeValue::interpolate(
392 &KeyframeValue::Color(0xFF0000FF),
393 &KeyframeValue::Color(0x0000FFFF),
394 0.5,
395 );
396 match c {
397 KeyframeValue::Color(v) => {
398 let r = (v >> 24) & 0xff;
399 let b = (v >> 8) & 0xff;
400 assert!(r > 100 && r < 155, "r={r}");
401 assert!(b > 100 && b < 155, "b={b}");
402 }
403 _ => panic!("wrong variant"),
404 }
405 }
406}