1use crate::loop_mode::Loop;
4use animato_core::{Animatable, Easing, Playable, Update};
5
6#[derive(Clone, Debug, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum TweenState {
10 Idle,
12 Running,
14 Paused,
16 Completed,
18}
19
20#[derive(Clone, Debug, PartialEq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct TweenSnapshot {
27 pub elapsed: f32,
29 pub delay_elapsed: f32,
31 pub loop_count: u32,
33 pub ping_pong_reverse: bool,
35 pub state: TweenState,
37}
38
39#[derive(Clone, Debug)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59pub struct Tween<T: Animatable> {
60 pub start: T,
62 pub end: T,
64 pub duration: f32,
66 pub easing: Easing,
68 pub delay: f32,
70 pub time_scale: f32,
72 pub looping: Loop,
74
75 elapsed: f32,
77 delay_elapsed: f32,
78 state: TweenState,
79 loop_count: u32,
80 ping_pong_reverse: bool,
82}
83
84impl<T: Animatable> Tween<T> {
85 #[doc(hidden)]
89 pub(crate) fn from_builder(
90 start: T,
91 end: T,
92 duration: f32,
93 easing: Easing,
94 delay: f32,
95 time_scale: f32,
96 looping: Loop,
97 ) -> Self {
98 let initial_state = if delay > 0.0 {
99 TweenState::Idle
100 } else {
101 TweenState::Running
102 };
103 Self {
104 start,
105 end,
106 duration: duration.max(0.0),
107 easing,
108 delay: delay.max(0.0),
109 time_scale: time_scale.max(0.0),
110 looping,
111 elapsed: 0.0,
112 delay_elapsed: 0.0,
113 state: initial_state,
114 loop_count: 0,
115 ping_pong_reverse: false,
116 }
117 }
118
119 pub fn value(&self) -> T {
133 if self.duration == 0.0 {
134 return self.end.clone();
135 }
136 let raw_t = (self.elapsed / self.duration).clamp(0.0, 1.0);
137 let curved_t = self.easing.apply(raw_t);
138 if self.ping_pong_reverse {
139 self.end.lerp(&self.start, curved_t)
140 } else {
141 self.start.lerp(&self.end, curved_t)
142 }
143 }
144
145 pub fn progress(&self) -> f32 {
147 if self.duration == 0.0 {
148 return 1.0;
149 }
150 (self.elapsed / self.duration).clamp(0.0, 1.0)
151 }
152
153 pub fn eased_progress(&self) -> f32 {
155 self.easing.apply(self.progress())
156 }
157
158 pub fn is_complete(&self) -> bool {
160 self.state == TweenState::Completed
161 }
162
163 pub fn state(&self) -> &TweenState {
165 &self.state
166 }
167
168 pub fn elapsed(&self) -> f32 {
170 self.elapsed
171 }
172
173 pub fn delay_elapsed(&self) -> f32 {
175 self.delay_elapsed
176 }
177
178 pub fn loop_count(&self) -> u32 {
180 self.loop_count
181 }
182
183 pub fn is_ping_pong_reversed(&self) -> bool {
185 self.ping_pong_reverse
186 }
187
188 pub fn snapshot(&self) -> TweenSnapshot {
190 TweenSnapshot {
191 elapsed: self.elapsed,
192 delay_elapsed: self.delay_elapsed,
193 loop_count: self.loop_count,
194 ping_pong_reverse: self.ping_pong_reverse,
195 state: self.state.clone(),
196 }
197 }
198
199 pub fn reset(&mut self) {
201 self.elapsed = 0.0;
202 self.delay_elapsed = 0.0;
203 self.loop_count = 0;
204 self.ping_pong_reverse = false;
205 self.state = if self.delay > 0.0 {
206 TweenState::Idle
207 } else {
208 TweenState::Running
209 };
210 }
211
212 pub fn seek(&mut self, t: f32) {
216 self.elapsed = (t.clamp(0.0, 1.0) * self.duration).max(0.0);
217 if self.state == TweenState::Completed {
218 self.state = TweenState::Running;
219 }
220 }
221
222 pub fn reverse(&mut self) {
247 core::mem::swap(&mut self.start, &mut self.end);
248 self.elapsed = (self.duration - self.elapsed).clamp(0.0, self.duration);
251 if self.state == TweenState::Completed {
252 self.state = TweenState::Running;
253 }
254 }
255
256 pub fn pause(&mut self) {
258 if self.state == TweenState::Running {
259 self.state = TweenState::Paused;
260 }
261 }
262
263 pub fn resume(&mut self) {
265 if self.state == TweenState::Paused {
266 self.state = TweenState::Running;
267 }
268 }
269
270 #[inline]
271 fn playback_duration(&self) -> f32 {
272 match self.looping {
273 Loop::Once => self.delay + self.duration,
274 Loop::Times(n) => self.delay + self.duration * n.max(1) as f32,
275 Loop::Forever | Loop::PingPong => f32::INFINITY,
276 }
277 }
278}
279
280impl<T: Animatable> Update for Tween<T> {
281 fn update(&mut self, dt: f32) -> bool {
286 let dt = dt.max(0.0);
287
288 match self.state {
289 TweenState::Completed => return false,
290 TweenState::Paused => return true,
291 TweenState::Idle => {
292 self.delay_elapsed += dt;
294 if self.delay_elapsed < self.delay {
295 return true;
296 }
297 let overflow = self.delay_elapsed - self.delay;
299 self.state = TweenState::Running;
300 self.elapsed += overflow * self.time_scale;
301 }
302 TweenState::Running => {
303 self.elapsed += dt * self.time_scale;
304 }
305 }
306
307 if self.duration == 0.0 {
309 self.state = TweenState::Completed;
310 return false;
311 }
312
313 while self.elapsed >= self.duration {
315 match &self.looping {
316 Loop::Once => {
317 self.elapsed = self.duration;
318 self.state = TweenState::Completed;
319 return false;
320 }
321 Loop::Times(n) => {
322 self.loop_count += 1;
323 if self.loop_count >= *n {
324 self.elapsed = self.duration;
325 self.state = TweenState::Completed;
326 return false;
327 }
328 self.elapsed -= self.duration;
329 }
330 Loop::Forever => {
331 self.elapsed -= self.duration;
332 }
333 Loop::PingPong => {
334 self.elapsed -= self.duration;
335 self.ping_pong_reverse = !self.ping_pong_reverse;
336 }
337 }
338 }
339
340 true
341 }
342}
343
344impl<T: Animatable> Playable for Tween<T> {
345 fn duration(&self) -> f32 {
346 self.playback_duration()
347 }
348
349 fn reset(&mut self) {
350 Tween::reset(self);
351 }
352
353 fn seek_to(&mut self, progress: f32) {
354 let progress = progress.clamp(0.0, 1.0);
355 let total = self.playback_duration();
356 let finite_total = if total.is_finite() {
357 total
358 } else {
359 self.delay + self.duration
360 };
361
362 Tween::reset(self);
363
364 if finite_total == 0.0 {
365 self.elapsed = self.duration;
366 self.state = TweenState::Completed;
367 return;
368 }
369
370 let secs = finite_total * progress;
371 if secs < self.delay {
372 self.delay_elapsed = secs;
373 self.state = if self.delay > 0.0 {
374 TweenState::Idle
375 } else {
376 TweenState::Running
377 };
378 return;
379 }
380
381 let anim_secs = (secs - self.delay).max(0.0);
382 if self.duration == 0.0 {
383 self.elapsed = 0.0;
384 self.state = TweenState::Completed;
385 return;
386 }
387
388 match self.looping {
389 Loop::Once => {
390 self.elapsed = anim_secs.min(self.duration);
391 self.state = if progress >= 1.0 {
392 TweenState::Completed
393 } else {
394 TweenState::Running
395 };
396 }
397 Loop::Times(n) => {
398 let plays = n.max(1);
399 let total_anim = self.duration * plays as f32;
400 if anim_secs >= total_anim || progress >= 1.0 {
401 self.loop_count = plays;
402 self.elapsed = self.duration;
403 self.state = TweenState::Completed;
404 } else {
405 self.loop_count = (anim_secs / self.duration) as u32;
406 self.elapsed = anim_secs - self.duration * self.loop_count as f32;
407 self.state = TweenState::Running;
408 }
409 }
410 Loop::Forever => {
411 self.elapsed = anim_secs % self.duration;
412 self.state = TweenState::Running;
413 }
414 Loop::PingPong => {
415 let cycle = anim_secs % (self.duration * 2.0);
416 self.ping_pong_reverse = cycle >= self.duration;
417 self.elapsed = if self.ping_pong_reverse {
418 cycle - self.duration
419 } else {
420 cycle
421 };
422 self.state = TweenState::Running;
423 }
424 }
425 }
426
427 fn is_complete(&self) -> bool {
428 Tween::is_complete(self)
429 }
430
431 fn as_any(&self) -> &dyn core::any::Any {
432 self
433 }
434
435 fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
436 self
437 }
438}
439
440#[cfg(test)]
445mod tests {
446 use super::*;
447 use crate::loop_mode::Loop;
448 use animato_core::Easing;
449
450 fn make(start: f32, end: f32, duration: f32) -> Tween<f32> {
451 Tween::new(start, end).duration(duration).build()
452 }
453
454 #[test]
455 fn value_at_start_equals_start() {
456 let t = make(10.0, 90.0, 2.0);
457 assert_eq!(t.value(), 10.0);
458 }
459
460 #[test]
461 fn value_at_end_equals_end() {
462 let mut t = make(10.0, 90.0, 1.0);
463 t.update(1.0);
464 assert_eq!(t.value(), 90.0);
465 }
466
467 #[test]
468 fn is_complete_after_full_duration() {
469 let mut t = make(0.0, 1.0, 1.0);
470 t.update(1.0);
471 assert!(t.is_complete());
472 }
473
474 #[test]
475 fn large_dt_completes_cleanly() {
476 let mut t = make(0.0, 1.0, 1.0);
477 t.update(100.0);
478 assert!(t.is_complete());
479 assert_eq!(t.value(), 1.0);
480 }
481
482 #[test]
483 fn no_update_after_complete() {
484 let mut t = make(0.0, 1.0, 0.5);
485 t.update(1.0);
486 assert!(!t.update(1.0)); }
488
489 #[test]
490 fn delay_holds_at_start() {
491 let mut t = Tween::new(0.0_f32, 100.0).duration(1.0).delay(0.5).build();
492 t.update(0.25); assert_eq!(t.value(), 0.0);
494 assert_eq!(t.state(), &TweenState::Idle);
495 }
496
497 #[test]
498 fn delay_transitions_to_running() {
499 let mut t = Tween::new(0.0_f32, 100.0).duration(1.0).delay(0.5).build();
500 t.update(0.5); assert_eq!(t.state(), &TweenState::Running);
502 }
503
504 #[test]
505 fn seek_jumps_to_midpoint() {
506 let mut t = make(0.0, 100.0, 1.0);
507 t.seek(0.5);
508 let t2 = Tween::new(0.0_f32, 100.0)
510 .duration(1.0)
511 .easing(Easing::Linear)
512 .build();
513 let mut t2 = t2;
514 t2.seek(0.5);
515 assert!((t2.value() - 50.0).abs() < 0.01);
516 }
517
518 #[test]
519 fn reverse_swaps_direction() {
520 let mut t = Tween::new(0.0_f32, 100.0)
521 .duration(1.0)
522 .easing(Easing::Linear)
523 .build();
524 t.update(0.4);
526 let before = t.value(); t.reverse();
529 assert!(
531 (t.value() - before).abs() < 1.0,
532 "visual position should be preserved: before={} after={}",
533 before,
534 t.value()
535 );
536 t.update(0.1);
538 assert!(t.value() < before, "value should decrease after reverse");
539 }
540
541 #[test]
542 fn pause_stops_progress() {
543 let mut t = make(0.0, 1.0, 2.0);
544 t.update(0.5);
545 let v_before = t.value();
546 t.pause();
547 t.update(0.5); assert_eq!(t.value(), v_before);
549 }
550
551 #[test]
552 fn resume_continues_progress() {
553 let mut t = make(0.0, 1.0, 2.0);
554 t.update(0.5);
555 t.pause();
556 t.update(0.5); let v_paused = t.value();
558 t.resume();
559 t.update(0.5); assert!(
561 t.value() > v_paused,
562 "resumed tween must advance past v_paused={}",
563 v_paused
564 );
565 }
566
567 #[test]
568 fn loop_times_completes_after_n() {
569 let mut t = Tween::new(0.0_f32, 1.0)
570 .duration(1.0)
571 .looping(Loop::Times(3))
572 .build();
573 t.update(3.0 + f32::EPSILON);
575 assert!(t.is_complete());
576 }
577
578 #[test]
579 fn loop_forever_never_completes() {
580 let mut t = Tween::new(0.0_f32, 1.0)
581 .duration(1.0)
582 .looping(Loop::Forever)
583 .build();
584 for _ in 0..1000 {
585 t.update(0.1);
586 }
587 assert!(!t.is_complete());
588 }
589
590 #[test]
591 fn pingpong_reverses_direction() {
592 let mut t = Tween::new(0.0_f32, 100.0)
593 .duration(1.0)
594 .easing(Easing::Linear)
595 .looping(Loop::PingPong)
596 .build();
597 t.update(1.0);
599 t.update(0.5);
601 let v = t.value();
602 assert!(v > 40.0 && v < 60.0, "pingpong mid-reverse = {}", v);
603 }
604
605 #[test]
606 fn reset_returns_to_idle_with_delay() {
607 let mut t = Tween::new(0.0_f32, 1.0).duration(1.0).delay(0.5).build();
608 t.update(2.0); t.reset();
610 assert_eq!(t.state(), &TweenState::Idle);
611 assert_eq!(t.value(), 0.0);
612 }
613
614 #[test]
615 fn zero_duration_completes_immediately() {
616 let mut t = make(0.0, 100.0, 0.0);
617 t.update(0.0);
618 assert!(t.is_complete());
619 assert_eq!(t.value(), 100.0);
620 }
621
622 #[test]
623 fn negative_dt_is_noop() {
624 let mut t = make(0.0, 100.0, 1.0);
625 t.update(-5.0);
626 assert_eq!(t.value(), 0.0);
627 }
628
629 #[test]
630 fn accessors_snapshot_and_eased_progress_are_current() {
631 let mut t = Tween::new(0.0_f32, 100.0)
632 .duration(2.0)
633 .delay(0.5)
634 .easing(Easing::EaseInQuad)
635 .build();
636
637 t.update(0.25);
638 assert_eq!(t.progress(), 0.0);
639 assert_eq!(t.eased_progress(), 0.0);
640 assert_eq!(t.elapsed(), 0.0);
641 assert_eq!(t.delay_elapsed(), 0.25);
642 assert_eq!(t.loop_count(), 0);
643 assert!(!t.is_ping_pong_reversed());
644
645 let snapshot = t.snapshot();
646 assert_eq!(snapshot.delay_elapsed, 0.25);
647 assert_eq!(snapshot.state, TweenState::Idle);
648 }
649
650 #[test]
651 fn seek_and_reverse_restart_completed_tween() {
652 let mut t = make(0.0, 100.0, 1.0);
653
654 t.update(1.0);
655 assert!(t.is_complete());
656 t.seek(0.25);
657 assert_eq!(t.state(), &TweenState::Running);
658 assert_eq!(t.value(), 25.0);
659
660 t.update(1.0);
661 t.reverse();
662 assert_eq!(t.state(), &TweenState::Running);
663 assert_eq!(t.value(), 100.0);
664 }
665
666 #[test]
667 fn playables_seek_cover_delay_looping_and_downcast() {
668 let mut delayed = Tween::new(0.0_f32, 100.0).duration(1.0).delay(1.0).build();
669 Playable::seek_to(&mut delayed, 0.25);
670 assert_eq!(delayed.state(), &TweenState::Idle);
671 assert_eq!(delayed.delay_elapsed(), 0.5);
672
673 let mut times = Tween::new(0.0_f32, 10.0)
674 .duration(1.0)
675 .looping(Loop::Times(3))
676 .build();
677 Playable::seek_to(&mut times, 0.5);
678 assert_eq!(times.loop_count(), 1);
679 assert_eq!(times.state(), &TweenState::Running);
680 Playable::seek_to(&mut times, 1.0);
681 assert!(Playable::is_complete(×));
682
683 let mut forever = Tween::new(0.0_f32, 10.0)
684 .duration(1.0)
685 .looping(Loop::Forever)
686 .build();
687 Playable::seek_to(&mut forever, 0.75);
688 assert_eq!(forever.value(), 7.5);
689
690 let mut ping_pong = Tween::new(0.0_f32, 10.0)
691 .duration(1.0)
692 .looping(Loop::PingPong)
693 .build();
694 Playable::seek_to(&mut ping_pong, 1.0);
695 assert!(ping_pong.is_ping_pong_reversed());
696 assert_eq!(ping_pong.value(), 10.0);
697
698 assert_eq!(Playable::duration(×), 3.0);
699 assert!(Playable::as_any(×).is::<Tween<f32>>());
700 assert!(Playable::as_any_mut(&mut times).is::<Tween<f32>>());
701 Playable::reset(&mut times);
702 assert_eq!(times.state(), &TweenState::Running);
703 }
704
705 #[test]
706 fn playable_seek_handles_zero_duration_and_delay_boundary() {
707 let mut zero = make(0.0, 1.0, 0.0);
708 Playable::seek_to(&mut zero, 0.5);
709 assert_eq!(zero.state(), &TweenState::Completed);
710 assert_eq!(zero.value(), 1.0);
711
712 let mut delayed = Tween::new(0.0_f32, 1.0).duration(1.0).delay(1.0).build();
713 Playable::seek_to(&mut delayed, 0.5);
714 assert_eq!(delayed.state(), &TweenState::Running);
715 assert_eq!(delayed.value(), 0.0);
716 }
717}