1#![allow(dead_code)]
8use crate::WasmError;
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum Easing {
139 Linear,
141
142 QuadraticIn,
144 QuadraticOut,
146 QuadraticInOut,
148
149 CubicIn,
151 CubicOut,
153 CubicInOut,
155
156 QuarticIn,
158 QuarticOut,
160 QuarticInOut,
162
163 QuinticIn,
165 QuinticOut,
167 QuinticInOut,
169
170 SineIn,
172 SineOut,
174 SineInOut,
176
177 ExponentialIn,
179 ExponentialOut,
181 ExponentialInOut,
183
184 CircularIn,
186 CircularOut,
188 CircularInOut,
190
191 ElasticIn,
193 ElasticOut,
195 ElasticInOut,
197
198 BackIn,
200 BackOut,
202 BackInOut,
204
205 BounceIn,
207 BounceOut,
209 BounceInOut,
211}
212
213pub trait EasingFunction {
215 fn apply(&self, t: f64) -> f64;
217}
218
219impl EasingFunction for Easing {
220 fn apply(&self, t: f64) -> f64 {
221 let t = t.clamp(0.0, 1.0);
222
223 match self {
224 Easing::Linear => t,
225
226 Easing::QuadraticIn => t * t,
227 Easing::QuadraticOut => t * (2.0 - t),
228 Easing::QuadraticInOut => {
229 if t < 0.5 {
230 2.0 * t * t
231 } else {
232 -1.0 + (4.0 - 2.0 * t) * t
233 }
234 }
235
236 Easing::CubicIn => t * t * t,
237 Easing::CubicOut => {
238 let t = t - 1.0;
239 t * t * t + 1.0
240 }
241 Easing::CubicInOut => {
242 if t < 0.5 {
243 4.0 * t * t * t
244 } else {
245 let t = 2.0 * t - 2.0;
246 1.0 + t * t * t / 2.0
247 }
248 }
249
250 Easing::QuarticIn => t * t * t * t,
251 Easing::QuarticOut => {
252 let t = t - 1.0;
253 1.0 - t * t * t * t
254 }
255 Easing::QuarticInOut => {
256 if t < 0.5 {
257 8.0 * t * t * t * t
258 } else {
259 let t = 2.0 * t - 2.0;
260 1.0 - t * t * t * t / 2.0
261 }
262 }
263
264 Easing::QuinticIn => t * t * t * t * t,
265 Easing::QuinticOut => {
266 let t = t - 1.0;
267 t * t * t * t * t + 1.0
268 }
269 Easing::QuinticInOut => {
270 if t < 0.5 {
271 16.0 * t * t * t * t * t
272 } else {
273 let t = 2.0 * t - 2.0;
274 1.0 + t * t * t * t * t / 2.0
275 }
276 }
277
278 Easing::SineIn => 1.0 - (t * std::f64::consts::PI / 2.0).cos(),
279 Easing::SineOut => (t * std::f64::consts::PI / 2.0).sin(),
280 Easing::SineInOut => -0.5 * ((std::f64::consts::PI * t).cos() - 1.0),
281
282 Easing::ExponentialIn => {
283 if t == 0.0 {
284 0.0
285 } else {
286 2.0_f64.powf(10.0 * (t - 1.0))
287 }
288 }
289 Easing::ExponentialOut => {
290 if t == 1.0 {
291 1.0
292 } else {
293 1.0 - 2.0_f64.powf(-10.0 * t)
294 }
295 }
296 Easing::ExponentialInOut => {
297 if t == 0.0 || t == 1.0 {
298 t
299 } else if t < 0.5 {
300 0.5 * 2.0_f64.powf(20.0 * t - 10.0)
301 } else {
302 1.0 - 0.5 * 2.0_f64.powf(-20.0 * t + 10.0)
303 }
304 }
305
306 Easing::CircularIn => 1.0 - (1.0 - t * t).sqrt(),
307 Easing::CircularOut => (2.0 * t - t * t).sqrt(),
308 Easing::CircularInOut => {
309 if t < 0.5 {
310 0.5 * (1.0 - (1.0 - 4.0 * t * t).sqrt())
311 } else {
312 0.5 * ((2.0 * t - 1.0) * (3.0 - 2.0 * t) * 4.0).sqrt() + 0.5
313 }
314 }
315
316 Easing::ElasticIn => {
317 if t == 0.0 || t == 1.0 {
318 t
319 } else {
320 let p = 0.3;
321 let s = p / 4.0;
322 let t = t - 1.0;
323 -(2.0_f64.powf(10.0 * t)) * ((t - s) * (2.0 * std::f64::consts::PI) / p).sin()
324 }
325 }
326 Easing::ElasticOut => {
327 if t == 0.0 || t == 1.0 {
328 t
329 } else {
330 let p = 0.3;
331 let s = p / 4.0;
332 2.0_f64.powf(-10.0 * t) * ((t - s) * (2.0 * std::f64::consts::PI) / p).sin()
333 + 1.0
334 }
335 }
336 Easing::ElasticInOut => {
337 if t == 0.0 || t == 1.0 {
338 t
339 } else {
340 let p = 0.45;
341 let s = p / 4.0;
342
343 if t < 0.5 {
344 let t = 2.0 * t - 1.0;
345 -0.5 * 2.0_f64.powf(10.0 * t)
346 * ((t - s) * (2.0 * std::f64::consts::PI) / p).sin()
347 } else {
348 let t = 2.0 * t - 1.0;
349 0.5 * 2.0_f64.powf(-10.0 * t)
350 * ((t - s) * (2.0 * std::f64::consts::PI) / p).sin()
351 + 1.0
352 }
353 }
354 }
355
356 Easing::BackIn => {
357 let c1 = 1.70158;
358 let c3 = c1 + 1.0;
359 c3 * t * t * t - c1 * t * t
360 }
361 Easing::BackOut => {
362 let c1 = 1.70158;
363 let c3 = c1 + 1.0;
364 let t = t - 1.0;
365 1.0 + c3 * t * t * t + c1 * t * t
366 }
367 Easing::BackInOut => {
368 let c1 = 1.70158;
369 let c2 = c1 * 1.525;
370
371 if t < 0.5 {
372 let t = 2.0 * t;
373 (t * t * ((c2 + 1.0) * t - c2)) / 2.0
374 } else {
375 let t = 2.0 * t - 2.0;
376 (t * t * ((c2 + 1.0) * t + c2) + 2.0) / 2.0
377 }
378 }
379
380 Easing::BounceIn => 1.0 - Easing::BounceOut.apply(1.0 - t),
381 Easing::BounceOut => {
382 let n1 = 7.5625;
383 let d1 = 2.75;
384
385 if t < 1.0 / d1 {
386 n1 * t * t
387 } else if t < 2.0 / d1 {
388 let t = t - 1.5 / d1;
389 n1 * t * t + 0.75
390 } else if t < 2.5 / d1 {
391 let t = t - 2.25 / d1;
392 n1 * t * t + 0.9375
393 } else {
394 let t = t - 2.625 / d1;
395 n1 * t * t + 0.984375
396 }
397 }
398 Easing::BounceInOut => {
399 if t < 0.5 {
400 0.5 * Easing::BounceIn.apply(2.0 * t)
401 } else {
402 0.5 * Easing::BounceOut.apply(2.0 * t - 1.0) + 0.5
403 }
404 }
405 }
406 }
407}
408
409#[derive(Debug, Clone)]
411pub struct Animation {
412 start_value: f64,
413 end_value: f64,
414 duration_ms: f64,
415 elapsed_ms: f64,
416 easing: Easing,
417}
418
419impl Animation {
420 pub fn new(start_value: f64, end_value: f64, duration_ms: f64, easing: Easing) -> Self {
422 Self {
423 start_value,
424 end_value,
425 duration_ms,
426 elapsed_ms: 0.0,
427 easing,
428 }
429 }
430
431 pub fn update(&mut self, delta_ms: f64) {
433 self.elapsed_ms += delta_ms;
434 if self.elapsed_ms > self.duration_ms {
435 self.elapsed_ms = self.duration_ms;
436 }
437 }
438
439 pub fn current_value(&self) -> f64 {
441 let t = if self.duration_ms > 0.0 {
442 self.elapsed_ms / self.duration_ms
443 } else {
444 1.0
445 };
446
447 let eased_t = self.easing.apply(t);
448 self.start_value + (self.end_value - self.start_value) * eased_t
449 }
450
451 pub fn is_complete(&self) -> bool {
453 self.elapsed_ms >= self.duration_ms
454 }
455
456 pub fn progress(&self) -> f64 {
458 if self.duration_ms > 0.0 {
459 (self.elapsed_ms / self.duration_ms).min(1.0)
460 } else {
461 1.0
462 }
463 }
464
465 pub fn reset(&mut self) {
467 self.elapsed_ms = 0.0;
468 }
469
470 pub fn reverse(&mut self) {
472 std::mem::swap(&mut self.start_value, &mut self.end_value);
473 self.elapsed_ms = 0.0;
474 }
475}
476
477#[derive(Debug, Clone)]
479pub struct PanAnimation {
480 start_pos: (f64, f64),
481 end_pos: (f64, f64),
482 duration_ms: f64,
483 elapsed_ms: f64,
484 easing: Easing,
485}
486
487impl PanAnimation {
488 pub fn new(
490 start_pos: (f64, f64),
491 end_pos: (f64, f64),
492 duration_ms: f64,
493 easing: Easing,
494 ) -> Self {
495 Self {
496 start_pos,
497 end_pos,
498 duration_ms,
499 elapsed_ms: 0.0,
500 easing,
501 }
502 }
503
504 pub fn update(&mut self, delta_ms: f64) {
506 self.elapsed_ms += delta_ms;
507 if self.elapsed_ms > self.duration_ms {
508 self.elapsed_ms = self.duration_ms;
509 }
510 }
511
512 pub fn current_position(&self) -> (f64, f64) {
514 let t = if self.duration_ms > 0.0 {
515 self.elapsed_ms / self.duration_ms
516 } else {
517 1.0
518 };
519
520 let eased_t = self.easing.apply(t);
521 let x = self.start_pos.0 + (self.end_pos.0 - self.start_pos.0) * eased_t;
522 let y = self.start_pos.1 + (self.end_pos.1 - self.start_pos.1) * eased_t;
523
524 (x, y)
525 }
526
527 pub fn is_complete(&self) -> bool {
529 self.elapsed_ms >= self.duration_ms
530 }
531
532 pub fn progress(&self) -> f64 {
534 if self.duration_ms > 0.0 {
535 (self.elapsed_ms / self.duration_ms).min(1.0)
536 } else {
537 1.0
538 }
539 }
540}
541
542#[derive(Debug, Clone)]
544pub struct ZoomAnimation {
545 start_zoom: f64,
546 end_zoom: f64,
547 focal_point: (f64, f64),
548 duration_ms: f64,
549 elapsed_ms: f64,
550 easing: Easing,
551}
552
553impl ZoomAnimation {
554 pub fn new(
556 start_zoom: f64,
557 end_zoom: f64,
558 focal_point: (f64, f64),
559 duration_ms: f64,
560 easing: Easing,
561 ) -> Self {
562 Self {
563 start_zoom,
564 end_zoom,
565 focal_point,
566 duration_ms,
567 elapsed_ms: 0.0,
568 easing,
569 }
570 }
571
572 pub fn update(&mut self, delta_ms: f64) {
574 self.elapsed_ms += delta_ms;
575 if self.elapsed_ms > self.duration_ms {
576 self.elapsed_ms = self.duration_ms;
577 }
578 }
579
580 pub fn current_zoom(&self) -> f64 {
582 let t = if self.duration_ms > 0.0 {
583 self.elapsed_ms / self.duration_ms
584 } else {
585 1.0
586 };
587
588 let eased_t = self.easing.apply(t);
589 self.start_zoom + (self.end_zoom - self.start_zoom) * eased_t
590 }
591
592 pub fn focal_point(&self) -> (f64, f64) {
594 self.focal_point
595 }
596
597 pub fn is_complete(&self) -> bool {
599 self.elapsed_ms >= self.duration_ms
600 }
601
602 pub fn progress(&self) -> f64 {
604 if self.duration_ms > 0.0 {
605 (self.elapsed_ms / self.duration_ms).min(1.0)
606 } else {
607 1.0
608 }
609 }
610}
611
612#[derive(Debug, Clone)]
614pub struct SpringAnimation {
615 current: f64,
616 target: f64,
617 velocity: f64,
618 stiffness: f64,
619 damping: f64,
620 threshold: f64,
621}
622
623impl SpringAnimation {
624 pub fn new(current: f64, target: f64, stiffness: f64, damping: f64) -> Self {
631 Self {
632 current,
633 target,
634 velocity: 0.0,
635 stiffness: stiffness.clamp(0.0, 1.0),
636 damping: damping.clamp(0.0, 1.0),
637 threshold: 0.01,
638 }
639 }
640
641 pub fn update(&mut self, delta_ms: f64) {
643 let delta_s = delta_ms / 1000.0; let spring_force = (self.target - self.current) * self.stiffness;
647
648 let damping_force = -self.velocity * self.damping;
650
651 self.velocity += (spring_force + damping_force) * delta_s;
653 self.current += self.velocity * delta_s;
654 }
655
656 pub fn current_value(&self) -> f64 {
658 self.current
659 }
660
661 pub fn is_settled(&self) -> bool {
663 (self.current - self.target).abs() < self.threshold && self.velocity.abs() < self.threshold
664 }
665
666 pub fn set_target(&mut self, target: f64) {
668 self.target = target;
669 }
670
671 pub fn set_threshold(&mut self, threshold: f64) {
673 self.threshold = threshold;
674 }
675}
676
677pub struct AnimationSequence {
679 animations: Vec<Box<dyn AnimationTrait>>,
680 current_index: usize,
681}
682
683pub trait AnimationTrait {
685 fn update(&mut self, delta_ms: f64);
687
688 fn is_complete(&self) -> bool;
690
691 fn progress(&self) -> f64;
693}
694
695impl AnimationTrait for Animation {
696 fn update(&mut self, delta_ms: f64) {
697 Animation::update(self, delta_ms);
698 }
699
700 fn is_complete(&self) -> bool {
701 Animation::is_complete(self)
702 }
703
704 fn progress(&self) -> f64 {
705 Animation::progress(self)
706 }
707}
708
709impl AnimationTrait for PanAnimation {
710 fn update(&mut self, delta_ms: f64) {
711 PanAnimation::update(self, delta_ms);
712 }
713
714 fn is_complete(&self) -> bool {
715 PanAnimation::is_complete(self)
716 }
717
718 fn progress(&self) -> f64 {
719 PanAnimation::progress(self)
720 }
721}
722
723impl AnimationTrait for ZoomAnimation {
724 fn update(&mut self, delta_ms: f64) {
725 ZoomAnimation::update(self, delta_ms);
726 }
727
728 fn is_complete(&self) -> bool {
729 ZoomAnimation::is_complete(self)
730 }
731
732 fn progress(&self) -> f64 {
733 ZoomAnimation::progress(self)
734 }
735}
736
737impl AnimationSequence {
738 pub fn new() -> Self {
740 Self {
741 animations: Vec::new(),
742 current_index: 0,
743 }
744 }
745
746 pub fn add<A: AnimationTrait + 'static>(&mut self, animation: A) {
748 self.animations.push(Box::new(animation));
749 }
750
751 pub fn update(&mut self, delta_ms: f64) -> Result<(), WasmError> {
753 if self.current_index >= self.animations.len() {
754 return Ok(());
755 }
756
757 let current = &mut self.animations[self.current_index];
758 current.update(delta_ms);
759
760 if current.is_complete() {
761 self.current_index += 1;
762 }
763
764 Ok(())
765 }
766
767 pub fn is_complete(&self) -> bool {
769 self.current_index >= self.animations.len()
770 }
771
772 pub fn progress(&self) -> f64 {
774 if self.animations.is_empty() {
775 return 1.0;
776 }
777
778 let completed = self.current_index as f64;
779 let current_progress = if self.current_index < self.animations.len() {
780 self.animations[self.current_index].progress()
781 } else {
782 0.0
783 };
784
785 (completed + current_progress) / self.animations.len() as f64
786 }
787
788 pub fn reset(&mut self) {
790 self.current_index = 0;
791 }
792}
793
794impl Default for AnimationSequence {
795 fn default() -> Self {
796 Self::new()
797 }
798}
799
800pub struct ParallelAnimation {
802 animations: Vec<Box<dyn AnimationTrait>>,
803}
804
805impl ParallelAnimation {
806 pub fn new() -> Self {
808 Self {
809 animations: Vec::new(),
810 }
811 }
812
813 pub fn add<A: AnimationTrait + 'static>(&mut self, animation: A) {
815 self.animations.push(Box::new(animation));
816 }
817
818 pub fn update(&mut self, delta_ms: f64) {
820 for anim in &mut self.animations {
821 if !anim.is_complete() {
822 anim.update(delta_ms);
823 }
824 }
825 }
826
827 pub fn is_complete(&self) -> bool {
829 self.animations.iter().all(|a| a.is_complete())
830 }
831
832 pub fn progress(&self) -> f64 {
834 if self.animations.is_empty() {
835 return 1.0;
836 }
837
838 let total: f64 = self.animations.iter().map(|a| a.progress()).sum();
839 total / self.animations.len() as f64
840 }
841}
842
843impl Default for ParallelAnimation {
844 fn default() -> Self {
845 Self::new()
846 }
847}
848
849#[derive(Debug)]
851pub struct DelayedAnimation<A: AnimationTrait> {
852 animation: A,
853 delay_ms: f64,
854 elapsed_delay_ms: f64,
855 started: bool,
856}
857
858impl<A: AnimationTrait> DelayedAnimation<A> {
859 pub fn new(animation: A, delay_ms: f64) -> Self {
861 Self {
862 animation,
863 delay_ms,
864 elapsed_delay_ms: 0.0,
865 started: false,
866 }
867 }
868
869 pub fn update(&mut self, delta_ms: f64) {
871 if !self.started {
872 self.elapsed_delay_ms += delta_ms;
873 if self.elapsed_delay_ms >= self.delay_ms {
874 self.started = true;
875 let overflow = self.elapsed_delay_ms - self.delay_ms;
876 if overflow > 0.0 {
877 self.animation.update(overflow);
878 }
879 }
880 } else {
881 self.animation.update(delta_ms);
882 }
883 }
884
885 pub fn is_complete(&self) -> bool {
887 self.started && self.animation.is_complete()
888 }
889
890 pub fn progress(&self) -> f64 {
892 if !self.started {
893 (self.elapsed_delay_ms / self.delay_ms).min(1.0) * 0.5
894 } else {
895 0.5 + self.animation.progress() * 0.5
896 }
897 }
898}
899
900#[cfg(test)]
901mod tests {
902 use super::*;
903
904 #[test]
905 fn test_linear_easing() {
906 let easing = Easing::Linear;
907 assert_eq!(easing.apply(0.0), 0.0);
908 assert_eq!(easing.apply(0.5), 0.5);
909 assert_eq!(easing.apply(1.0), 1.0);
910 }
911
912 #[test]
913 fn test_quadratic_easing() {
914 let easing = Easing::QuadraticIn;
915 assert_eq!(easing.apply(0.0), 0.0);
916 assert!(easing.apply(0.5) < 0.5);
917 assert_eq!(easing.apply(1.0), 1.0);
918 }
919
920 #[test]
921 fn test_animation_basic() {
922 let mut anim = Animation::new(0.0, 100.0, 1000.0, Easing::Linear);
923
924 assert_eq!(anim.current_value(), 0.0);
925 assert!(!anim.is_complete());
926
927 anim.update(500.0);
928 assert!((anim.current_value() - 50.0).abs() < 0.01);
929
930 anim.update(500.0);
931 assert_eq!(anim.current_value(), 100.0);
932 assert!(anim.is_complete());
933 }
934
935 #[test]
936 fn test_pan_animation() {
937 let mut pan = PanAnimation::new((0.0, 0.0), (100.0, 50.0), 1000.0, Easing::Linear);
938
939 let (x, y) = pan.current_position();
940 assert_eq!(x, 0.0);
941 assert_eq!(y, 0.0);
942
943 pan.update(500.0);
944 let (x, y) = pan.current_position();
945 assert!((x - 50.0).abs() < 0.01);
946 assert!((y - 25.0).abs() < 0.01);
947 }
948
949 #[test]
950 fn test_spring_animation() {
951 let mut spring = SpringAnimation::new(0.0, 100.0, 0.8, 0.98);
953 spring.set_threshold(1.0); for _ in 0..1000 {
957 spring.update(16.67);
958 if spring.is_settled() {
959 break;
960 }
961 }
962
963 assert!((spring.current_value() - 100.0).abs() < 5.0);
965 }
966
967 #[test]
968 fn test_animation_reverse() {
969 let mut anim = Animation::new(0.0, 100.0, 1000.0, Easing::Linear);
970 anim.update(500.0);
971
972 anim.reverse();
973 assert_eq!(anim.current_value(), 100.0);
974
975 anim.update(500.0);
976 assert_eq!(anim.current_value(), 50.0);
977 }
978}