1use crate::style::Style;
35use crate::widget::{Color, Rect};
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
46pub enum Easing {
47 #[default]
49 Linear,
50 EaseIn,
52 EaseOut,
54 EaseInOut,
56 EaseInCubic,
58 EaseOutCubic,
60 EaseInOutCubic,
62 Bounce,
64 Step(u8),
66}
67
68impl Easing {
69 pub fn apply(self, t: f32) -> f32 {
73 let t = t.clamp(0.0, 1.0);
74 match self {
75 Easing::Linear => t,
76 Easing::EaseIn => t * t,
77 Easing::EaseOut => {
78 let inv = 1.0 - t;
79 1.0 - inv * inv
80 }
81 Easing::EaseInOut => t * t * (3.0 - 2.0 * t),
82 Easing::EaseInCubic => t * t * t,
83 Easing::EaseOutCubic => {
84 let inv = 1.0 - t;
85 1.0 - inv * inv * inv
86 }
87 Easing::EaseInOutCubic => {
88 if t < 0.5 {
89 4.0 * t * t * t
90 } else {
91 let inv = -2.0 * t + 2.0;
92 1.0 - inv * inv * inv / 2.0
93 }
94 }
95 Easing::Bounce => {
96 let t = 1.0 - t;
97 let out = if t < 1.0 / 2.75 {
98 7.5625 * t * t
99 } else if t < 2.0 / 2.75 {
100 let t = t - 1.5 / 2.75;
101 7.5625 * t * t + 0.75
102 } else if t < 2.5 / 2.75 {
103 let t = t - 2.25 / 2.75;
104 7.5625 * t * t + 0.9375
105 } else {
106 let t = t - 2.625 / 2.75;
107 7.5625 * t * t + 0.984375
108 };
109 1.0 - out
110 }
111 Easing::Step(n) => {
112 if n == 0 {
113 t
114 } else {
115 let steps = n as f32;
116 ((t * steps) as u32 as f32) / steps
117 }
118 }
119 }
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
129pub enum LoopMode {
130 #[default]
132 Once,
133 Repeat(u16),
135 PingPong(u16),
137}
138
139pub(crate) fn loop_progress(elapsed: u32, duration_ms: u32, loop_mode: LoopMode) -> (f32, bool) {
145 if duration_ms == 0 {
146 return (1.0, true);
147 }
148 match loop_mode {
149 LoopMode::Once => {
150 let e = if elapsed > duration_ms {
151 duration_ms
152 } else {
153 elapsed
154 };
155 (e as f32 / duration_ms as f32, elapsed >= duration_ms)
156 }
157 LoopMode::Repeat(n) => {
158 let cycle = elapsed / duration_ms;
159 let finished = n > 0 && cycle >= n as u32;
160 if finished {
161 (1.0, true)
162 } else {
163 let within = elapsed % duration_ms;
164 (within as f32 / duration_ms as f32, false)
165 }
166 }
167 LoopMode::PingPong(n) => {
168 let half_cycle = elapsed / duration_ms;
170 let finished = n > 0 && half_cycle >= (n as u32) * 2;
171 if finished {
172 (0.0, true)
173 } else {
174 let within = elapsed % duration_ms;
175 let t = within as f32 / duration_ms as f32;
176 if half_cycle.is_multiple_of(2) {
177 (t, false)
178 } else {
179 (1.0 - t, false)
180 }
181 }
182 }
183 }
184}
185
186#[deprecated(note = "ms/wall-clock animation is superseded by tick-driven core::anim; see LPAR-06")]
196pub struct Fade {
197 style: *mut Style,
198 start: Color,
199 end: Color,
200 duration_ms: u32,
201 elapsed: u32,
202 easing: Easing,
203 loop_mode: LoopMode,
204}
205
206#[allow(deprecated)]
207impl Fade {
208 pub fn new(style: &mut Style, start: Color, end: Color, duration_ms: u32) -> Self {
210 Self {
211 style: style as *mut Style,
212 start,
213 end,
214 duration_ms,
215 elapsed: 0,
216 easing: Easing::Linear,
217 loop_mode: LoopMode::Once,
218 }
219 }
220
221 pub fn with_easing(mut self, easing: Easing) -> Self {
223 self.easing = easing;
224 self
225 }
226
227 pub fn with_loop(mut self, loop_mode: LoopMode) -> Self {
229 self.loop_mode = loop_mode;
230 self
231 }
232
233 pub fn tick(&mut self, delta_ms: u32) {
235 self.elapsed = self.elapsed.saturating_add(delta_ms);
236 let (raw_t, _) = loop_progress(self.elapsed, self.duration_ms, self.loop_mode);
237 let t = self.easing.apply(raw_t);
238 let lerp = |a: u8, b: u8| a as f32 + (b as f32 - a as f32) * t;
239 unsafe {
240 (*self.style).bg_color = Color(
241 lerp(self.start.0, self.end.0) as u8,
242 lerp(self.start.1, self.end.1) as u8,
243 lerp(self.start.2, self.end.2) as u8,
244 lerp(self.start.3, self.end.3) as u8,
245 );
246 }
247 }
248
249 pub fn finished(&self) -> bool {
251 let (_, done) = loop_progress(self.elapsed, self.duration_ms, self.loop_mode);
252 done
253 }
254}
255
256#[deprecated(note = "ms/wall-clock animation is superseded by tick-driven core::anim; see LPAR-06")]
262pub struct Slide {
263 rect: *mut Rect,
264 start: Rect,
265 end: Rect,
266 duration_ms: u32,
267 elapsed: u32,
268 easing: Easing,
269 loop_mode: LoopMode,
270}
271
272#[allow(deprecated)]
273impl Slide {
274 pub fn new(rect: &mut Rect, start: Rect, end: Rect, duration_ms: u32) -> Self {
276 Self {
277 rect: rect as *mut Rect,
278 start,
279 end,
280 duration_ms,
281 elapsed: 0,
282 easing: Easing::Linear,
283 loop_mode: LoopMode::Once,
284 }
285 }
286
287 pub fn with_easing(mut self, easing: Easing) -> Self {
289 self.easing = easing;
290 self
291 }
292
293 pub fn with_loop(mut self, loop_mode: LoopMode) -> Self {
295 self.loop_mode = loop_mode;
296 self
297 }
298
299 pub fn tick(&mut self, delta_ms: u32) {
301 self.elapsed = self.elapsed.saturating_add(delta_ms);
302 let (raw_t, _) = loop_progress(self.elapsed, self.duration_ms, self.loop_mode);
303 let t = self.easing.apply(raw_t);
304 let lerp = |a: i32, b: i32| a as f32 + (b as f32 - a as f32) * t;
305 unsafe {
306 *self.rect = Rect {
307 x: lerp(self.start.x, self.end.x) as i32,
308 y: lerp(self.start.y, self.end.y) as i32,
309 width: lerp(self.start.width, self.end.width) as i32,
310 height: lerp(self.start.height, self.end.height) as i32,
311 };
312 }
313 }
314
315 pub fn finished(&self) -> bool {
317 let (_, done) = loop_progress(self.elapsed, self.duration_ms, self.loop_mode);
318 done
319 }
320}
321
322#[deprecated(note = "ms/wall-clock animation is superseded by tick-driven core::anim; see LPAR-06")]
331pub struct Motion {
332 rect: *mut Rect,
333 start: Rect,
334 end: Rect,
335 duration_ms: u32,
336 elapsed: u32,
337 easing: Easing,
338 loop_mode: LoopMode,
339}
340
341#[allow(deprecated)]
342impl Motion {
343 pub fn new(rect: &mut Rect, start: Rect, end: Rect, duration_ms: u32) -> Self {
345 Self {
346 rect: rect as *mut Rect,
347 start,
348 end,
349 duration_ms,
350 elapsed: 0,
351 easing: Easing::Linear,
352 loop_mode: LoopMode::Once,
353 }
354 }
355
356 pub fn with_easing(mut self, easing: Easing) -> Self {
358 self.easing = easing;
359 self
360 }
361
362 pub fn with_loop(mut self, loop_mode: LoopMode) -> Self {
364 self.loop_mode = loop_mode;
365 self
366 }
367
368 pub fn tick(&mut self, delta_ms: u32) {
370 self.elapsed = self.elapsed.saturating_add(delta_ms);
371 let (raw_t, _) = loop_progress(self.elapsed, self.duration_ms, self.loop_mode);
372 let t = self.easing.apply(raw_t);
373 let lerp = |a: i32, b: i32| a as f32 + (b as f32 - a as f32) * t;
374 unsafe {
375 *self.rect = Rect {
376 x: lerp(self.start.x, self.end.x) as i32,
377 y: lerp(self.start.y, self.end.y) as i32,
378 width: lerp(self.start.width, self.end.width) as i32,
379 height: lerp(self.start.height, self.end.height) as i32,
380 };
381 }
382 }
383
384 pub fn finished(&self) -> bool {
386 let (_, done) = loop_progress(self.elapsed, self.duration_ms, self.loop_mode);
387 done
388 }
389}
390
391#[deprecated(note = "ms/wall-clock animation is superseded by tick-driven core::anim; see LPAR-06")]
400pub struct FadeTransition {
401 alpha: *mut u8,
402 start: u8,
403 end: u8,
404 duration_ms: u32,
405 elapsed: u32,
406 easing: Easing,
407 loop_mode: LoopMode,
408}
409
410#[allow(deprecated)]
411impl FadeTransition {
412 pub fn new(alpha: &mut u8, start: u8, end: u8, duration_ms: u32) -> Self {
414 Self {
415 alpha: alpha as *mut u8,
416 start,
417 end,
418 duration_ms,
419 elapsed: 0,
420 easing: Easing::Linear,
421 loop_mode: LoopMode::Once,
422 }
423 }
424
425 pub fn with_easing(mut self, easing: Easing) -> Self {
427 self.easing = easing;
428 self
429 }
430
431 pub fn with_loop(mut self, loop_mode: LoopMode) -> Self {
433 self.loop_mode = loop_mode;
434 self
435 }
436
437 pub fn tick(&mut self, delta_ms: u32) {
439 self.elapsed = self.elapsed.saturating_add(delta_ms);
440 let (raw_t, _) = loop_progress(self.elapsed, self.duration_ms, self.loop_mode);
441 let t = self.easing.apply(raw_t);
442 let v = self.start as f32 + (self.end as f32 - self.start as f32) * t;
443 unsafe {
444 *self.alpha = v as u8;
445 }
446 }
447
448 pub fn finished(&self) -> bool {
450 let (_, done) = loop_progress(self.elapsed, self.duration_ms, self.loop_mode);
451 done
452 }
453}
454
455#[derive(Debug, Clone, Copy)]
461pub struct AlphaKey {
462 pub time_ms: u32,
464 pub alpha: u8,
466 pub easing: Easing,
468}
469
470#[deprecated(note = "ms/wall-clock animation is superseded by tick-driven core::anim; see LPAR-06")]
476pub struct KeyFade {
477 alpha: *mut u8,
478 keys: alloc::vec::Vec<AlphaKey>,
479 total_ms: u32,
480 elapsed: u32,
481 loop_mode: LoopMode,
482}
483
484#[allow(deprecated)]
485impl KeyFade {
486 pub fn new(alpha: &mut u8) -> Self {
491 Self {
492 alpha: alpha as *mut u8,
493 keys: alloc::vec::Vec::new(),
494 total_ms: 0,
495 elapsed: 0,
496 loop_mode: LoopMode::Once,
497 }
498 }
499
500 pub fn key(mut self, time_ms: u32, alpha: u8, easing: Easing) -> Self {
508 self.keys.push(AlphaKey {
509 time_ms,
510 alpha,
511 easing,
512 });
513 if time_ms > self.total_ms {
514 self.total_ms = time_ms;
515 }
516 self
517 }
518
519 pub fn with_loop(mut self, loop_mode: LoopMode) -> Self {
521 self.loop_mode = loop_mode;
522 self
523 }
524
525 pub fn tick(&mut self, delta_ms: u32) {
527 if self.keys.len() < 2 {
528 return;
529 }
530 self.elapsed = self.elapsed.saturating_add(delta_ms);
531 let (raw_t, _) = loop_progress(self.elapsed, self.total_ms, self.loop_mode);
532 let time = raw_t * self.total_ms as f32;
533
534 let mut k0 = &self.keys[0];
536 let mut k1 = &self.keys[1];
537 for i in 1..self.keys.len() {
538 if self.keys[i].time_ms as f32 >= time {
539 k1 = &self.keys[i];
540 k0 = &self.keys[i - 1];
541 break;
542 }
543 k0 = &self.keys[i - 1];
545 k1 = &self.keys[i];
546 }
547
548 let seg_dur = k1.time_ms as f32 - k0.time_ms as f32;
549 let local_t = if seg_dur > 0.0 {
550 (time - k0.time_ms as f32) / seg_dur
551 } else {
552 1.0
553 };
554 let eased = k0.easing.apply(local_t);
555 let v = k0.alpha as f32 + (k1.alpha as f32 - k0.alpha as f32) * eased;
556 unsafe {
557 *self.alpha = v as u8;
558 }
559 }
560
561 pub fn finished(&self) -> bool {
563 if self.keys.len() < 2 {
564 return true;
565 }
566 let (_, done) = loop_progress(self.elapsed, self.total_ms, self.loop_mode);
567 done
568 }
569}
570
571#[allow(deprecated)]
577#[deprecated(note = "ms/wall-clock animation is superseded by tick-driven core::anim; see LPAR-06")]
578pub struct Timeline {
579 fades: alloc::vec::Vec<Fade>,
580 slides: alloc::vec::Vec<Slide>,
581 motions: alloc::vec::Vec<Motion>,
582 fade_transitions: alloc::vec::Vec<FadeTransition>,
583 key_fades: alloc::vec::Vec<KeyFade>,
584}
585
586#[allow(deprecated)]
587impl Timeline {
588 pub fn new() -> Self {
590 Self {
591 fades: alloc::vec::Vec::new(),
592 slides: alloc::vec::Vec::new(),
593 motions: alloc::vec::Vec::new(),
594 fade_transitions: alloc::vec::Vec::new(),
595 key_fades: alloc::vec::Vec::new(),
596 }
597 }
598
599 pub fn add_fade(&mut self, fade: Fade) {
601 self.fades.push(fade);
602 }
603
604 pub fn add_slide(&mut self, slide: Slide) {
606 self.slides.push(slide);
607 }
608
609 pub fn add_motion(&mut self, motion: Motion) {
611 self.motions.push(motion);
612 }
613
614 pub fn add_fade_transition(&mut self, ft: FadeTransition) {
616 self.fade_transitions.push(ft);
617 }
618
619 pub fn add_key_fade(&mut self, kf: KeyFade) {
621 self.key_fades.push(kf);
622 }
623
624 pub fn tick(&mut self, delta_ms: u32) {
626 for f in &mut self.fades {
627 f.tick(delta_ms);
628 }
629 for s in &mut self.slides {
630 s.tick(delta_ms);
631 }
632 for m in &mut self.motions {
633 m.tick(delta_ms);
634 }
635 for ft in &mut self.fade_transitions {
636 ft.tick(delta_ms);
637 }
638 for kf in &mut self.key_fades {
639 kf.tick(delta_ms);
640 }
641 self.fades.retain(|f| !f.finished());
642 self.slides.retain(|s| !s.finished());
643 self.motions.retain(|m| !m.finished());
644 self.fade_transitions.retain(|ft| !ft.finished());
645 self.key_fades.retain(|kf| !kf.finished());
646 }
647
648 pub fn is_empty(&self) -> bool {
650 self.fades.is_empty()
651 && self.slides.is_empty()
652 && self.motions.is_empty()
653 && self.fade_transitions.is_empty()
654 && self.key_fades.is_empty()
655 }
656}
657
658#[allow(deprecated)]
659impl Default for Timeline {
660 fn default() -> Self {
661 Self::new()
662 }
663}