1use super::*;
5use crate::{
6 animations::simulations::{
7 Parameter, Simulation,
8 spring::{SpringDurationBounceParameters, SpringParameters, SpringRegime},
9 },
10 items::{AnimationDirection, PropertyAnimation},
11 lengths::LogicalLength,
12};
13use euclid::Length;
14#[cfg(not(feature = "std"))]
15use num_traits::Float;
16
17enum AnimationState {
18 Delaying,
20 Animating {
22 current_iteration: u64,
23 },
24 Done {
25 iteration_count: u64,
26 },
27}
28
29pub(super) struct PropertyPhysicsAnimationData<S> {
30 simulation: S,
31 state: AnimationState,
32}
33
34impl<S> PropertyPhysicsAnimationData<S>
35where
36 S: Simulation,
37{
38 pub fn new(simulation: S) -> PropertyPhysicsAnimationData<S> {
39 PropertyPhysicsAnimationData { simulation, state: AnimationState::Delaying }
40 }
41
42 pub fn update_value(&mut self, target: &mut crate::Coord) -> bool {
44 match self.state {
45 AnimationState::Delaying => {
46 self.state = AnimationState::Animating { current_iteration: 0 };
48 self.update_value(target)
49 }
50 AnimationState::Animating { current_iteration: _ } => {
51 let mut value: f32 = *target as f32;
53 let finished = self.simulation.step(&mut value, crate::animations::current_tick());
54 *target = value as crate::Coord;
55 if finished {
56 self.state = AnimationState::Done { iteration_count: 0 };
57 true
58 } else {
59 false
60 }
61 }
62 AnimationState::Done { iteration_count: _ } => true,
63 }
64 }
65}
66
67pub(super) struct PropertyValueAnimationData<T> {
68 from_value: T,
69 to_value: Option<T>,
70 details: PropertyAnimation,
71 start_time: crate::animations::Instant,
72 state: AnimationState,
73 map: Option<fn(T) -> T>,
77 spring: Option<SpringRegime>,
78 spring_settle_clamped: bool,
80}
81
82impl<T: InterpolatedPropertyValue + Clone> PropertyValueAnimationData<T> {
83 pub fn new(from_value: T, to_value: Option<T>, details: PropertyAnimation) -> Self {
84 Self::new_with_velocity(from_value, to_value, details, 0.0)
85 }
86
87 pub fn new_with_velocity(
89 from_value: T,
90 to_value: Option<T>,
91 details: PropertyAnimation,
92 initial_velocity: f32,
93 ) -> Self {
94 let start_time = crate::animations::current_tick();
95 let spring = Self::compute_spring(&details, &from_value, &to_value, initial_velocity);
96 Self {
97 from_value,
98 to_value,
99 details,
100 start_time,
101 state: AnimationState::Delaying,
102 map: None,
103 spring,
104 spring_settle_clamped: false,
105 }
106 }
107
108 fn compute_spring(
110 details: &PropertyAnimation,
111 from_value: &T,
112 to_value: &Option<T>,
113 initial_velocity: f32,
114 ) -> Option<SpringRegime> {
115 matches!(details.easing, crate::animations::EasingCurve::Spring(_))
116 .then(|| {
117 let crate::animations::EasingCurve::Spring(bounce) = details.easing else {
118 return None;
119 };
120 let (w_n, zeta) = if details.duration > 0 {
121 Some(
122 SpringDurationBounceParameters::new(
123 details.duration as f32 / 1000.0,
124 bounce,
125 )
126 .to_natural_frequency_and_damping_ratio(),
127 )
128 } else {
129 None
130 }?;
131
132 let delta = to_value.as_ref().map_or(0.0, |tv| from_value.scalar_delta(tv));
135 let v0 = if delta != 0.0 { initial_velocity / delta } else { 0.0 };
136 Some(SpringRegime::new(-1.0, v0, w_n, zeta))
137 })
138 .flatten()
139 }
140
141 pub fn with_map(mut self, map: fn(T) -> T) -> Self {
142 self.map = Some(map);
143 self
144 }
145
146 fn apply_map(&self, value: T) -> T {
147 match self.map {
148 Some(map) => map(value),
149 None => value,
150 }
151 }
152
153 fn current_velocity(&self) -> Option<f32> {
155 if !matches!(self.state, AnimationState::Animating { .. }) {
156 return None;
157 }
158 let spring = self.spring.as_ref()?;
159 let elapsed_secs =
160 crate::animations::current_tick().duration_since(self.start_time).as_millis() as f32
161 / 1000.0;
162 let (_, rel_vel) = spring.evaluate(elapsed_secs);
163 let to_value = self.to_value.as_ref().expect("The animation should have a to_value");
164 Some(rel_vel * self.from_value.scalar_delta(to_value))
165 }
166
167 pub fn compute_interpolated_value(&mut self) -> (T, bool) {
169 let to_value = self.to_value.clone().expect("The animation should have a to_value");
171 if !self.details.enabled {
172 return (self.apply_map(to_value), true);
173 }
174
175 let new_tick = crate::animations::current_tick();
176 let mut time_progress = new_tick.duration_since(self.start_time).as_millis() as u64;
177 let reversed = |iteration: u64| -> bool {
178 #[allow(clippy::manual_is_multiple_of)] match self.details.direction {
180 AnimationDirection::Normal => false,
181 AnimationDirection::Reverse => true,
182 AnimationDirection::Alternate => iteration % 2 == 1,
183 AnimationDirection::AlternateReverse => iteration % 2 == 0,
184 }
185 };
186
187 match self.state {
188 AnimationState::Delaying => {
189 if self.details.delay <= 0 {
190 self.state = AnimationState::Animating { current_iteration: 0 };
191 return self.compute_interpolated_value();
192 }
193
194 let delay = self.details.delay as u64;
195
196 if time_progress < delay {
197 if reversed(0) {
198 (self.apply_map(to_value), false)
199 } else {
200 (self.apply_map(self.from_value.clone()), false)
201 }
202 } else {
203 self.start_time =
204 new_tick - core::time::Duration::from_millis(time_progress - delay);
205
206 self.state = AnimationState::Animating { current_iteration: 0 };
208 self.compute_interpolated_value()
209 }
210 }
211 AnimationState::Animating { current_iteration } => {
212 if matches!(self.details.easing, crate::animations::EasingCurve::Spring(_)) {
214 if self.details.iteration_count == 0. {
215 self.state = AnimationState::Done { iteration_count: 0 };
216 return self.compute_interpolated_value();
217 }
218 return if let Some(spring) = self.spring.as_ref() {
219 let next_iteration = current_iteration + 1;
220 let has_more_iterations = self.details.iteration_count < 0.
221 || (next_iteration as f64) < self.details.iteration_count as f64;
222 let duration_ms = self.details.duration as u64;
223
224 if has_more_iterations && time_progress >= duration_ms {
225 let duration_secs = duration_ms as f32 / 1000.0;
231 let (rel_pos, rel_vel) = spring.evaluate(duration_secs);
232 let crate::animations::EasingCurve::Spring(bounce) =
233 self.details.easing
234 else {
235 unreachable!()
236 };
237 let (w_n, zeta) =
238 SpringDurationBounceParameters::new(duration_secs, bounce)
239 .to_natural_frequency_and_damping_ratio();
240 let x0 = if reversed(current_iteration) != reversed(next_iteration) {
241 -(1.0 + rel_pos)
242 } else {
243 -1.0
244 };
245 self.spring = Some(SpringRegime::new(x0, rel_vel, w_n, zeta));
246 self.start_time += core::time::Duration::from_millis(duration_ms);
247 self.state =
248 AnimationState::Animating { current_iteration: next_iteration };
249 self.compute_interpolated_value()
250 } else {
251 let elapsed_secs = time_progress as f32 / 1000.0;
252 let (t, settled) =
253 crate::animations::spring_settle_progress(spring, elapsed_secs);
254 if !settled
255 && !self.spring_settle_clamped
256 && time_progress >= duration_ms
257 {
258 self.spring_settle_clamped = true;
262 let duration_secs = duration_ms as f32 / 1000.0;
263 let w_n = 2.0 * core::f32::consts::PI / duration_secs;
264 let settled_regime = crate::animations::spring_settle_within(
265 spring,
266 duration_secs,
267 w_n,
268 );
269 self.spring = Some(settled_regime);
270 self.start_time += core::time::Duration::from_millis(duration_ms);
271 return self.compute_interpolated_value();
272 }
273 if settled {
274 self.state = if has_more_iterations {
275 self.start_time = new_tick;
276 AnimationState::Animating { current_iteration: next_iteration }
277 } else {
278 AnimationState::Done { iteration_count: current_iteration }
279 };
280 self.compute_interpolated_value()
281 } else {
282 let progress = if reversed(current_iteration) { 1. - t } else { t };
283 let val = self.from_value.interpolate(&to_value, progress);
284 (self.apply_map(val), false)
285 }
286 }
287 } else {
288 self.state = AnimationState::Done { iteration_count: 0 };
289 self.compute_interpolated_value()
290 };
291 }
292 let mut current_iteration = current_iteration;
293
294 if self.details.duration <= 0 || self.details.iteration_count == 0. {
295 self.state = AnimationState::Done { iteration_count: 0 };
296 return self.compute_interpolated_value();
297 }
298
299 let duration = self.details.duration as u64;
300 if time_progress >= duration {
301 current_iteration += time_progress / duration;
303 time_progress %= duration;
304 self.start_time = new_tick - core::time::Duration::from_millis(time_progress);
305 }
306
307 if (self.details.iteration_count < 0.)
308 || (((current_iteration * duration) + time_progress) as f64)
309 < ((self.details.iteration_count as f64) * (duration as f64))
310 {
311 self.state = AnimationState::Animating { current_iteration };
312
313 let progress = {
314 let progress =
315 (time_progress as f32 / self.details.duration as f32).clamp(0., 1.);
316 if reversed(current_iteration) { 1. - progress } else { progress }
317 };
318 let t = crate::animations::easing_curve(&self.details.easing, progress);
319 let val = self.from_value.interpolate(&to_value, t);
320
321 (self.apply_map(val), false)
322 } else {
323 self.state =
324 AnimationState::Done { iteration_count: current_iteration.max(1) - 1 };
325 self.compute_interpolated_value()
326 }
327 }
328 AnimationState::Done { iteration_count } => {
329 if reversed(iteration_count) {
330 (self.apply_map(self.from_value.clone()), true)
331 } else {
332 (self.apply_map(to_value), true)
333 }
334 }
335 }
336 }
337}
338
339#[derive(Clone, Copy, Eq, PartialEq, Debug)]
340pub(super) enum AnimatedBindingState {
341 Animating,
342 NotAnimating,
343 ShouldStart,
344}
345
346#[pin_project::pin_project]
347pub(super) struct AnimatedBindingCallable<T, A> {
348 #[pin]
349 pub(super) original_binding: PropertyHandle,
350 pub(super) state: Cell<AnimatedBindingState>,
351 pub(super) animation_data: RefCell<PropertyValueAnimationData<T>>,
352 pub(super) compute_animation_details: A,
353 pub(super) dirty_time: Cell<crate::animations::Instant>,
355 pub(crate) carried_velocity: Cell<f32>,
356}
357
358pub(super) type AnimationDetail = (PropertyAnimation, Option<crate::animations::Instant>);
359
360unsafe impl<T: InterpolatedPropertyValue + Clone, A: Fn() -> AnimationDetail> BindingCallable<T>
361 for AnimatedBindingCallable<T, A>
362{
363 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult {
364 let original_binding = self.project_ref().original_binding;
365 original_binding.register_as_dependency_to_current_binding(
366 #[cfg(slint_debug_property)]
367 "<AnimatedBindingCallable>",
368 );
369 match self.state.get() {
370 AnimatedBindingState::Animating => {
371 let (val, finished) = self.animation_data.borrow_mut().compute_interpolated_value();
372 *value = val;
373 if finished {
374 self.state.set(AnimatedBindingState::NotAnimating)
375 } else {
376 crate::animations::CURRENT_ANIMATION_DRIVER
377 .with(|driver| driver.set_has_active_animations());
378 }
379 }
380 AnimatedBindingState::NotAnimating => {
381 unsafe { self.original_binding.update(value as *mut T) };
383 }
384 AnimatedBindingState::ShouldStart => {
385 let mut animation_data = self.animation_data.borrow_mut();
386
387 let previous_to_value = animation_data.to_value.clone();
391 let mut new_to_value = T::default();
392 unsafe { self.original_binding.update(&mut new_to_value as *mut T) };
395 animation_data.to_value = Some(new_to_value);
396
397 if animation_data.to_value != previous_to_value {
398 animation_data.state = AnimationState::Delaying;
399 animation_data.start_time = self.dirty_time.get();
401 animation_data.from_value = value.clone();
403 let (details, start_time) = (self.compute_animation_details)();
404 if let Some(start_time) = start_time {
405 animation_data.start_time = start_time;
406 }
407 animation_data.details = details;
408 animation_data.spring = PropertyValueAnimationData::<T>::compute_spring(
409 &animation_data.details,
410 &animation_data.from_value,
411 &animation_data.to_value,
412 self.carried_velocity.get(),
413 );
414 animation_data.spring_settle_clamped = false;
415 }
416
417 self.state.set(AnimatedBindingState::Animating);
418 let (val, finished) = animation_data.compute_interpolated_value();
419 *value = val;
420 if finished {
421 self.state.set(AnimatedBindingState::NotAnimating)
422 } else {
423 crate::animations::CURRENT_ANIMATION_DRIVER
424 .with(|driver| driver.set_has_active_animations());
425 }
426 }
427 };
428 BindingResult::KeepBinding
429 }
430 fn mark_dirty(self: Pin<&Self>) {
431 if self.state.get() == AnimatedBindingState::ShouldStart {
432 return;
433 }
434 let original_dirty = self.original_binding.access(|b| b.unwrap().dirty.get());
435 if original_dirty {
436 self.carried_velocity
437 .set(self.animation_data.borrow().current_velocity().unwrap_or(0.0));
438 self.state.set(AnimatedBindingState::ShouldStart);
439 self.dirty_time.set(crate::animations::current_tick());
440 }
441 }
442
443 fn velocity(self: Pin<&Self>) -> Option<f32> {
444 self.animation_data.borrow().current_velocity()
445 }
446}
447
448pub trait InterpolatedPropertyValue: PartialEq + Default + 'static {
452 #[must_use]
456 fn interpolate(&self, target_value: &Self, t: f32) -> Self;
457
458 fn scalar_delta(&self, _target_value: &Self) -> f32 {
461 0.0
462 }
463}
464
465impl InterpolatedPropertyValue for f32 {
466 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
467 self + t * (target_value - self)
468 }
469
470 fn scalar_delta(&self, target_value: &Self) -> f32 {
471 target_value - self
472 }
473}
474
475impl InterpolatedPropertyValue for i32 {
476 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
477 self + (t * (target_value - self) as f32).round() as i32
478 }
479
480 fn scalar_delta(&self, target_value: &Self) -> f32 {
481 (target_value - self) as f32
482 }
483}
484
485impl InterpolatedPropertyValue for i64 {
486 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
487 self + (t * (target_value - self) as f32).round() as Self
488 }
489
490 fn scalar_delta(&self, target_value: &Self) -> f32 {
491 (target_value - self) as f32
492 }
493}
494
495impl InterpolatedPropertyValue for u8 {
496 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
497 ((*self as f32) + (t * ((*target_value as f32) - (*self as f32)))).round().clamp(0., 255.)
498 as u8
499 }
500
501 fn scalar_delta(&self, target_value: &Self) -> f32 {
502 (*target_value as f32) - (*self as f32)
503 }
504}
505
506impl InterpolatedPropertyValue for LogicalLength {
507 fn interpolate(&self, target_value: &Self, t: f32) -> Self {
508 LogicalLength::new(self.get().interpolate(&target_value.get(), t))
509 }
510
511 fn scalar_delta(&self, target_value: &Self) -> f32 {
512 (target_value.get() - self.get()) as f32
513 }
514}
515
516struct AnimatedValueBinding<T> {
519 animation_data: RefCell<PropertyValueAnimationData<T>>,
520}
521
522unsafe impl<T: InterpolatedPropertyValue + Clone + 'static> BindingCallable<T>
523 for AnimatedValueBinding<T>
524{
525 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult {
526 let (val, finished) = self.animation_data.borrow_mut().compute_interpolated_value();
527 *value = val;
528 if finished {
529 BindingResult::RemoveBinding
530 } else {
531 crate::animations::CURRENT_ANIMATION_DRIVER
532 .with(|driver| driver.set_has_active_animations());
533 BindingResult::KeepBinding
534 }
535 }
536
537 fn velocity(self: Pin<&Self>) -> Option<f32> {
538 self.animation_data.borrow().current_velocity()
539 }
540}
541
542impl<T: Clone + InterpolatedPropertyValue + 'static> Property<T> {
543 pub fn remove_binding(self: Pin<&Self>) {
548 self.set_animated_value(self.get(), PropertyAnimation::default());
553 }
554
555 pub fn set_animated_value(self: Pin<&Self>, value: T, animation_data: PropertyAnimation) {
562 self.set_animated_value_impl(value, animation_data, None)
563 }
564
565 pub fn set_animated_value_with_map(
569 self: Pin<&Self>,
570 value: T,
571 animation_data: PropertyAnimation,
572 map: fn(T) -> T,
573 ) {
574 self.set_animated_value_impl(value, animation_data, Some(map))
575 }
576
577 fn set_animated_value_impl(
578 self: Pin<&Self>,
579 value: T,
580 animation_data: PropertyAnimation,
581 map: Option<fn(T) -> T>,
582 ) {
583 let carried_velocity = self.handle.current_velocity().unwrap_or(0.0);
585 let mut d = properties_animations::PropertyValueAnimationData::new_with_velocity(
586 self.get(),
587 Some(value),
588 animation_data,
589 carried_velocity,
590 );
591 if let Some(map) = map {
592 d = d.with_map(map);
593 }
594 let binding =
595 properties_animations::AnimatedValueBinding { animation_data: RefCell::new(d) };
596 unsafe {
598 self.handle.set_binding(
599 binding,
600 #[cfg(slint_debug_property)]
601 self.debug_name.borrow().as_str(),
602 );
603 }
604 self.handle.mark_dirty(
605 #[cfg(slint_debug_property)]
606 self.debug_name.borrow().as_str(),
607 );
608 }
609
610 pub fn set_animated_binding(
613 &self,
614 binding: impl Binding<T> + 'static,
615 compute_animation_details: impl Fn() -> (PropertyAnimation, Option<crate::animations::Instant>)
616 + 'static,
617 ) {
618 self.set_animated_binding_impl(binding, compute_animation_details, None)
619 }
620
621 pub fn set_animated_binding_with_map(
625 &self,
626 binding: impl Binding<T> + 'static,
627 compute_animation_details: impl Fn() -> (PropertyAnimation, Option<crate::animations::Instant>)
628 + 'static,
629 map: fn(T) -> T,
630 ) {
631 self.set_animated_binding_impl(binding, compute_animation_details, Some(map))
632 }
633
634 fn set_animated_binding_impl(
635 &self,
636 binding: impl Binding<T> + 'static,
637 compute_animation_details: impl Fn() -> (PropertyAnimation, Option<crate::animations::Instant>)
638 + 'static,
639 map: Option<fn(T) -> T>,
640 ) {
641 let mut animation_data = properties_animations::PropertyValueAnimationData::new(
642 T::default(),
643 None,
644 PropertyAnimation::default(),
645 );
646 if let Some(map) = map {
647 animation_data = animation_data.with_map(map);
648 }
649 let binding_callable = properties_animations::AnimatedBindingCallable::<T, _> {
650 original_binding: PropertyHandle {
651 handle: Cell::new(
652 (alloc_binding_holder(move |val: &mut T| {
653 *val = binding.evaluate(val);
654 BindingResult::KeepBinding
655 }) as *mut ())
656 .map_addr(|a| a | 0b10),
657 ),
658 },
659 state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating),
660 animation_data: RefCell::new(animation_data),
661 compute_animation_details,
662 dirty_time: Cell::new(crate::animations::current_tick()),
663 carried_velocity: Cell::new(0.0),
664 };
665
666 unsafe {
668 self.handle.set_binding(
669 binding_callable,
670 #[cfg(slint_debug_property)]
671 self.debug_name.borrow().as_str(),
672 )
673 };
674 self.handle.mark_dirty(
675 #[cfg(slint_debug_property)]
676 self.debug_name.borrow().as_str(),
677 );
678 }
679}
680
681unsafe impl<Unit, S: Simulation> BindingCallable<Length<crate::Coord, Unit>>
682 for RefCell<PropertyPhysicsAnimationData<S>>
683{
684 fn evaluate(self: Pin<&Self>, value: &mut Length<crate::Coord, Unit>) -> BindingResult {
685 let finished = self.borrow_mut().update_value(&mut value.0);
686 if finished {
687 BindingResult::RemoveBinding
688 } else {
689 crate::animations::CURRENT_ANIMATION_DRIVER
690 .with(|driver| driver.set_has_active_animations());
691 BindingResult::KeepBinding
692 }
693 }
694
695 fn intercept_set(self: Pin<&Self>, _value: &Length<crate::Coord, Unit>) -> bool {
697 true
698 }
699}
700
701impl<Unit> Property<Length<crate::Coord, Unit>> {
702 pub fn set_physic_animation_value<S: Simulation + 'static, AD: Parameter<Output = S>>(
704 &self,
705 limit_value: Pin<Box<Property<f32>>>,
706 simulation_data: AD,
707 ) {
708 unsafe {
710 self.handle.set_binding::<Length<crate::Coord, Unit>, core::cell::RefCell<PropertyPhysicsAnimationData<S>>>(RefCell::new(PropertyPhysicsAnimationData::new(
711 simulation_data.simulation(self.get_internal().0 as f32, limit_value),
712 )),
713 #[cfg(slint_debug_property)]
714 self.debug_name.borrow().as_str()
715 );
716 }
717 self.handle.mark_dirty(
718 #[cfg(slint_debug_property)]
719 self.debug_name.borrow().as_str(),
720 );
721 }
722}
723
724#[cfg(test)]
725mod animation_tests {
726 use super::*;
727 use pin_weak::rc::PinWeak;
728 use std::rc::Rc;
729
730 #[derive(Default)]
731 struct Component {
732 width: Property<i32>,
733 width_times_two: Property<i32>,
734 feed_property: Property<i32>, }
736
737 impl Component {
738 fn new_test_component() -> Pin<Rc<Self>> {
739 let compo = Rc::pin(Component::default());
740 let w = PinWeak::downgrade(compo.clone());
741 compo.width_times_two.set_binding(move || {
742 let compo = w.upgrade().unwrap();
743 get_prop_value(&compo.width) * 2
744 });
745
746 compo
747 }
748 }
749
750 const DURATION: std::time::Duration = std::time::Duration::from_millis(10000);
751 const DELAY: std::time::Duration = std::time::Duration::from_millis(800);
752
753 fn get_prop_value<T: Clone>(prop: &Property<T>) -> T {
755 unsafe { Pin::new_unchecked(prop).get() }
756 }
757
758 fn set_animated_value<T: Clone + InterpolatedPropertyValue + 'static>(
760 prop: &Property<T>,
761 value: T,
762 animation_data: PropertyAnimation,
763 ) {
764 unsafe { Pin::new_unchecked(prop) }.set_animated_value(value, animation_data);
765 }
766
767 #[test]
768 fn properties_test_animation_negative_delay_triggered_by_set() {
769 let compo = Component::new_test_component();
770
771 let animation_details = PropertyAnimation {
772 delay: -25,
773 duration: DURATION.as_millis() as _,
774 iteration_count: 1.,
775 ..PropertyAnimation::default()
776 };
777
778 compo.width.set(100);
779 assert_eq!(get_prop_value(&compo.width), 100);
780 assert_eq!(get_prop_value(&compo.width_times_two), 200);
781
782 let start_time = crate::animations::current_tick();
783
784 set_animated_value(&compo.width, 200, animation_details);
785 assert_eq!(get_prop_value(&compo.width), 100);
786 assert_eq!(get_prop_value(&compo.width_times_two), 200);
787
788 crate::animations::CURRENT_ANIMATION_DRIVER
789 .with(|driver| driver.update_animations(start_time + DURATION / 2));
790 assert_eq!(get_prop_value(&compo.width), 150);
791 assert_eq!(get_prop_value(&compo.width_times_two), 300);
792
793 crate::animations::CURRENT_ANIMATION_DRIVER
794 .with(|driver| driver.update_animations(start_time + DURATION));
795 assert_eq!(get_prop_value(&compo.width), 200);
796 assert_eq!(get_prop_value(&compo.width_times_two), 400);
797
798 crate::animations::CURRENT_ANIMATION_DRIVER
800 .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
801 assert_eq!(get_prop_value(&compo.width), 200);
802 assert_eq!(get_prop_value(&compo.width_times_two), 400);
803
804 compo.width.handle.access(|binding| assert!(binding.is_none()));
806 }
807
808 #[test]
809 fn properties_test_animation_triggered_by_set() {
810 let compo = Component::new_test_component();
811
812 let animation_details = PropertyAnimation {
813 duration: DURATION.as_millis() as _,
814 iteration_count: 1.,
815 ..PropertyAnimation::default()
816 };
817
818 compo.width.set(100);
819 assert_eq!(get_prop_value(&compo.width), 100);
820 assert_eq!(get_prop_value(&compo.width_times_two), 200);
821
822 let start_time = crate::animations::current_tick();
823
824 set_animated_value(&compo.width, 200, animation_details);
825 assert_eq!(get_prop_value(&compo.width), 100);
826 assert_eq!(get_prop_value(&compo.width_times_two), 200);
827
828 crate::animations::CURRENT_ANIMATION_DRIVER
829 .with(|driver| driver.update_animations(start_time + DURATION / 2));
830 assert_eq!(get_prop_value(&compo.width), 150);
831 assert_eq!(get_prop_value(&compo.width_times_two), 300);
832
833 crate::animations::CURRENT_ANIMATION_DRIVER
834 .with(|driver| driver.update_animations(start_time + DURATION));
835 assert_eq!(get_prop_value(&compo.width), 200);
836 assert_eq!(get_prop_value(&compo.width_times_two), 400);
837
838 crate::animations::CURRENT_ANIMATION_DRIVER
840 .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
841 assert_eq!(get_prop_value(&compo.width), 200);
842 assert_eq!(get_prop_value(&compo.width_times_two), 400);
843
844 compo.width.handle.access(|binding| assert!(binding.is_none()));
846 }
847
848 #[test]
849 fn properties_test_delayed_animation_triggered_by_set() {
850 let compo = Component::new_test_component();
851
852 let animation_details = PropertyAnimation {
853 delay: DELAY.as_millis() as _,
854 iteration_count: 1.,
855 duration: DURATION.as_millis() as _,
856 ..PropertyAnimation::default()
857 };
858
859 compo.width.set(100);
860 assert_eq!(get_prop_value(&compo.width), 100);
861 assert_eq!(get_prop_value(&compo.width_times_two), 200);
862
863 let start_time = crate::animations::current_tick();
864
865 set_animated_value(&compo.width, 200, animation_details);
866 assert_eq!(get_prop_value(&compo.width), 100);
867 assert_eq!(get_prop_value(&compo.width_times_two), 200);
868
869 crate::animations::CURRENT_ANIMATION_DRIVER
871 .with(|driver| driver.update_animations(start_time + DELAY / 2));
872 assert_eq!(get_prop_value(&compo.width), 100);
873 assert_eq!(get_prop_value(&compo.width_times_two), 200);
874
875 crate::animations::CURRENT_ANIMATION_DRIVER
877 .with(|driver| driver.update_animations(start_time + DELAY));
878 assert_eq!(get_prop_value(&compo.width), 100);
879 assert_eq!(get_prop_value(&compo.width_times_two), 200);
880
881 crate::animations::CURRENT_ANIMATION_DRIVER
882 .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
883 assert_eq!(get_prop_value(&compo.width), 150);
884 assert_eq!(get_prop_value(&compo.width_times_two), 300);
885
886 crate::animations::CURRENT_ANIMATION_DRIVER
887 .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
888 assert_eq!(get_prop_value(&compo.width), 200);
889 assert_eq!(get_prop_value(&compo.width_times_two), 400);
890
891 crate::animations::CURRENT_ANIMATION_DRIVER
893 .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
894 assert_eq!(get_prop_value(&compo.width), 200);
895 assert_eq!(get_prop_value(&compo.width_times_two), 400);
896
897 compo.width.handle.access(|binding| assert!(binding.is_none()));
899 }
900
901 #[test]
902 fn properties_test_delayed_animation_fractal_iteration_triggered_by_set() {
903 let compo = Component::new_test_component();
904
905 let animation_details = PropertyAnimation {
906 delay: DELAY.as_millis() as _,
907 iteration_count: 1.5,
908 duration: DURATION.as_millis() as _,
909 ..PropertyAnimation::default()
910 };
911
912 compo.width.set(100);
913 assert_eq!(get_prop_value(&compo.width), 100);
914 assert_eq!(get_prop_value(&compo.width_times_two), 200);
915
916 let start_time = crate::animations::current_tick();
917
918 set_animated_value(&compo.width, 200, animation_details);
919 assert_eq!(get_prop_value(&compo.width), 100);
920 assert_eq!(get_prop_value(&compo.width_times_two), 200);
921
922 crate::animations::CURRENT_ANIMATION_DRIVER
924 .with(|driver| driver.update_animations(start_time + DELAY / 2));
925 assert_eq!(get_prop_value(&compo.width), 100);
926 assert_eq!(get_prop_value(&compo.width_times_two), 200);
927
928 crate::animations::CURRENT_ANIMATION_DRIVER
930 .with(|driver| driver.update_animations(start_time + DELAY));
931 assert_eq!(get_prop_value(&compo.width), 100);
932 assert_eq!(get_prop_value(&compo.width_times_two), 200);
933
934 crate::animations::CURRENT_ANIMATION_DRIVER
935 .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
936 assert_eq!(get_prop_value(&compo.width), 150);
937 assert_eq!(get_prop_value(&compo.width_times_two), 300);
938
939 crate::animations::CURRENT_ANIMATION_DRIVER
940 .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
941 assert_eq!(get_prop_value(&compo.width), 100);
942 assert_eq!(get_prop_value(&compo.width_times_two), 200);
943
944 crate::animations::CURRENT_ANIMATION_DRIVER
946 .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 4));
947 assert_eq!(get_prop_value(&compo.width), 125);
948 assert_eq!(get_prop_value(&compo.width_times_two), 250);
949
950 crate::animations::CURRENT_ANIMATION_DRIVER
952 .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
953 assert_eq!(get_prop_value(&compo.width), 200);
954 assert_eq!(get_prop_value(&compo.width_times_two), 400);
955
956 compo.width.handle.access(|binding| assert!(binding.is_none()));
958 }
959 #[test]
960 fn properties_test_delayed_animation_null_duration_triggered_by_set() {
961 let compo = Component::new_test_component();
962
963 let animation_details = PropertyAnimation {
964 delay: DELAY.as_millis() as _,
965 iteration_count: 1.0,
966 duration: 0,
967 ..PropertyAnimation::default()
968 };
969
970 compo.width.set(100);
971 assert_eq!(get_prop_value(&compo.width), 100);
972 assert_eq!(get_prop_value(&compo.width_times_two), 200);
973
974 let start_time = crate::animations::current_tick();
975
976 set_animated_value(&compo.width, 200, animation_details);
977 assert_eq!(get_prop_value(&compo.width), 100);
978 assert_eq!(get_prop_value(&compo.width_times_two), 200);
979
980 crate::animations::CURRENT_ANIMATION_DRIVER
982 .with(|driver| driver.update_animations(start_time + DELAY / 2));
983 assert_eq!(get_prop_value(&compo.width), 100);
984 assert_eq!(get_prop_value(&compo.width_times_two), 200);
985
986 crate::animations::CURRENT_ANIMATION_DRIVER
988 .with(|driver| driver.update_animations(start_time + DELAY));
989 assert_eq!(get_prop_value(&compo.width), 200);
990 assert_eq!(get_prop_value(&compo.width_times_two), 400);
991
992 crate::animations::CURRENT_ANIMATION_DRIVER
994 .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
995 assert_eq!(get_prop_value(&compo.width), 200);
996 assert_eq!(get_prop_value(&compo.width_times_two), 400);
997
998 compo.width.handle.access(|binding| assert!(binding.is_none()));
1000 }
1001
1002 #[test]
1003 fn properties_test_delayed_animation_negative_duration_triggered_by_set() {
1004 let compo = Component::new_test_component();
1005
1006 let animation_details = PropertyAnimation {
1007 delay: DELAY.as_millis() as _,
1008 iteration_count: 1.0,
1009 duration: -25,
1010 ..PropertyAnimation::default()
1011 };
1012
1013 compo.width.set(100);
1014 assert_eq!(get_prop_value(&compo.width), 100);
1015 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1016
1017 let start_time = crate::animations::current_tick();
1018
1019 set_animated_value(&compo.width, 200, animation_details);
1020 assert_eq!(get_prop_value(&compo.width), 100);
1021 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1022
1023 crate::animations::CURRENT_ANIMATION_DRIVER
1025 .with(|driver| driver.update_animations(start_time + DELAY / 2));
1026 assert_eq!(get_prop_value(&compo.width), 100);
1027 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1028
1029 crate::animations::CURRENT_ANIMATION_DRIVER
1031 .with(|driver| driver.update_animations(start_time + DELAY));
1032 assert_eq!(get_prop_value(&compo.width), 200);
1033 assert_eq!(get_prop_value(&compo.width_times_two), 400);
1034
1035 crate::animations::CURRENT_ANIMATION_DRIVER
1037 .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
1038 assert_eq!(get_prop_value(&compo.width), 200);
1039 assert_eq!(get_prop_value(&compo.width_times_two), 400);
1040
1041 compo.width.handle.access(|binding| assert!(binding.is_none()));
1043 }
1044
1045 #[test]
1046 fn properties_test_delayed_animation_no_iteration_triggered_by_set() {
1047 let compo = Component::new_test_component();
1048
1049 let animation_details = PropertyAnimation {
1050 delay: DELAY.as_millis() as _,
1051 iteration_count: 0.0,
1052 duration: DURATION.as_millis() as _,
1053 ..PropertyAnimation::default()
1054 };
1055
1056 compo.width.set(100);
1057 assert_eq!(get_prop_value(&compo.width), 100);
1058 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1059
1060 let start_time = crate::animations::current_tick();
1061
1062 set_animated_value(&compo.width, 200, animation_details);
1063 assert_eq!(get_prop_value(&compo.width), 100);
1064 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1065
1066 crate::animations::CURRENT_ANIMATION_DRIVER
1068 .with(|driver| driver.update_animations(start_time + DELAY / 2));
1069 assert_eq!(get_prop_value(&compo.width), 100);
1070 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1071
1072 crate::animations::CURRENT_ANIMATION_DRIVER
1074 .with(|driver| driver.update_animations(start_time + DELAY));
1075 assert_eq!(get_prop_value(&compo.width), 200);
1076 assert_eq!(get_prop_value(&compo.width_times_two), 400);
1077
1078 crate::animations::CURRENT_ANIMATION_DRIVER
1080 .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
1081 assert_eq!(get_prop_value(&compo.width), 200);
1082 assert_eq!(get_prop_value(&compo.width_times_two), 400);
1083
1084 compo.width.handle.access(|binding| assert!(binding.is_none()));
1086 }
1087
1088 #[test]
1089 fn properties_test_delayed_animation_negative_iteration_triggered_by_set() {
1090 let compo = Component::new_test_component();
1091
1092 let animation_details = PropertyAnimation {
1093 delay: DELAY.as_millis() as _,
1094 iteration_count: -42., duration: DURATION.as_millis() as _,
1096 ..PropertyAnimation::default()
1097 };
1098
1099 compo.width.set(100);
1100 assert_eq!(get_prop_value(&compo.width), 100);
1101 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1102
1103 let start_time = crate::animations::current_tick();
1104
1105 set_animated_value(&compo.width, 200, animation_details);
1106 assert_eq!(get_prop_value(&compo.width), 100);
1107 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1108
1109 crate::animations::CURRENT_ANIMATION_DRIVER
1111 .with(|driver| driver.update_animations(start_time + DELAY / 2));
1112 assert_eq!(get_prop_value(&compo.width), 100);
1113 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1114
1115 crate::animations::CURRENT_ANIMATION_DRIVER
1117 .with(|driver| driver.update_animations(start_time + DELAY));
1118 assert_eq!(get_prop_value(&compo.width), 100);
1119 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1120
1121 crate::animations::CURRENT_ANIMATION_DRIVER
1122 .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
1123 assert_eq!(get_prop_value(&compo.width), 150);
1124 assert_eq!(get_prop_value(&compo.width_times_two), 300);
1125
1126 crate::animations::CURRENT_ANIMATION_DRIVER
1127 .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
1128 assert_eq!(get_prop_value(&compo.width), 100);
1129 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1130
1131 crate::animations::CURRENT_ANIMATION_DRIVER
1133 .with(|driver| driver.update_animations(start_time + DELAY + 500 * DURATION));
1134 assert_eq!(get_prop_value(&compo.width), 100);
1135 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1136
1137 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1138 driver.update_animations(start_time + DELAY + 50000 * DURATION + DURATION / 2)
1139 });
1140 assert_eq!(get_prop_value(&compo.width), 150);
1141 assert_eq!(get_prop_value(&compo.width_times_two), 300);
1142
1143 compo.width.handle.access(|binding| assert!(binding.is_some()));
1145 }
1146
1147 #[test]
1148 fn properties_test_animation_direction_triggered_by_set() {
1149 let compo = Component::new_test_component();
1150
1151 let animation_details = PropertyAnimation {
1152 delay: -25,
1153 duration: DURATION.as_millis() as _,
1154 direction: AnimationDirection::AlternateReverse,
1155 iteration_count: 1.,
1156 ..PropertyAnimation::default()
1157 };
1158
1159 compo.width.set(100);
1160 assert_eq!(get_prop_value(&compo.width), 100);
1161 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1162
1163 let start_time = crate::animations::current_tick();
1164
1165 set_animated_value(&compo.width, 200, animation_details);
1166 assert_eq!(get_prop_value(&compo.width), 200);
1167 assert_eq!(get_prop_value(&compo.width_times_two), 400);
1168
1169 crate::animations::CURRENT_ANIMATION_DRIVER
1170 .with(|driver| driver.update_animations(start_time + DURATION / 2));
1171 assert_eq!(get_prop_value(&compo.width), 150);
1172 assert_eq!(get_prop_value(&compo.width_times_two), 300);
1173
1174 crate::animations::CURRENT_ANIMATION_DRIVER
1175 .with(|driver| driver.update_animations(start_time + DURATION));
1176 assert_eq!(get_prop_value(&compo.width), 100);
1177 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1178
1179 crate::animations::CURRENT_ANIMATION_DRIVER
1181 .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
1182 assert_eq!(get_prop_value(&compo.width), 100);
1183 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1184
1185 compo.width.handle.access(|binding| assert!(binding.is_none()));
1187 }
1188
1189 #[test]
1190 fn properties_test_animation_triggered_by_binding() {
1191 let compo = Component::new_test_component();
1192
1193 let start_time = crate::animations::current_tick();
1194
1195 let animation_details = PropertyAnimation {
1196 duration: DURATION.as_millis() as _,
1197 iteration_count: 1.,
1198 ..PropertyAnimation::default()
1199 };
1200
1201 let w = PinWeak::downgrade(compo.clone());
1202 compo.width.set_animated_binding(
1203 move || {
1204 let compo = w.upgrade().unwrap();
1205 get_prop_value(&compo.feed_property)
1206 },
1207 move || (animation_details.clone(), None),
1208 );
1209
1210 compo.feed_property.set(100);
1211 assert_eq!(get_prop_value(&compo.width), 100);
1212 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1213
1214 compo.feed_property.set(200);
1215 assert_eq!(get_prop_value(&compo.width), 100);
1216 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1217
1218 crate::animations::CURRENT_ANIMATION_DRIVER
1219 .with(|driver| driver.update_animations(start_time + DURATION / 2));
1220 assert_eq!(get_prop_value(&compo.width), 150);
1221 assert_eq!(get_prop_value(&compo.width_times_two), 300);
1222
1223 crate::animations::CURRENT_ANIMATION_DRIVER
1224 .with(|driver| driver.update_animations(start_time + DURATION));
1225 assert_eq!(get_prop_value(&compo.width), 200);
1226 assert_eq!(get_prop_value(&compo.width_times_two), 400);
1227 }
1228
1229 #[test]
1230 fn properties_test_delayed_animation_triggered_by_binding() {
1231 let compo = Component::new_test_component();
1232
1233 let start_time = crate::animations::current_tick();
1234
1235 let animation_details = PropertyAnimation {
1236 delay: DELAY.as_millis() as _,
1237 duration: DURATION.as_millis() as _,
1238 iteration_count: 1.0,
1239 ..PropertyAnimation::default()
1240 };
1241
1242 let w = PinWeak::downgrade(compo.clone());
1243 compo.width.set_animated_binding(
1244 move || {
1245 let compo = w.upgrade().unwrap();
1246 get_prop_value(&compo.feed_property)
1247 },
1248 move || (animation_details.clone(), None),
1249 );
1250
1251 compo.feed_property.set(100);
1252 assert_eq!(get_prop_value(&compo.width), 100);
1253 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1254
1255 compo.feed_property.set(200);
1256 assert_eq!(get_prop_value(&compo.width), 100);
1257 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1258
1259 crate::animations::CURRENT_ANIMATION_DRIVER
1261 .with(|driver| driver.update_animations(start_time + DELAY / 2));
1262 assert_eq!(get_prop_value(&compo.width), 100);
1263 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1264
1265 crate::animations::CURRENT_ANIMATION_DRIVER
1267 .with(|driver| driver.update_animations(start_time + DELAY));
1268 assert_eq!(get_prop_value(&compo.width), 100);
1269 assert_eq!(get_prop_value(&compo.width_times_two), 200);
1270
1271 crate::animations::CURRENT_ANIMATION_DRIVER
1272 .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
1273 assert_eq!(get_prop_value(&compo.width), 150);
1274 assert_eq!(get_prop_value(&compo.width_times_two), 300);
1275
1276 crate::animations::CURRENT_ANIMATION_DRIVER
1277 .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
1278 assert_eq!(get_prop_value(&compo.width), 200);
1279 assert_eq!(get_prop_value(&compo.width_times_two), 400);
1280
1281 crate::animations::CURRENT_ANIMATION_DRIVER
1283 .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
1284 assert_eq!(get_prop_value(&compo.width), 200);
1285 assert_eq!(get_prop_value(&compo.width_times_two), 400);
1286 }
1287
1288 #[test]
1289 fn properties_test_animation_triggered_by_binding_with_unrelated_dirty() {
1290 #[derive(Default)]
1295 struct Component {
1296 width: Property<i32>,
1297 row: Property<(i32, bool)>,
1298 }
1299
1300 let compo = Rc::pin(Component::default());
1301
1302 let animation_details = PropertyAnimation {
1303 duration: DURATION.as_millis() as _,
1304 iteration_count: 1.,
1305 ..PropertyAnimation::default()
1306 };
1307
1308 let w = PinWeak::downgrade(compo.clone());
1309 compo.width.set_animated_binding(
1310 move || {
1311 let compo = w.upgrade().unwrap();
1312 if get_prop_value(&compo.row).1 { 200 } else { 40 }
1313 },
1314 move || (animation_details.clone(), None),
1315 );
1316
1317 compo.row.set((0, false));
1318 assert_eq!(get_prop_value(&compo.width), 40);
1319
1320 let start_time = crate::animations::current_tick();
1321
1322 compo.row.set((0, true));
1325 assert_eq!(get_prop_value(&compo.width), 40);
1326
1327 let tick = core::time::Duration::from_millis(16);
1330 for i in 1..=700u32 {
1331 compo.row.set((i as i32, true));
1332 crate::animations::CURRENT_ANIMATION_DRIVER
1333 .with(|driver| driver.update_animations(start_time + tick * i));
1334 let _ = get_prop_value(&compo.width);
1336 }
1337
1338 assert_eq!(get_prop_value(&compo.width), 200);
1341 }
1342
1343 #[test]
1344 fn test_loop() {
1345 let compo = Component::new_test_component();
1346
1347 let animation_details = PropertyAnimation {
1348 duration: DURATION.as_millis() as _,
1349 iteration_count: 2.,
1350 ..PropertyAnimation::default()
1351 };
1352
1353 compo.width.set(100);
1354
1355 let start_time = crate::animations::current_tick();
1356
1357 set_animated_value(&compo.width, 200, animation_details);
1358 assert_eq!(get_prop_value(&compo.width), 100);
1359
1360 crate::animations::CURRENT_ANIMATION_DRIVER
1361 .with(|driver| driver.update_animations(start_time + DURATION / 2));
1362 assert_eq!(get_prop_value(&compo.width), 150);
1363
1364 crate::animations::CURRENT_ANIMATION_DRIVER
1365 .with(|driver| driver.update_animations(start_time + DURATION));
1366 assert_eq!(get_prop_value(&compo.width), 100);
1367
1368 crate::animations::CURRENT_ANIMATION_DRIVER
1369 .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
1370 assert_eq!(get_prop_value(&compo.width), 150);
1371
1372 crate::animations::CURRENT_ANIMATION_DRIVER
1373 .with(|driver| driver.update_animations(start_time + DURATION * 2));
1374 assert_eq!(get_prop_value(&compo.width), 200);
1375
1376 compo.width.handle.access(|binding| assert!(binding.is_none()));
1378 }
1379
1380 #[test]
1381 fn test_loop_via_binding() {
1382 let compo = Component::new_test_component();
1385
1386 let start_time = crate::animations::current_tick();
1387
1388 let animation_details = PropertyAnimation {
1389 duration: DURATION.as_millis() as _,
1390 iteration_count: 2.,
1391 ..PropertyAnimation::default()
1392 };
1393
1394 let w = PinWeak::downgrade(compo.clone());
1395 compo.width.set_animated_binding(
1396 move || {
1397 let compo = w.upgrade().unwrap();
1398 get_prop_value(&compo.feed_property)
1399 },
1400 move || (animation_details.clone(), None),
1401 );
1402
1403 compo.feed_property.set(100);
1404 assert_eq!(get_prop_value(&compo.width), 100);
1405
1406 compo.feed_property.set(200);
1407 assert_eq!(get_prop_value(&compo.width), 100);
1408
1409 crate::animations::CURRENT_ANIMATION_DRIVER
1410 .with(|driver| driver.update_animations(start_time + DURATION / 2));
1411
1412 assert_eq!(get_prop_value(&compo.width), 150);
1413
1414 crate::animations::CURRENT_ANIMATION_DRIVER
1415 .with(|driver| driver.update_animations(start_time + DURATION));
1416
1417 assert_eq!(get_prop_value(&compo.width), 100);
1418
1419 crate::animations::CURRENT_ANIMATION_DRIVER
1420 .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
1421
1422 assert_eq!(get_prop_value(&compo.width), 150);
1423
1424 crate::animations::CURRENT_ANIMATION_DRIVER
1425 .with(|driver| driver.update_animations(start_time + 2 * DURATION));
1426
1427 assert_eq!(get_prop_value(&compo.width), 200);
1428
1429 crate::animations::CURRENT_ANIMATION_DRIVER
1431 .with(|driver| driver.update_animations(start_time + 2 * DURATION + DURATION / 2));
1432
1433 assert_eq!(get_prop_value(&compo.width), 200);
1434
1435 let start_time = crate::animations::current_tick();
1438
1439 compo.feed_property.set(300);
1440 assert_eq!(get_prop_value(&compo.width), 200);
1441
1442 crate::animations::CURRENT_ANIMATION_DRIVER
1443 .with(|driver| driver.update_animations(start_time + DURATION / 2));
1444
1445 assert_eq!(get_prop_value(&compo.width), 250);
1446
1447 crate::animations::CURRENT_ANIMATION_DRIVER
1448 .with(|driver| driver.update_animations(start_time + DURATION));
1449
1450 assert_eq!(get_prop_value(&compo.width), 200);
1451
1452 crate::animations::CURRENT_ANIMATION_DRIVER
1453 .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
1454
1455 assert_eq!(get_prop_value(&compo.width), 250);
1456
1457 crate::animations::CURRENT_ANIMATION_DRIVER
1458 .with(|driver| driver.update_animations(start_time + 2 * DURATION));
1459
1460 assert_eq!(get_prop_value(&compo.width), 300);
1461
1462 crate::animations::CURRENT_ANIMATION_DRIVER
1463 .with(|driver| driver.update_animations(start_time + 2 * DURATION + DURATION / 2));
1464
1465 assert_eq!(get_prop_value(&compo.width), 300);
1466 }
1467
1468 #[test]
1469 fn spring_retarget_carries_velocity() {
1470 let compo = Component::new_test_component();
1473
1474 let spring_details = PropertyAnimation {
1475 duration: 1000,
1476 easing: crate::animations::EasingCurve::Spring(0.0),
1477 ..PropertyAnimation::default()
1478 };
1479
1480 compo.width.set(0);
1481 let start_time = crate::animations::current_tick();
1482 set_animated_value(&compo.width, 1000, spring_details.clone());
1483
1484 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1486 driver.update_animations(start_time + core::time::Duration::from_millis(300))
1487 });
1488 let before_a = get_prop_value(&compo.width) as f32;
1489 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1490 driver.update_animations(start_time + core::time::Duration::from_millis(310))
1491 });
1492 let before_b = get_prop_value(&compo.width) as f32;
1493 let slope_before = before_b - before_a; set_animated_value(&compo.width, 2000, spring_details);
1497 assert_eq!(
1498 get_prop_value(&compo.width) as f32,
1499 before_b,
1500 "retarget must not snap the value"
1501 );
1502
1503 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1504 driver.update_animations(start_time + core::time::Duration::from_millis(320))
1505 });
1506 let after = get_prop_value(&compo.width) as f32;
1507 let slope_after = after - before_b; assert!(slope_before > 0.5, "sanity check: spring should be moving before retarget");
1513 assert!(
1514 slope_after > slope_before * 0.5,
1515 "velocity was not carried over: slope_before={slope_before}, slope_after={slope_after}"
1516 );
1517 }
1518
1519 #[test]
1520 fn spring_retarget_via_binding_carries_velocity() {
1521 let compo = Component::new_test_component();
1522
1523 let spring_details = PropertyAnimation {
1524 duration: 1000,
1525 easing: crate::animations::EasingCurve::Spring(0.0),
1526 ..PropertyAnimation::default()
1527 };
1528
1529 let w = PinWeak::downgrade(compo.clone());
1530 let details = spring_details.clone();
1531 compo.width.set_animated_binding(
1532 move || {
1533 let compo = w.upgrade().unwrap();
1534 get_prop_value(&compo.feed_property)
1535 },
1536 move || (details.clone(), None),
1537 );
1538
1539 compo.feed_property.set(0);
1541 assert_eq!(get_prop_value(&compo.width), 0);
1542
1543 let start_time = crate::animations::current_tick();
1544 compo.feed_property.set(1000);
1545 assert_eq!(get_prop_value(&compo.width), 0);
1546
1547 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1549 driver.update_animations(start_time + core::time::Duration::from_millis(300))
1550 });
1551 let before_a = get_prop_value(&compo.width) as f32;
1552 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1553 driver.update_animations(start_time + core::time::Duration::from_millis(310))
1554 });
1555 let before_b = get_prop_value(&compo.width) as f32;
1556 let slope_before = before_b - before_a; compo.feed_property.set(2000);
1560 assert_eq!(
1561 get_prop_value(&compo.width) as f32,
1562 before_b,
1563 "retarget must not snap the value"
1564 );
1565
1566 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1567 driver.update_animations(start_time + core::time::Duration::from_millis(320))
1568 });
1569 let after = get_prop_value(&compo.width) as f32;
1570 let slope_after = after - before_b; assert!(slope_before > 0.5, "sanity check: spring should be moving before retarget");
1573 assert!(
1574 slope_after > slope_before * 0.5,
1575 "velocity was not carried over through the binding-triggered retarget path: slope_before={slope_before}, slope_after={slope_after}"
1576 );
1577 }
1578
1579 #[test]
1580 fn spring_continuous_retarget_keeps_advancing() {
1581 let compo = Component::new_test_component();
1582
1583 let spring_details = PropertyAnimation {
1584 duration: 1000,
1585 easing: crate::animations::EasingCurve::Spring(0.7),
1586 ..PropertyAnimation::default()
1587 };
1588
1589 let w = PinWeak::downgrade(compo.clone());
1590 let details = spring_details.clone();
1591 compo.width.set_animated_binding(
1592 move || {
1593 let compo = w.upgrade().unwrap();
1594 get_prop_value(&compo.feed_property)
1595 },
1596 move || (details.clone(), None),
1597 );
1598
1599 compo.feed_property.set(0);
1600 assert_eq!(get_prop_value(&compo.width), 0);
1601
1602 let start_time = crate::animations::current_tick();
1603 let mut mouse_x = 0i32;
1604 let mut final_width = 0f32;
1605 for frame in 1..=200 {
1606 mouse_x += 5; compo.feed_property.set(mouse_x);
1608 let t = start_time + core::time::Duration::from_millis(frame * 16);
1609 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.update_animations(t));
1610 final_width = get_prop_value(&compo.width) as f32;
1613 }
1614
1615 assert!(
1616 final_width > 500.0,
1617 "spring should have tracked the continuously-moving target by now, got {final_width}"
1618 );
1619 }
1620
1621 #[test]
1622 fn spring_respects_reverse_direction() {
1623 let compo = Component::new_test_component();
1624
1625 let spring_details = PropertyAnimation {
1626 duration: 200,
1627 easing: crate::animations::EasingCurve::Spring(0.0),
1628 direction: AnimationDirection::Reverse,
1629 ..PropertyAnimation::default()
1630 };
1631
1632 compo.width.set(0);
1633 let start_time = crate::animations::current_tick();
1634 set_animated_value(&compo.width, 100, spring_details);
1635
1636 assert_eq!(get_prop_value(&compo.width), 100);
1638
1639 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1640 driver.update_animations(start_time + core::time::Duration::from_millis(2000))
1641 });
1642 assert_eq!(get_prop_value(&compo.width), 0);
1643
1644 compo.width.handle.access(|binding| assert!(binding.is_none()));
1646 }
1647
1648 #[test]
1649 fn spring_respects_iteration_count_bounce() {
1650 let compo = Component::new_test_component();
1651
1652 let spring_details = PropertyAnimation {
1653 duration: 200,
1654 easing: crate::animations::EasingCurve::Spring(0.0),
1655 direction: AnimationDirection::Alternate,
1656 iteration_count: 2.,
1657 ..PropertyAnimation::default()
1658 };
1659
1660 compo.width.set(0);
1661 let start_time = crate::animations::current_tick();
1662 set_animated_value(&compo.width, 100, spring_details);
1663 assert_eq!(get_prop_value(&compo.width), 0);
1664
1665 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1668 driver.update_animations(start_time + core::time::Duration::from_millis(200))
1669 });
1670 let mid = get_prop_value(&compo.width);
1671 assert!(mid > 90, "expected the first leg to have reached the target, got {mid}");
1672 compo.width.handle.access(|binding| assert!(binding.is_some()));
1673
1674 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1676 driver.update_animations(start_time + core::time::Duration::from_millis(600))
1677 });
1678 assert_eq!(get_prop_value(&compo.width), 0);
1679 compo.width.handle.access(|binding| assert!(binding.is_none()));
1680 }
1681
1682 #[test]
1683 fn spring_never_settling_on_its_own_settles_within_budget() {
1684 let compo = Component::new_test_component();
1688
1689 let spring_details = PropertyAnimation {
1690 duration: 200,
1691 easing: crate::animations::EasingCurve::Spring(1.0),
1692 ..PropertyAnimation::default()
1693 };
1694
1695 compo.width.set(0);
1696 let start_time = crate::animations::current_tick();
1697 set_animated_value(&compo.width, 100, spring_details);
1698
1699 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1702 driver.update_animations(start_time + core::time::Duration::from_millis(200))
1703 });
1704 assert!(get_prop_value(&compo.width) < 20, "should still be near the start at duration");
1705 compo.width.handle.access(|binding| assert!(binding.is_some()));
1706
1707 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1709 driver.update_animations(start_time + core::time::Duration::from_millis(2000))
1710 });
1711 assert_eq!(get_prop_value(&compo.width), 100);
1712 compo.width.handle.access(|binding| assert!(binding.is_none()));
1713 }
1714
1715 #[test]
1716 fn spring_never_settling_stays_infinite_with_iteration_count_minus_one() {
1717 let compo = Component::new_test_component();
1720
1721 let spring_details = PropertyAnimation {
1722 duration: 200,
1723 easing: crate::animations::EasingCurve::Spring(1.0),
1724 iteration_count: -1.,
1725 direction: AnimationDirection::Alternate,
1726 ..PropertyAnimation::default()
1727 };
1728
1729 compo.width.set(0);
1730 let start_time = crate::animations::current_tick();
1731 set_animated_value(&compo.width, 100, spring_details);
1732
1733 crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
1735 driver.update_animations(start_time + core::time::Duration::from_millis(3000))
1736 });
1737 compo.width.handle.access(|binding| assert!(binding.is_some()));
1738 }
1739}