1#![allow(non_snake_case)]
9#![allow(non_upper_case_globals)]
10
11use std::cell::{Cell, RefCell};
12use std::marker::PhantomData;
13use std::rc::{Rc, Weak};
14
15use cranpose_core::internal::FrameCallbackRegistration;
16use cranpose_core::{
17 with_current_composer, DisposableEffectResult, Owned, OwnedMutableState, RuntimeHandle,
18 SideEffect, State,
19};
20
21pub trait Lerp {
23 fn lerp(&self, target: &Self, fraction: f32) -> Self;
24}
25
26impl Lerp for f32 {
27 fn lerp(&self, target: &Self, fraction: f32) -> Self {
28 self + (target - self) * fraction
29 }
30}
31
32impl Lerp for f64 {
33 fn lerp(&self, target: &Self, fraction: f32) -> Self {
34 self + (target - self) * fraction as f64
35 }
36}
37
38pub trait SpringScalar: Lerp + Clone {
46 const DIMENSIONS: usize;
48
49 fn dimension(&self, index: usize) -> f32;
51
52 fn from_dimensions(dimensions: [f32; SPRING_MAX_DIMENSIONS]) -> Self;
55}
56
57pub const SPRING_MAX_DIMENSIONS: usize = 4;
59
60pub fn advance_spring(
65 value: f32,
66 velocity: f32,
67 target: f32,
68 damping_ratio: f32,
69 stiffness: f32,
70 dt: f32,
71) -> (f32, f32) {
72 let omega = stiffness.max(f32::EPSILON).sqrt();
73 let zeta = damping_ratio.max(0.0);
74 let displacement = value - target;
75
76 if (zeta - 1.0).abs() < 1e-4 {
77 let c1 = displacement;
79 let c2 = velocity + omega * displacement;
80 let decay = (-omega * dt).exp();
81 let next_displacement = (c1 + c2 * dt) * decay;
82 let next_velocity = (c2 - omega * (c1 + c2 * dt)) * decay;
83 (target + next_displacement, next_velocity)
84 } else if zeta < 1.0 {
85 let omega_d = omega * (1.0 - zeta * zeta).sqrt();
87 let decay = (-zeta * omega * dt).exp();
88 let (sin, cos) = (omega_d * dt).sin_cos();
89 let a = displacement;
90 let b = (velocity + zeta * omega * displacement) / omega_d;
91 let next_displacement = decay * (a * cos + b * sin);
92 let next_velocity = decay
93 * ((b * omega_d - a * zeta * omega) * cos - (a * omega_d + b * zeta * omega) * sin);
94 (target + next_displacement, next_velocity)
95 } else {
96 let root = (zeta * zeta - 1.0).sqrt();
98 let r1 = -omega * (zeta - root);
99 let r2 = -omega * (zeta + root);
100 let c2 = (velocity - r1 * displacement) / (r2 - r1);
101 let c1 = displacement - c2;
102 let e1 = (r1 * dt).exp();
103 let e2 = (r2 * dt).exp();
104 (target + c1 * e1 + c2 * e2, c1 * r1 * e1 + c2 * r2 * e2)
105 }
106}
107
108impl SpringScalar for f32 {
109 const DIMENSIONS: usize = 1;
110
111 fn dimension(&self, _index: usize) -> f32 {
112 *self
113 }
114
115 fn from_dimensions(dimensions: [f32; SPRING_MAX_DIMENSIONS]) -> Self {
116 dimensions[0]
117 }
118}
119
120impl SpringScalar for f64 {
121 const DIMENSIONS: usize = 1;
122
123 fn dimension(&self, _index: usize) -> f32 {
124 *self as f32
125 }
126
127 fn from_dimensions(dimensions: [f32; SPRING_MAX_DIMENSIONS]) -> Self {
128 f64::from(dimensions[0])
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq)]
134pub enum Easing {
135 LinearEasing,
138 EaseIn,
141 EaseOut,
144 EaseInOut,
147 FastOutSlowInEasing,
150 LinearOutSlowInEasing,
153 FastOutLinearEasing,
156}
157
158impl Easing {
159 pub fn transform(&self, fraction: f32) -> f32 {
161 match self {
162 Easing::LinearEasing => fraction,
163 Easing::EaseIn => cubic_bezier(0.42, 0.0, 1.0, 1.0, fraction),
164 Easing::EaseOut => cubic_bezier(0.0, 0.0, 0.58, 1.0, fraction),
165 Easing::EaseInOut => cubic_bezier(0.42, 0.0, 0.58, 1.0, fraction),
166 Easing::FastOutSlowInEasing => cubic_bezier(0.4, 0.0, 0.2, 1.0, fraction),
167 Easing::LinearOutSlowInEasing => cubic_bezier(0.0, 0.0, 0.2, 1.0, fraction),
168 Easing::FastOutLinearEasing => cubic_bezier(0.4, 0.0, 1.0, 1.0, fraction),
169 }
170 }
171}
172
173fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32, fraction: f32) -> f32 {
175 if fraction <= 0.0 {
176 return 0.0;
177 }
178 if fraction >= 1.0 {
179 return 1.0;
180 }
181
182 let cx = 3.0 * x1;
183 let bx = 3.0 * (x2 - x1) - cx;
184 let ax = 1.0 - cx - bx;
185
186 let cy = 3.0 * y1;
187 let by = 3.0 * (y2 - y1) - cy;
188 let ay = 1.0 - cy - by;
189
190 fn sample_curve(a: f32, b: f32, c: f32, t: f32) -> f32 {
191 ((a * t + b) * t + c) * t
192 }
193
194 fn sample_derivative(a: f32, b: f32, c: f32, t: f32) -> f32 {
195 (3.0 * a * t + 2.0 * b) * t + c
196 }
197
198 let mut t = fraction;
202 let mut newton_success = false;
203 for _ in 0..8 {
204 let x = sample_curve(ax, bx, cx, t) - fraction;
205 if x.abs() < 1e-6 {
206 newton_success = true;
207 break;
208 }
209 let dx = sample_derivative(ax, bx, cx, t);
210 if dx.abs() < 1e-6 {
211 break;
212 }
213 t = (t - x / dx).clamp(0.0, 1.0);
214 }
215
216 if !newton_success {
217 let mut t0 = 0.0;
219 let mut t1 = 1.0;
220 t = fraction;
221 for _ in 0..16 {
222 let x = sample_curve(ax, bx, cx, t);
223 let delta = x - fraction;
224 if delta.abs() < 1e-6 {
225 break;
226 }
227 if delta > 0.0 {
228 t1 = t;
229 } else {
230 t0 = t;
231 }
232 t = 0.5 * (t0 + t1);
233 }
234 }
235
236 sample_curve(ay, by, cy, t)
237}
238
239#[derive(Debug, Clone, Copy, PartialEq)]
241pub struct AnimationSpec {
242 pub duration_millis: u64,
244 pub easing: Easing,
246 pub delay_millis: u64,
248}
249
250impl AnimationSpec {
251 pub fn tween(duration_millis: u64, easing: Easing) -> Self {
253 Self {
254 duration_millis,
255 easing,
256 delay_millis: 0,
257 }
258 }
259
260 pub fn linear(duration_millis: u64) -> Self {
262 Self::tween(duration_millis, Easing::LinearEasing)
263 }
264
265 pub fn with_delay(mut self, delay_millis: u64) -> Self {
267 self.delay_millis = delay_millis;
268 self
269 }
270}
271
272impl Default for AnimationSpec {
273 fn default() -> Self {
274 Self::tween(300, Easing::FastOutSlowInEasing)
275 }
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280pub enum RepeatMode {
281 Restart,
283 Reverse,
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub enum StartOffsetType {
290 Delay,
292 FastForward,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub struct StartOffset {
299 pub offset_millis: i64,
301 pub offset_type: StartOffsetType,
303}
304
305impl Default for StartOffset {
306 fn default() -> Self {
307 Self {
308 offset_millis: 0,
309 offset_type: StartOffsetType::Delay,
310 }
311 }
312}
313
314#[derive(Debug, Clone, PartialEq)]
316pub struct InfiniteRepeatableSpec<T> {
317 pub animation: AnimationSpec,
319 pub repeat_mode: RepeatMode,
321 pub initial_start_offset: StartOffset,
323 _marker: PhantomData<fn() -> T>,
324}
325
326pub fn infiniteRepeatable<T>(
328 animation: AnimationSpec,
329 repeat_mode: RepeatMode,
330 initial_start_offset: StartOffset,
331) -> InfiniteRepeatableSpec<T> {
332 InfiniteRepeatableSpec {
333 animation,
334 repeat_mode,
335 initial_start_offset,
336 _marker: PhantomData,
337 }
338}
339
340#[derive(Debug, Clone, Copy, PartialEq)]
342pub struct SpringSpec {
343 pub damping_ratio: f32,
345 pub stiffness: f32,
347 pub velocity_threshold: f32,
349 pub position_threshold: f32,
351 pub delay_millis: u64,
353}
354
355impl SpringSpec {
356 pub fn new(damping_ratio: f32, stiffness: f32) -> Self {
358 Self {
359 damping_ratio,
360 stiffness,
361 velocity_threshold: 0.1,
362 position_threshold: 0.01,
363 delay_millis: 0,
364 }
365 }
366
367 pub fn with_delay(mut self, delay_millis: u64) -> Self {
369 self.delay_millis = delay_millis;
370 self
371 }
372
373 pub fn default_spring() -> Self {
375 Self {
376 damping_ratio: 1.0,
377 stiffness: 1500.0,
378 velocity_threshold: 0.1,
379 position_threshold: 0.01,
380 delay_millis: 0,
381 }
382 }
383
384 pub fn bouncy() -> Self {
386 Self {
387 damping_ratio: 0.5,
388 stiffness: 1500.0,
389 velocity_threshold: 0.1,
390 position_threshold: 0.01,
391 delay_millis: 0,
392 }
393 }
394
395 pub fn stiff() -> Self {
397 Self {
398 damping_ratio: 1.0,
399 stiffness: 3000.0,
400 velocity_threshold: 0.1,
401 position_threshold: 0.01,
402 delay_millis: 0,
403 }
404 }
405}
406
407impl Default for SpringSpec {
408 fn default() -> Self {
409 Self::default_spring()
410 }
411}
412
413pub struct Spring;
415
416impl Spring {
417 pub const DampingRatioNoBouncy: f32 = 1.0;
418 pub const DampingRatioLowBouncy: f32 = 0.75;
419 pub const DampingRatioMediumBouncy: f32 = 0.5;
420 pub const DampingRatioHighBouncy: f32 = 0.2;
421
422 pub const StiffnessHigh: f32 = 10_000.0;
423 pub const StiffnessMedium: f32 = 1_500.0;
424 pub const StiffnessMediumLow: f32 = 400.0;
425 pub const StiffnessLow: f32 = 200.0;
426 pub const StiffnessVeryLow: f32 = 50.0;
427}
428
429pub fn spring(damping_ratio: f32, stiffness: f32) -> AnimationType {
431 AnimationType::Spring(SpringSpec::new(damping_ratio, stiffness))
432}
433
434pub fn tween(duration_millis: u64, easing: Easing) -> AnimationType {
436 AnimationType::Tween(AnimationSpec::tween(duration_millis, easing))
437}
438
439#[derive(Debug, Clone, Copy, PartialEq)]
441pub enum AnimationType {
442 Tween(AnimationSpec),
444 Spring(SpringSpec),
446}
447
448impl AnimationType {
449 pub fn with_delay(self, delay_millis: u64) -> Self {
451 match self {
452 Self::Tween(spec) => Self::Tween(spec.with_delay(delay_millis)),
453 Self::Spring(spec) => Self::Spring(spec.with_delay(delay_millis)),
454 }
455 }
456}
457
458impl Default for AnimationType {
459 fn default() -> Self {
460 AnimationType::Tween(AnimationSpec::default())
461 }
462}
463
464trait InfiniteTransitionAnimation {
465 fn on_frame(&self, play_time_nanos: u64);
466}
467
468struct TransitionAnimationState<T: Lerp + Clone + PartialEq + 'static> {
469 value_state: OwnedMutableState<T>,
470 initial_value: RefCell<T>,
471 target_value: RefCell<T>,
472 spec: RefCell<InfiniteRepeatableSpec<T>>,
473 start_on_next_frame: Cell<bool>,
474 play_time_offset_nanos: Cell<u64>,
475}
476
477impl<T: Lerp + Clone + PartialEq + 'static> TransitionAnimationState<T> {
478 fn new(
479 initial_value: T,
480 target_value: T,
481 spec: InfiniteRepeatableSpec<T>,
482 runtime: RuntimeHandle,
483 ) -> Self {
484 Self {
485 value_state: OwnedMutableState::with_runtime(initial_value.clone(), runtime),
486 initial_value: RefCell::new(initial_value),
487 target_value: RefCell::new(target_value),
488 spec: RefCell::new(spec),
489 start_on_next_frame: Cell::new(true),
490 play_time_offset_nanos: Cell::new(0),
491 }
492 }
493
494 fn state(&self) -> State<T> {
495 self.value_state.as_state()
496 }
497
498 fn update_values(&self, initial_value: T, target_value: T, spec: InfiniteRepeatableSpec<T>) {
499 let needs_update = {
500 let current_initial = self.initial_value.borrow();
501 let current_target = self.target_value.borrow();
502 *current_initial != initial_value
503 || *current_target != target_value
504 || *self.spec.borrow() != spec
505 };
506
507 if needs_update {
508 *self.initial_value.borrow_mut() = initial_value.clone();
509 *self.target_value.borrow_mut() = target_value;
510 *self.spec.borrow_mut() = spec;
511 self.start_on_next_frame.set(true);
512 self.value_state.set(initial_value);
513 }
514 }
515
516 fn compute_value(&self, play_time_nanos: u64) -> T {
517 let offset = if self.start_on_next_frame.get() {
518 self.start_on_next_frame.set(false);
519 self.play_time_offset_nanos.set(play_time_nanos);
520 play_time_nanos
521 } else {
522 self.play_time_offset_nanos.get()
523 };
524 let local_play_time = play_time_nanos.saturating_sub(offset);
525 let spec = self.spec.borrow().clone();
526 let initial = self.initial_value.borrow();
527 let target = self.target_value.borrow();
528 compute_repeatable_value(local_play_time, &initial, &target, spec)
529 }
530}
531
532impl<T: Lerp + Clone + PartialEq + 'static> InfiniteTransitionAnimation
533 for TransitionAnimationState<T>
534{
535 fn on_frame(&self, play_time_nanos: u64) {
536 let value = self.compute_value(play_time_nanos);
537 self.value_state.set(value);
538 }
539}
540
541fn compute_repeatable_value<T: Lerp + Clone>(
542 play_time_nanos: u64,
543 initial: &T,
544 target: &T,
545 spec: InfiniteRepeatableSpec<T>,
546) -> T {
547 let duration_ms = spec.animation.duration_millis.max(1) as i64;
548 let delay_ms = spec.animation.delay_millis as i64;
549 let mut play_time_ms = (play_time_nanos / 1_000_000) as i64;
550
551 match spec.initial_start_offset.offset_type {
552 StartOffsetType::Delay => {
553 play_time_ms -= spec.initial_start_offset.offset_millis;
554 }
555 StartOffsetType::FastForward => {
556 play_time_ms += spec.initial_start_offset.offset_millis;
557 }
558 }
559
560 if play_time_ms < 0 {
561 return initial.clone();
562 }
563
564 let iteration_duration = (delay_ms + duration_ms).max(1);
565 let iteration = play_time_ms / iteration_duration;
566 let iteration_time = play_time_ms % iteration_duration;
567
568 let reverse = matches!(spec.repeat_mode, RepeatMode::Reverse) && iteration % 2 != 0;
569 let (start, end) = if reverse {
570 (target, initial)
571 } else {
572 (initial, target)
573 };
574
575 if iteration_time < delay_ms {
576 return start.clone();
577 }
578
579 let linear_progress = ((iteration_time - delay_ms) as f32 / duration_ms as f32).clamp(0.0, 1.0);
580 let eased = spec.animation.easing.transform(linear_progress);
581 start.lerp(end, eased)
582}
583
584#[derive(Clone)]
585pub struct InfiniteTransition {
586 inner: Rc<InfiniteTransitionInner>,
587}
588
589struct InfiniteTransitionInner {
590 label: String,
591 animations: RefCell<Vec<Rc<dyn InfiniteTransitionAnimation>>>,
592 run_token: OwnedMutableState<u64>,
593}
594
595impl InfiniteTransition {
596 fn new(label: &str, runtime: RuntimeHandle) -> Self {
597 Self {
598 inner: Rc::new(InfiniteTransitionInner {
599 label: label.to_string(),
600 animations: RefCell::new(Vec::new()),
601 run_token: OwnedMutableState::with_runtime(0u64, runtime),
602 }),
603 }
604 }
605
606 pub fn label(&self) -> &str {
607 &self.inner.label
608 }
609
610 fn run(&self) {
611 let run_key = self.inner.run_token.get();
612 cranpose_core::label_next_ui_task(format!("loop {}", self.inner.label));
613 let weak: Weak<InfiniteTransitionInner> = Rc::downgrade(&self.inner);
614 cranpose_core::LaunchedEffectAsync!(run_key, move |scope| {
615 Box::pin(async move {
616 let clock = scope.runtime().frame_clock();
617 let mut start_time: Option<u64> = None;
618
619 loop {
620 if !scope.is_active() {
621 break;
622 }
623
624 let Some(inner) = weak.upgrade() else {
625 break;
626 };
627
628 if inner.animations.borrow().is_empty() {
629 break;
630 }
631
632 let now = clock.next_frame().await;
633 if !scope.is_active() {
634 break;
635 }
636
637 let start = start_time.get_or_insert(now);
638 let play_time = now.saturating_sub(*start);
639 inner.on_frame(play_time);
640 }
641 })
642 });
643 }
644
645 #[allow(non_snake_case)]
646 pub fn animateFloat(
647 &self,
648 initial_value: f32,
649 target_value: f32,
650 animation_spec: InfiniteRepeatableSpec<f32>,
651 label: &str,
652 ) -> State<f32> {
653 let _ = label;
654 self.animateValue(initial_value, target_value, animation_spec)
655 }
656
657 #[allow(non_snake_case)]
658 pub fn animateValue<T: Lerp + Clone + PartialEq + 'static>(
659 &self,
660 initial_value: T,
661 target_value: T,
662 animation_spec: InfiniteRepeatableSpec<T>,
663 ) -> State<T> {
664 let runtime = with_current_composer(|composer| composer.runtime_handle());
665 let initial_for_remember = initial_value.clone();
666 let target_for_remember = target_value.clone();
667 let spec_for_remember = animation_spec.clone();
668 let animation_state = cranpose_core::remember(move || {
669 Rc::new(TransitionAnimationState::new(
670 initial_for_remember,
671 target_for_remember,
672 spec_for_remember,
673 runtime.clone(),
674 ))
675 })
676 .with(Rc::clone);
677
678 let animation_state_for_effect = Rc::clone(&animation_state);
679 let spec_for_effect = animation_spec;
680 SideEffect(move || {
681 animation_state_for_effect.update_values(
682 initial_value.clone(),
683 target_value.clone(),
684 spec_for_effect,
685 );
686 });
687
688 let animation_any: Rc<dyn InfiniteTransitionAnimation> = animation_state.clone();
689 let transition_inner = Rc::clone(&self.inner);
690 let animation_id = Rc::as_ptr(&animation_state) as usize;
691 cranpose_core::DisposableEffect!(animation_id, move |_scope| {
692 transition_inner.add_animation(animation_any.clone());
693 let transition_inner = Rc::clone(&transition_inner);
694 let animation_any = animation_any.clone();
695 DisposableEffectResult::new(move || {
696 transition_inner.remove_animation(&animation_any);
697 })
698 });
699
700 animation_state.state()
701 }
702}
703
704impl InfiniteTransitionInner {
705 fn add_animation(&self, animation: Rc<dyn InfiniteTransitionAnimation>) {
706 let mut list = self.animations.borrow_mut();
707 let was_empty = list.is_empty();
708 let already_present = list.iter().any(|item| Rc::ptr_eq(item, &animation));
709 if !already_present {
710 list.push(animation);
711 }
712 if was_empty && !list.is_empty() {
713 self.run_token
714 .update(|value| *value = value.wrapping_add(1));
715 }
716 }
717
718 fn remove_animation(&self, animation: &Rc<dyn InfiniteTransitionAnimation>) {
719 let mut list = self.animations.borrow_mut();
720 let was_empty = list.is_empty();
721 if let Some(index) = list.iter().position(|item| Rc::ptr_eq(item, animation)) {
722 list.remove(index);
723 }
724 let is_empty = list.is_empty();
725 drop(list);
726
727 if !was_empty && is_empty {
728 self.run_token
729 .update(|value| *value = value.wrapping_add(1));
730 }
731 }
732
733 fn on_frame(&self, play_time_nanos: u64) {
734 let animations = self.animations.borrow().clone();
735 for animation in animations {
736 animation.on_frame(play_time_nanos);
737 }
738 }
739}
740
741#[allow(non_snake_case)]
742pub fn rememberInfiniteTransition(label: &str) -> InfiniteTransition {
743 let runtime = with_current_composer(|composer| composer.runtime_handle());
744 let transition =
745 cranpose_core::remember(move || InfiniteTransition::new(label, runtime.clone()))
746 .with(|transition| transition.clone());
747 transition.run();
748 transition
749}
750
751pub struct Animatable<T: SpringScalar + 'static> {
753 inner: Rc<RefCell<AnimatableInner<T>>>,
754}
755
756struct AnimatableInner<T: SpringScalar + 'static> {
757 state: OwnedMutableState<T>,
758 runtime: RuntimeHandle,
759 current: T,
760 velocity: [f32; SPRING_MAX_DIMENSIONS],
763 start: T,
764 target: T,
765 animation_type: AnimationType,
766 start_time_nanos: Option<u64>,
767 last_frame_nanos: Option<u64>,
770 registration: Option<FrameCallbackRegistration>,
771}
772
773impl<T: SpringScalar + 'static> Animatable<T> {
774 pub fn new(initial: T, runtime: RuntimeHandle) -> Self {
776 let inner = AnimatableInner {
777 state: OwnedMutableState::with_runtime(initial.clone(), runtime.clone()),
778 runtime,
779 current: initial.clone(),
780 velocity: [0.0; SPRING_MAX_DIMENSIONS],
781 start: initial.clone(),
782 target: initial,
783 animation_type: AnimationType::default(),
784 start_time_nanos: None,
785 last_frame_nanos: None,
786 registration: None,
787 };
788 Self {
789 inner: Rc::new(RefCell::new(inner)),
790 }
791 }
792
793 pub fn animateTo(&mut self, target: T, animation: AnimationType) {
798 self.start_animation(target, animation, None);
799 }
800
801 fn start_animation(
802 &mut self,
803 target: T,
804 animation: AnimationType,
805 exact_start_time_nanos: Option<u64>,
806 ) {
807 {
808 let mut inner = self.inner.borrow_mut();
809 let previous_animation = inner.animation_type;
810
811 if let Some(registration) = inner.registration.take() {
813 registration.cancel();
814 }
815
816 inner.start = inner.current.clone();
817 inner.target = target;
818 inner.animation_type = animation;
819 inner.start_time_nanos = exact_start_time_nanos;
820 match animation {
821 AnimationType::Spring(spec) => {
822 let continues_spring = matches!(previous_animation, AnimationType::Spring(_));
823 if let Some(start_time_nanos) = exact_start_time_nanos {
824 let delay_nanos = spec.delay_millis.saturating_mul(1_000_000);
825 inner.last_frame_nanos = Some(start_time_nanos.saturating_add(delay_nanos));
826 } else if !continues_spring {
827 inner.last_frame_nanos = None;
828 }
829 }
830 AnimationType::Tween(_) => {
831 inner.last_frame_nanos = None;
832 inner.velocity = [0.0; SPRING_MAX_DIMENSIONS];
833 }
834 }
835 }
844
845 Self::schedule_frame(&self.inner);
846 }
847
848 pub fn animate_to_with_velocity(&mut self, target: T, velocity: T, animation: AnimationType) {
852 {
853 let mut inner = self.inner.borrow_mut();
854 for index in 0..T::DIMENSIONS.min(SPRING_MAX_DIMENSIONS) {
855 inner.velocity[index] = velocity.dimension(index);
856 }
857 }
858 self.animateTo(target, animation);
859 }
860
861 pub fn animate_to_with_velocity_at(
866 &mut self,
867 target: T,
868 velocity: T,
869 animation: AnimationType,
870 start_time_nanos: u64,
871 ) {
872 {
873 let mut inner = self.inner.borrow_mut();
874 for index in 0..T::DIMENSIONS.min(SPRING_MAX_DIMENSIONS) {
875 inner.velocity[index] = velocity.dimension(index);
876 }
877 }
878 self.start_animation(target, animation, Some(start_time_nanos));
879 }
880
881 pub fn velocity(&self) -> T {
883 T::from_dimensions(self.inner.borrow().velocity)
884 }
885
886 pub fn target(&self) -> T {
888 self.inner.borrow().target.clone()
889 }
890
891 pub fn animation_type(&self) -> AnimationType {
893 self.inner.borrow().animation_type
894 }
895
896 pub fn state(&self) -> State<T> {
898 self.inner.borrow().state.as_state()
899 }
900
901 pub fn snapTo(&mut self, target: T) {
903 let mut inner = self.inner.borrow_mut();
904 if let Some(registration) = inner.registration.take() {
905 registration.cancel();
906 }
907 inner.current = target.clone();
908 inner.start = target.clone();
909 inner.target = target.clone();
910 inner.start_time_nanos = None;
911 inner.last_frame_nanos = None;
912 inner.velocity = [0.0; SPRING_MAX_DIMENSIONS];
913 inner.state.set_value(target);
914 }
915
916 fn schedule_frame(this: &Rc<RefCell<AnimatableInner<T>>>) {
917 let runtime = {
918 let inner = this.borrow();
919 if inner.registration.is_some() {
920 return;
921 }
922 inner.runtime.clone()
923 };
924 let weak = Rc::downgrade(this);
925 let registration = runtime.frame_clock().with_frame_nanos(move |time| {
926 if let Some(strong) = weak.upgrade() {
927 Self::on_frame(&strong, time);
928 }
929 });
930 this.borrow_mut().registration = Some(registration);
931 }
932
933 fn on_frame(this: &Rc<RefCell<AnimatableInner<T>>>, frame_time_nanos: u64) {
934 let mut schedule_next = false;
935 {
936 let mut inner = this.borrow_mut();
937 inner.registration = None;
938
939 match inner.animation_type {
940 AnimationType::Tween(spec) => {
941 let start_time = inner.start_time_nanos.get_or_insert(frame_time_nanos);
942 let elapsed_nanos = frame_time_nanos.saturating_sub(*start_time);
943 let delay_nanos = spec.delay_millis.saturating_mul(1_000_000);
944
945 if elapsed_nanos < delay_nanos {
946 schedule_next = true;
947 } else {
948 let animation_elapsed = elapsed_nanos - delay_nanos;
949 let duration_nanos = spec.duration_millis * 1_000_000;
950 let duration_nanos = duration_nanos.max(1);
951 let linear_progress =
952 (animation_elapsed as f32 / duration_nanos as f32).clamp(0.0, 1.0);
953 let progress = spec.easing.transform(linear_progress);
954
955 let new_value = inner.start.lerp(&inner.target, progress);
956 inner.current = new_value.clone();
957 inner.state.set_value(new_value);
958
959 if linear_progress >= 1.0 {
960 inner.current = inner.target.clone();
961 inner.start = inner.target.clone();
962 inner.start_time_nanos = None;
963 inner.state.set_value(inner.target.clone());
964 } else {
965 schedule_next = true;
966 }
967 }
968 }
969 AnimationType::Spring(spec) => {
970 let start_time = inner.start_time_nanos.get_or_insert(frame_time_nanos);
971 let elapsed_nanos = frame_time_nanos.saturating_sub(*start_time);
972 let delay_nanos = spec.delay_millis.saturating_mul(1_000_000);
973 if elapsed_nanos < delay_nanos {
974 inner.last_frame_nanos = Some(start_time.saturating_add(delay_nanos));
975 schedule_next = true;
976 } else {
977 let last = inner.last_frame_nanos.replace(frame_time_nanos);
982 let dt = last
983 .map(|last| {
984 frame_time_nanos.saturating_sub(last) as f32 / 1_000_000_000.0
985 })
986 .unwrap_or(0.0);
987
988 if dt <= 0.0 {
989 schedule_next = true;
990 } else {
991 let dimensions = T::DIMENSIONS.min(SPRING_MAX_DIMENSIONS);
992 let mut position = [0.0f32; SPRING_MAX_DIMENSIONS];
993 for (index, slot) in position.iter_mut().enumerate().take(dimensions) {
994 let value = inner.current.dimension(index);
995 let target = inner.target.dimension(index);
996 let (next_value, next_velocity) = advance_spring(
997 value,
998 inner.velocity[index],
999 target,
1000 spec.damping_ratio,
1001 spec.stiffness,
1002 dt,
1003 );
1004 *slot = next_value;
1005 inner.velocity[index] = next_velocity;
1006 }
1007
1008 inner.current = T::from_dimensions(position);
1009 inner.state.set_value(inner.current.clone());
1010
1011 let settled = (0..dimensions).all(|index| {
1013 inner.velocity[index].abs() < spec.velocity_threshold
1014 && (position[index] - inner.target.dimension(index)).abs()
1015 < spec.position_threshold
1016 });
1017
1018 if settled {
1019 inner.current = inner.target.clone();
1020 inner.start = inner.target.clone();
1021 inner.start_time_nanos = None;
1022 inner.last_frame_nanos = None;
1023 inner.velocity = [0.0; SPRING_MAX_DIMENSIONS];
1024 inner.state.set_value(inner.target.clone());
1025 } else {
1026 schedule_next = true;
1027 }
1028 }
1029 }
1030 }
1031 }
1032 }
1033
1034 if schedule_next {
1035 Self::schedule_frame(this);
1036 }
1037 }
1038}
1039
1040pub fn animate_float_as_state_with_initial(
1046 initial: f32,
1047 target: f32,
1048 animation: AnimationType,
1049 label: &str,
1050) -> State<f32> {
1051 let _ = label;
1052 with_current_composer(|composer| {
1053 let runtime = composer.runtime_handle();
1054 let anim: Owned<Animatable<f32>> = composer.remember(|| Animatable::new(initial, runtime));
1055 anim.update(|animatable| {
1056 let is_new_target = (animatable.target() - target).abs() > f32::EPSILON;
1057 let is_new_animation = animatable.animation_type() != animation;
1058 if is_new_target || is_new_animation {
1059 animatable.animateTo(target, animation);
1060 }
1061 });
1062 anim.with(|animatable| animatable.state())
1063 })
1064}
1065
1066#[allow(non_snake_case)]
1067pub fn animateFloatAsState(target: f32, animation: AnimationType, label: &str) -> State<f32> {
1068 let _ = label;
1069 with_current_composer(|composer| {
1070 let runtime = composer.runtime_handle();
1071 let anim: Owned<Animatable<f32>> = composer.remember(|| Animatable::new(target, runtime));
1072 anim.update(|animatable| {
1073 let is_new_target = (animatable.target() - target).abs() > f32::EPSILON;
1074 let is_new_animation = animatable.animation_type() != animation;
1075 if is_new_target || is_new_animation {
1076 animatable.animateTo(target, animation);
1077 }
1078 });
1079 anim.with(|animatable| animatable.state())
1080 })
1081}
1082
1083impl<T: SpringScalar + 'static> Clone for Animatable<T> {
1084 fn clone(&self) -> Self {
1085 Self {
1086 inner: self.inner.clone(),
1087 }
1088 }
1089}
1090
1091#[cfg(test)]
1092#[path = "tests/animation_tests.rs"]
1093mod tests;