Skip to main content

i_slint_core/properties/
properties_animations.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use super::*;
5use crate::{
6    items::{AnimationDirection, PropertyAnimation},
7    lengths::LogicalLength,
8};
9#[cfg(not(feature = "std"))]
10use num_traits::Float;
11
12enum AnimationState {
13    /// The animation will start after the delay is finished
14    Delaying,
15    /// Actual animation
16    Animating {
17        current_iteration: u64,
18    },
19    Done {
20        iteration_count: u64,
21    },
22}
23
24pub(super) struct PropertyValueAnimationData<T> {
25    from_value: T,
26    to_value: T,
27    details: PropertyAnimation,
28    start_time: crate::animations::Instant,
29    state: AnimationState,
30}
31
32impl<T: InterpolatedPropertyValue + Clone> PropertyValueAnimationData<T> {
33    pub fn new(from_value: T, to_value: T, details: PropertyAnimation) -> Self {
34        let start_time = crate::animations::current_tick();
35
36        Self { from_value, to_value, details, start_time, state: AnimationState::Delaying }
37    }
38
39    /// Single iteration of the animation
40    pub fn compute_interpolated_value(&mut self) -> (T, bool) {
41        let new_tick = crate::animations::current_tick();
42        let mut time_progress = new_tick.duration_since(self.start_time).as_millis() as u64;
43        let reversed = |iteration: u64| -> bool {
44            match self.details.direction {
45                AnimationDirection::Normal => false,
46                AnimationDirection::Reverse => true,
47                AnimationDirection::Alternate => iteration % 2 == 1,
48                AnimationDirection::AlternateReverse => iteration % 2 == 0,
49            }
50        };
51
52        match self.state {
53            AnimationState::Delaying => {
54                if self.details.delay <= 0 {
55                    self.state = AnimationState::Animating { current_iteration: 0 };
56                    return self.compute_interpolated_value();
57                }
58
59                let delay = self.details.delay as u64;
60
61                if time_progress < delay {
62                    if reversed(0) {
63                        (self.to_value.clone(), false)
64                    } else {
65                        (self.from_value.clone(), false)
66                    }
67                } else {
68                    self.start_time =
69                        new_tick - core::time::Duration::from_millis(time_progress - delay);
70
71                    // Decide on next state:
72                    self.state = AnimationState::Animating { current_iteration: 0 };
73                    self.compute_interpolated_value()
74                }
75            }
76            AnimationState::Animating { mut current_iteration } => {
77                if self.details.duration <= 0 || self.details.iteration_count == 0. {
78                    self.state = AnimationState::Done { iteration_count: 0 };
79                    return self.compute_interpolated_value();
80                }
81
82                let duration = self.details.duration as u64;
83                if time_progress >= duration {
84                    // wrap around
85                    current_iteration += time_progress / duration;
86                    time_progress %= duration;
87                    self.start_time = new_tick - core::time::Duration::from_millis(time_progress);
88                }
89
90                if (self.details.iteration_count < 0.)
91                    || (((current_iteration * duration) + time_progress) as f64)
92                        < ((self.details.iteration_count as f64) * (duration as f64))
93                {
94                    self.state = AnimationState::Animating { current_iteration };
95
96                    let progress = {
97                        let progress =
98                            (time_progress as f32 / self.details.duration as f32).clamp(0., 1.);
99                        if reversed(current_iteration) { 1. - progress } else { progress }
100                    };
101                    let t = crate::animations::easing_curve(&self.details.easing, progress);
102                    let val = self.from_value.interpolate(&self.to_value, t);
103
104                    (val, false)
105                } else {
106                    self.state =
107                        AnimationState::Done { iteration_count: current_iteration.max(1) - 1 };
108                    self.compute_interpolated_value()
109                }
110            }
111            AnimationState::Done { iteration_count } => {
112                if reversed(iteration_count) {
113                    (self.from_value.clone(), true)
114                } else {
115                    (self.to_value.clone(), true)
116                }
117            }
118        }
119    }
120
121    fn reset(&mut self) {
122        self.state = AnimationState::Delaying;
123        self.start_time = crate::animations::current_tick();
124    }
125}
126
127#[derive(Clone, Copy, Eq, PartialEq, Debug)]
128pub(super) enum AnimatedBindingState {
129    Animating,
130    NotAnimating,
131    ShouldStart,
132}
133
134#[pin_project::pin_project]
135pub(super) struct AnimatedBindingCallable<T, A> {
136    #[pin]
137    pub(super) original_binding: PropertyHandle,
138    pub(super) state: Cell<AnimatedBindingState>,
139    pub(super) animation_data: RefCell<PropertyValueAnimationData<T>>,
140    pub(super) compute_animation_details: A,
141}
142
143pub(super) type AnimationDetail = (PropertyAnimation, Option<crate::animations::Instant>);
144
145unsafe impl<T: InterpolatedPropertyValue + Clone, A: Fn() -> AnimationDetail> BindingCallable<T>
146    for AnimatedBindingCallable<T, A>
147{
148    fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult {
149        let original_binding = self.project_ref().original_binding;
150        original_binding.register_as_dependency_to_current_binding(
151            #[cfg(slint_debug_property)]
152            "<AnimatedBindingCallable>",
153        );
154        match self.state.get() {
155            AnimatedBindingState::Animating => {
156                let (val, finished) = self.animation_data.borrow_mut().compute_interpolated_value();
157                *value = val;
158                if finished {
159                    self.state.set(AnimatedBindingState::NotAnimating)
160                } else {
161                    crate::animations::CURRENT_ANIMATION_DRIVER
162                        .with(|driver| driver.set_has_active_animations());
163                }
164            }
165            AnimatedBindingState::NotAnimating => {
166                // Safety: `value` is a valid mutable reference
167                unsafe { self.original_binding.update(value as *mut T) };
168            }
169            AnimatedBindingState::ShouldStart => {
170                self.state.set(AnimatedBindingState::Animating);
171                let mut animation_data = self.animation_data.borrow_mut();
172                // animation_data.details.iteration_count = 1.;
173                animation_data.from_value = value.clone();
174                let (details, start_time) = (self.compute_animation_details)();
175                if let Some(start_time) = start_time {
176                    animation_data.start_time = start_time;
177                }
178                animation_data.details = details;
179
180                // Safety: `animation_data.to_value` is a valid mutable reference
181                unsafe { self.original_binding.update((&mut animation_data.to_value) as *mut T) };
182                let (val, finished) = animation_data.compute_interpolated_value();
183                *value = val;
184                if finished {
185                    self.state.set(AnimatedBindingState::NotAnimating)
186                } else {
187                    crate::animations::CURRENT_ANIMATION_DRIVER
188                        .with(|driver| driver.set_has_active_animations());
189                }
190            }
191        };
192        BindingResult::KeepBinding
193    }
194    fn mark_dirty(self: Pin<&Self>) {
195        if self.state.get() == AnimatedBindingState::ShouldStart {
196            return;
197        }
198        let original_dirty = self.original_binding.access(|b| b.unwrap().dirty.get());
199        if original_dirty {
200            self.state.set(AnimatedBindingState::ShouldStart);
201            self.animation_data.borrow_mut().reset();
202        }
203    }
204}
205
206/// InterpolatedPropertyValue is a trait used to enable properties to be used with
207/// animations that interpolate values. The basic requirement is the ability to apply
208/// a progress that's typically between 0 and 1 to a range.
209pub trait InterpolatedPropertyValue: PartialEq + Default + 'static {
210    /// Returns the interpolated value between self and target_value according to the
211    /// progress parameter t that's usually between 0 and 1. With certain animation
212    /// easing curves it may over- or undershoot though.
213    #[must_use]
214    fn interpolate(&self, target_value: &Self, t: f32) -> Self;
215}
216
217impl InterpolatedPropertyValue for f32 {
218    fn interpolate(&self, target_value: &Self, t: f32) -> Self {
219        self + t * (target_value - self)
220    }
221}
222
223impl InterpolatedPropertyValue for i32 {
224    fn interpolate(&self, target_value: &Self, t: f32) -> Self {
225        self + (t * (target_value - self) as f32).round() as i32
226    }
227}
228
229impl InterpolatedPropertyValue for i64 {
230    fn interpolate(&self, target_value: &Self, t: f32) -> Self {
231        self + (t * (target_value - self) as f32).round() as Self
232    }
233}
234
235impl InterpolatedPropertyValue for u8 {
236    fn interpolate(&self, target_value: &Self, t: f32) -> Self {
237        ((*self as f32) + (t * ((*target_value as f32) - (*self as f32)))).round().clamp(0., 255.)
238            as u8
239    }
240}
241
242impl InterpolatedPropertyValue for LogicalLength {
243    fn interpolate(&self, target_value: &Self, t: f32) -> Self {
244        LogicalLength::new(self.get().interpolate(&target_value.get(), t))
245    }
246}
247
248impl<T: Clone + InterpolatedPropertyValue + 'static> Property<T> {
249    /// Change the value of this property, by animating (interpolating) from the current property's value
250    /// to the specified parameter value. The animation is done according to the parameters described by
251    /// the PropertyAnimation object.
252    ///
253    /// If other properties have binding depending of this property, these properties will
254    /// be marked as dirty.
255    pub fn set_animated_value(&self, value: T, animation_data: PropertyAnimation) {
256        // FIXME if the current value is a dirty binding, we must run it, but we do not have the context
257        let d = RefCell::new(properties_animations::PropertyValueAnimationData::new(
258            self.get_internal(),
259            value,
260            animation_data,
261        ));
262        // Safety: the BindingCallable will cast its argument to T
263        unsafe {
264            self.handle.set_binding(
265                move |val: &mut T| {
266                    let (value, finished) = d.borrow_mut().compute_interpolated_value();
267                    *val = value;
268                    if finished {
269                        BindingResult::RemoveBinding
270                    } else {
271                        crate::animations::CURRENT_ANIMATION_DRIVER
272                            .with(|driver| driver.set_has_active_animations());
273                        BindingResult::KeepBinding
274                    }
275                },
276                #[cfg(slint_debug_property)]
277                self.debug_name.borrow().as_str(),
278            );
279        }
280        self.handle.mark_dirty(
281            #[cfg(slint_debug_property)]
282            self.debug_name.borrow().as_str(),
283        );
284    }
285
286    /// Set a binding to this property, providing a callback for the animation and an optional
287    /// start_time (relevant for state transitions).
288    pub fn set_animated_binding(
289        &self,
290        binding: impl Binding<T> + 'static,
291        compute_animation_details: impl Fn() -> (PropertyAnimation, Option<crate::animations::Instant>)
292        + 'static,
293    ) {
294        let binding_callable = properties_animations::AnimatedBindingCallable::<T, _> {
295            original_binding: PropertyHandle {
296                handle: Cell::new(
297                    (alloc_binding_holder(move |val: &mut T| {
298                        *val = binding.evaluate(val);
299                        BindingResult::KeepBinding
300                    }) as usize)
301                        | 0b10,
302                ),
303            },
304            state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating),
305            animation_data: RefCell::new(properties_animations::PropertyValueAnimationData::new(
306                T::default(),
307                T::default(),
308                PropertyAnimation::default(),
309            )),
310            compute_animation_details,
311        };
312
313        // Safety: the `AnimatedBindingCallable`'s type match the property type
314        unsafe {
315            self.handle.set_binding(
316                binding_callable,
317                #[cfg(slint_debug_property)]
318                self.debug_name.borrow().as_str(),
319            )
320        };
321        self.handle.mark_dirty(
322            #[cfg(slint_debug_property)]
323            self.debug_name.borrow().as_str(),
324        );
325    }
326}
327
328#[cfg(test)]
329mod animation_tests {
330    use super::*;
331
332    #[derive(Default)]
333    struct Component {
334        width: Property<i32>,
335        width_times_two: Property<i32>,
336        feed_property: Property<i32>, // used by binding to feed values into width
337    }
338
339    impl Component {
340        fn new_test_component() -> Rc<Self> {
341            let compo = Rc::new(Component::default());
342            let w = Rc::downgrade(&compo);
343            compo.width_times_two.set_binding(move || {
344                let compo = w.upgrade().unwrap();
345                get_prop_value(&compo.width) * 2
346            });
347
348            compo
349        }
350    }
351
352    const DURATION: std::time::Duration = std::time::Duration::from_millis(10000);
353    const DELAY: std::time::Duration = std::time::Duration::from_millis(800);
354
355    // Helper just for testing
356    fn get_prop_value<T: Clone>(prop: &Property<T>) -> T {
357        unsafe { Pin::new_unchecked(prop).get() }
358    }
359
360    #[test]
361    fn properties_test_animation_negative_delay_triggered_by_set() {
362        let compo = Component::new_test_component();
363
364        let animation_details = PropertyAnimation {
365            delay: -25,
366            duration: DURATION.as_millis() as _,
367            iteration_count: 1.,
368            ..PropertyAnimation::default()
369        };
370
371        compo.width.set(100);
372        assert_eq!(get_prop_value(&compo.width), 100);
373        assert_eq!(get_prop_value(&compo.width_times_two), 200);
374
375        let start_time = crate::animations::current_tick();
376
377        compo.width.set_animated_value(200, animation_details);
378        assert_eq!(get_prop_value(&compo.width), 100);
379        assert_eq!(get_prop_value(&compo.width_times_two), 200);
380
381        crate::animations::CURRENT_ANIMATION_DRIVER
382            .with(|driver| driver.update_animations(start_time + DURATION / 2));
383        assert_eq!(get_prop_value(&compo.width), 150);
384        assert_eq!(get_prop_value(&compo.width_times_two), 300);
385
386        crate::animations::CURRENT_ANIMATION_DRIVER
387            .with(|driver| driver.update_animations(start_time + DURATION));
388        assert_eq!(get_prop_value(&compo.width), 200);
389        assert_eq!(get_prop_value(&compo.width_times_two), 400);
390
391        // Overshoot: Always to_value.
392        crate::animations::CURRENT_ANIMATION_DRIVER
393            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
394        assert_eq!(get_prop_value(&compo.width), 200);
395        assert_eq!(get_prop_value(&compo.width_times_two), 400);
396
397        // the binding should be removed
398        compo.width.handle.access(|binding| assert!(binding.is_none()));
399    }
400
401    #[test]
402    fn properties_test_animation_triggered_by_set() {
403        let compo = Component::new_test_component();
404
405        let animation_details = PropertyAnimation {
406            duration: DURATION.as_millis() as _,
407            iteration_count: 1.,
408            ..PropertyAnimation::default()
409        };
410
411        compo.width.set(100);
412        assert_eq!(get_prop_value(&compo.width), 100);
413        assert_eq!(get_prop_value(&compo.width_times_two), 200);
414
415        let start_time = crate::animations::current_tick();
416
417        compo.width.set_animated_value(200, animation_details);
418        assert_eq!(get_prop_value(&compo.width), 100);
419        assert_eq!(get_prop_value(&compo.width_times_two), 200);
420
421        crate::animations::CURRENT_ANIMATION_DRIVER
422            .with(|driver| driver.update_animations(start_time + DURATION / 2));
423        assert_eq!(get_prop_value(&compo.width), 150);
424        assert_eq!(get_prop_value(&compo.width_times_two), 300);
425
426        crate::animations::CURRENT_ANIMATION_DRIVER
427            .with(|driver| driver.update_animations(start_time + DURATION));
428        assert_eq!(get_prop_value(&compo.width), 200);
429        assert_eq!(get_prop_value(&compo.width_times_two), 400);
430
431        // Overshoot: Always to_value.
432        crate::animations::CURRENT_ANIMATION_DRIVER
433            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
434        assert_eq!(get_prop_value(&compo.width), 200);
435        assert_eq!(get_prop_value(&compo.width_times_two), 400);
436
437        // the binding should be removed
438        compo.width.handle.access(|binding| assert!(binding.is_none()));
439    }
440
441    #[test]
442    fn properties_test_delayed_animation_triggered_by_set() {
443        let compo = Component::new_test_component();
444
445        let animation_details = PropertyAnimation {
446            delay: DELAY.as_millis() as _,
447            iteration_count: 1.,
448            duration: DURATION.as_millis() as _,
449            ..PropertyAnimation::default()
450        };
451
452        compo.width.set(100);
453        assert_eq!(get_prop_value(&compo.width), 100);
454        assert_eq!(get_prop_value(&compo.width_times_two), 200);
455
456        let start_time = crate::animations::current_tick();
457
458        compo.width.set_animated_value(200, animation_details);
459        assert_eq!(get_prop_value(&compo.width), 100);
460        assert_eq!(get_prop_value(&compo.width_times_two), 200);
461
462        // In delay:
463        crate::animations::CURRENT_ANIMATION_DRIVER
464            .with(|driver| driver.update_animations(start_time + DELAY / 2));
465        assert_eq!(get_prop_value(&compo.width), 100);
466        assert_eq!(get_prop_value(&compo.width_times_two), 200);
467
468        // In animation:
469        crate::animations::CURRENT_ANIMATION_DRIVER
470            .with(|driver| driver.update_animations(start_time + DELAY));
471        assert_eq!(get_prop_value(&compo.width), 100);
472        assert_eq!(get_prop_value(&compo.width_times_two), 200);
473
474        crate::animations::CURRENT_ANIMATION_DRIVER
475            .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
476        assert_eq!(get_prop_value(&compo.width), 150);
477        assert_eq!(get_prop_value(&compo.width_times_two), 300);
478
479        crate::animations::CURRENT_ANIMATION_DRIVER
480            .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
481        assert_eq!(get_prop_value(&compo.width), 200);
482        assert_eq!(get_prop_value(&compo.width_times_two), 400);
483
484        // Overshoot: Always to_value.
485        crate::animations::CURRENT_ANIMATION_DRIVER
486            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
487        assert_eq!(get_prop_value(&compo.width), 200);
488        assert_eq!(get_prop_value(&compo.width_times_two), 400);
489
490        // the binding should be removed
491        compo.width.handle.access(|binding| assert!(binding.is_none()));
492    }
493
494    #[test]
495    fn properties_test_delayed_animation_fractual_iteration_triggered_by_set() {
496        let compo = Component::new_test_component();
497
498        let animation_details = PropertyAnimation {
499            delay: DELAY.as_millis() as _,
500            iteration_count: 1.5,
501            duration: DURATION.as_millis() as _,
502            ..PropertyAnimation::default()
503        };
504
505        compo.width.set(100);
506        assert_eq!(get_prop_value(&compo.width), 100);
507        assert_eq!(get_prop_value(&compo.width_times_two), 200);
508
509        let start_time = crate::animations::current_tick();
510
511        compo.width.set_animated_value(200, animation_details);
512        assert_eq!(get_prop_value(&compo.width), 100);
513        assert_eq!(get_prop_value(&compo.width_times_two), 200);
514
515        // In delay:
516        crate::animations::CURRENT_ANIMATION_DRIVER
517            .with(|driver| driver.update_animations(start_time + DELAY / 2));
518        assert_eq!(get_prop_value(&compo.width), 100);
519        assert_eq!(get_prop_value(&compo.width_times_two), 200);
520
521        // In animation:
522        crate::animations::CURRENT_ANIMATION_DRIVER
523            .with(|driver| driver.update_animations(start_time + DELAY));
524        assert_eq!(get_prop_value(&compo.width), 100);
525        assert_eq!(get_prop_value(&compo.width_times_two), 200);
526
527        crate::animations::CURRENT_ANIMATION_DRIVER
528            .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
529        assert_eq!(get_prop_value(&compo.width), 150);
530        assert_eq!(get_prop_value(&compo.width_times_two), 300);
531
532        crate::animations::CURRENT_ANIMATION_DRIVER
533            .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
534        assert_eq!(get_prop_value(&compo.width), 100);
535        assert_eq!(get_prop_value(&compo.width_times_two), 200);
536
537        // (fractual) end of animation
538        crate::animations::CURRENT_ANIMATION_DRIVER
539            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 4));
540        assert_eq!(get_prop_value(&compo.width), 125);
541        assert_eq!(get_prop_value(&compo.width_times_two), 250);
542
543        // End of animation:
544        crate::animations::CURRENT_ANIMATION_DRIVER
545            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
546        assert_eq!(get_prop_value(&compo.width), 200);
547        assert_eq!(get_prop_value(&compo.width_times_two), 400);
548
549        // the binding should be removed
550        compo.width.handle.access(|binding| assert!(binding.is_none()));
551    }
552    #[test]
553    fn properties_test_delayed_animation_null_duration_triggered_by_set() {
554        let compo = Component::new_test_component();
555
556        let animation_details = PropertyAnimation {
557            delay: DELAY.as_millis() as _,
558            iteration_count: 1.0,
559            duration: 0,
560            ..PropertyAnimation::default()
561        };
562
563        compo.width.set(100);
564        assert_eq!(get_prop_value(&compo.width), 100);
565        assert_eq!(get_prop_value(&compo.width_times_two), 200);
566
567        let start_time = crate::animations::current_tick();
568
569        compo.width.set_animated_value(200, animation_details);
570        assert_eq!(get_prop_value(&compo.width), 100);
571        assert_eq!(get_prop_value(&compo.width_times_two), 200);
572
573        // In delay:
574        crate::animations::CURRENT_ANIMATION_DRIVER
575            .with(|driver| driver.update_animations(start_time + DELAY / 2));
576        assert_eq!(get_prop_value(&compo.width), 100);
577        assert_eq!(get_prop_value(&compo.width_times_two), 200);
578
579        // No animation:
580        crate::animations::CURRENT_ANIMATION_DRIVER
581            .with(|driver| driver.update_animations(start_time + DELAY));
582        assert_eq!(get_prop_value(&compo.width), 200);
583        assert_eq!(get_prop_value(&compo.width_times_two), 400);
584
585        // Overshoot: Always to_value.
586        crate::animations::CURRENT_ANIMATION_DRIVER
587            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
588        assert_eq!(get_prop_value(&compo.width), 200);
589        assert_eq!(get_prop_value(&compo.width_times_two), 400);
590
591        // the binding should be removed
592        compo.width.handle.access(|binding| assert!(binding.is_none()));
593    }
594
595    #[test]
596    fn properties_test_delayed_animation_negative_duration_triggered_by_set() {
597        let compo = Component::new_test_component();
598
599        let animation_details = PropertyAnimation {
600            delay: DELAY.as_millis() as _,
601            iteration_count: 1.0,
602            duration: -25,
603            ..PropertyAnimation::default()
604        };
605
606        compo.width.set(100);
607        assert_eq!(get_prop_value(&compo.width), 100);
608        assert_eq!(get_prop_value(&compo.width_times_two), 200);
609
610        let start_time = crate::animations::current_tick();
611
612        compo.width.set_animated_value(200, animation_details);
613        assert_eq!(get_prop_value(&compo.width), 100);
614        assert_eq!(get_prop_value(&compo.width_times_two), 200);
615
616        // In delay:
617        crate::animations::CURRENT_ANIMATION_DRIVER
618            .with(|driver| driver.update_animations(start_time + DELAY / 2));
619        assert_eq!(get_prop_value(&compo.width), 100);
620        assert_eq!(get_prop_value(&compo.width_times_two), 200);
621
622        // No animation:
623        crate::animations::CURRENT_ANIMATION_DRIVER
624            .with(|driver| driver.update_animations(start_time + DELAY));
625        assert_eq!(get_prop_value(&compo.width), 200);
626        assert_eq!(get_prop_value(&compo.width_times_two), 400);
627
628        // Overshoot: Always to_value.
629        crate::animations::CURRENT_ANIMATION_DRIVER
630            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
631        assert_eq!(get_prop_value(&compo.width), 200);
632        assert_eq!(get_prop_value(&compo.width_times_two), 400);
633
634        // the binding should be removed
635        compo.width.handle.access(|binding| assert!(binding.is_none()));
636    }
637
638    #[test]
639    fn properties_test_delayed_animation_no_iteration_triggered_by_set() {
640        let compo = Component::new_test_component();
641
642        let animation_details = PropertyAnimation {
643            delay: DELAY.as_millis() as _,
644            iteration_count: 0.0,
645            duration: DURATION.as_millis() as _,
646            ..PropertyAnimation::default()
647        };
648
649        compo.width.set(100);
650        assert_eq!(get_prop_value(&compo.width), 100);
651        assert_eq!(get_prop_value(&compo.width_times_two), 200);
652
653        let start_time = crate::animations::current_tick();
654
655        compo.width.set_animated_value(200, animation_details);
656        assert_eq!(get_prop_value(&compo.width), 100);
657        assert_eq!(get_prop_value(&compo.width_times_two), 200);
658
659        // In delay:
660        crate::animations::CURRENT_ANIMATION_DRIVER
661            .with(|driver| driver.update_animations(start_time + DELAY / 2));
662        assert_eq!(get_prop_value(&compo.width), 100);
663        assert_eq!(get_prop_value(&compo.width_times_two), 200);
664
665        // No animation:
666        crate::animations::CURRENT_ANIMATION_DRIVER
667            .with(|driver| driver.update_animations(start_time + DELAY));
668        assert_eq!(get_prop_value(&compo.width), 200);
669        assert_eq!(get_prop_value(&compo.width_times_two), 400);
670
671        // Overshoot: Always to_value.
672        crate::animations::CURRENT_ANIMATION_DRIVER
673            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
674        assert_eq!(get_prop_value(&compo.width), 200);
675        assert_eq!(get_prop_value(&compo.width_times_two), 400);
676
677        // the binding should be removed
678        compo.width.handle.access(|binding| assert!(binding.is_none()));
679    }
680
681    #[test]
682    fn properties_test_delayed_animation_negative_iteration_triggered_by_set() {
683        let compo = Component::new_test_component();
684
685        let animation_details = PropertyAnimation {
686            delay: DELAY.as_millis() as _,
687            iteration_count: -42., // loop forever!
688            duration: DURATION.as_millis() as _,
689            ..PropertyAnimation::default()
690        };
691
692        compo.width.set(100);
693        assert_eq!(get_prop_value(&compo.width), 100);
694        assert_eq!(get_prop_value(&compo.width_times_two), 200);
695
696        let start_time = crate::animations::current_tick();
697
698        compo.width.set_animated_value(200, animation_details);
699        assert_eq!(get_prop_value(&compo.width), 100);
700        assert_eq!(get_prop_value(&compo.width_times_two), 200);
701
702        // In delay:
703        crate::animations::CURRENT_ANIMATION_DRIVER
704            .with(|driver| driver.update_animations(start_time + DELAY / 2));
705        assert_eq!(get_prop_value(&compo.width), 100);
706        assert_eq!(get_prop_value(&compo.width_times_two), 200);
707
708        // In animation:
709        crate::animations::CURRENT_ANIMATION_DRIVER
710            .with(|driver| driver.update_animations(start_time + DELAY));
711        assert_eq!(get_prop_value(&compo.width), 100);
712        assert_eq!(get_prop_value(&compo.width_times_two), 200);
713
714        crate::animations::CURRENT_ANIMATION_DRIVER
715            .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
716        assert_eq!(get_prop_value(&compo.width), 150);
717        assert_eq!(get_prop_value(&compo.width_times_two), 300);
718
719        crate::animations::CURRENT_ANIMATION_DRIVER
720            .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
721        assert_eq!(get_prop_value(&compo.width), 100);
722        assert_eq!(get_prop_value(&compo.width_times_two), 200);
723
724        // In animation (again):
725        crate::animations::CURRENT_ANIMATION_DRIVER
726            .with(|driver| driver.update_animations(start_time + DELAY + 500 * DURATION));
727        assert_eq!(get_prop_value(&compo.width), 100);
728        assert_eq!(get_prop_value(&compo.width_times_two), 200);
729
730        crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
731            driver.update_animations(start_time + DELAY + 50000 * DURATION + DURATION / 2)
732        });
733        assert_eq!(get_prop_value(&compo.width), 150);
734        assert_eq!(get_prop_value(&compo.width_times_two), 300);
735
736        // the binding should not be removed as it is still animating!
737        compo.width.handle.access(|binding| assert!(binding.is_some()));
738    }
739
740    #[test]
741    fn properties_test_animation_direction_triggered_by_set() {
742        let compo = Component::new_test_component();
743
744        let animation_details = PropertyAnimation {
745            delay: -25,
746            duration: DURATION.as_millis() as _,
747            direction: AnimationDirection::AlternateReverse,
748            iteration_count: 1.,
749            ..PropertyAnimation::default()
750        };
751
752        compo.width.set(100);
753        assert_eq!(get_prop_value(&compo.width), 100);
754        assert_eq!(get_prop_value(&compo.width_times_two), 200);
755
756        let start_time = crate::animations::current_tick();
757
758        compo.width.set_animated_value(200, animation_details);
759        assert_eq!(get_prop_value(&compo.width), 200);
760        assert_eq!(get_prop_value(&compo.width_times_two), 400);
761
762        crate::animations::CURRENT_ANIMATION_DRIVER
763            .with(|driver| driver.update_animations(start_time + DURATION / 2));
764        assert_eq!(get_prop_value(&compo.width), 150);
765        assert_eq!(get_prop_value(&compo.width_times_two), 300);
766
767        crate::animations::CURRENT_ANIMATION_DRIVER
768            .with(|driver| driver.update_animations(start_time + DURATION));
769        assert_eq!(get_prop_value(&compo.width), 100);
770        assert_eq!(get_prop_value(&compo.width_times_two), 200);
771
772        // Overshoot: Always from_value.
773        crate::animations::CURRENT_ANIMATION_DRIVER
774            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
775        assert_eq!(get_prop_value(&compo.width), 100);
776        assert_eq!(get_prop_value(&compo.width_times_two), 200);
777
778        // the binding should be removed
779        compo.width.handle.access(|binding| assert!(binding.is_none()));
780    }
781
782    #[test]
783    fn properties_test_animation_triggered_by_binding() {
784        let compo = Component::new_test_component();
785
786        let start_time = crate::animations::current_tick();
787
788        let animation_details = PropertyAnimation {
789            duration: DURATION.as_millis() as _,
790            iteration_count: 1.,
791            ..PropertyAnimation::default()
792        };
793
794        let w = Rc::downgrade(&compo);
795        compo.width.set_animated_binding(
796            move || {
797                let compo = w.upgrade().unwrap();
798                get_prop_value(&compo.feed_property)
799            },
800            move || (animation_details.clone(), None),
801        );
802
803        compo.feed_property.set(100);
804        assert_eq!(get_prop_value(&compo.width), 100);
805        assert_eq!(get_prop_value(&compo.width_times_two), 200);
806
807        compo.feed_property.set(200);
808        assert_eq!(get_prop_value(&compo.width), 100);
809        assert_eq!(get_prop_value(&compo.width_times_two), 200);
810
811        crate::animations::CURRENT_ANIMATION_DRIVER
812            .with(|driver| driver.update_animations(start_time + DURATION / 2));
813        assert_eq!(get_prop_value(&compo.width), 150);
814        assert_eq!(get_prop_value(&compo.width_times_two), 300);
815
816        crate::animations::CURRENT_ANIMATION_DRIVER
817            .with(|driver| driver.update_animations(start_time + DURATION));
818        assert_eq!(get_prop_value(&compo.width), 200);
819        assert_eq!(get_prop_value(&compo.width_times_two), 400);
820    }
821
822    #[test]
823    fn properties_test_delayed_animation_triggered_by_binding() {
824        let compo = Component::new_test_component();
825
826        let start_time = crate::animations::current_tick();
827
828        let animation_details = PropertyAnimation {
829            delay: DELAY.as_millis() as _,
830            duration: DURATION.as_millis() as _,
831            iteration_count: 1.0,
832            ..PropertyAnimation::default()
833        };
834
835        let w = Rc::downgrade(&compo);
836        compo.width.set_animated_binding(
837            move || {
838                let compo = w.upgrade().unwrap();
839                get_prop_value(&compo.feed_property)
840            },
841            move || (animation_details.clone(), None),
842        );
843
844        compo.feed_property.set(100);
845        assert_eq!(get_prop_value(&compo.width), 100);
846        assert_eq!(get_prop_value(&compo.width_times_two), 200);
847
848        compo.feed_property.set(200);
849        assert_eq!(get_prop_value(&compo.width), 100);
850        assert_eq!(get_prop_value(&compo.width_times_two), 200);
851
852        // In delay:
853        crate::animations::CURRENT_ANIMATION_DRIVER
854            .with(|driver| driver.update_animations(start_time + DELAY / 2));
855        assert_eq!(get_prop_value(&compo.width), 100);
856        assert_eq!(get_prop_value(&compo.width_times_two), 200);
857
858        // In animation:
859        crate::animations::CURRENT_ANIMATION_DRIVER
860            .with(|driver| driver.update_animations(start_time + DELAY));
861        assert_eq!(get_prop_value(&compo.width), 100);
862        assert_eq!(get_prop_value(&compo.width_times_two), 200);
863
864        crate::animations::CURRENT_ANIMATION_DRIVER
865            .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
866        assert_eq!(get_prop_value(&compo.width), 150);
867        assert_eq!(get_prop_value(&compo.width_times_two), 300);
868
869        crate::animations::CURRENT_ANIMATION_DRIVER
870            .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
871        assert_eq!(get_prop_value(&compo.width), 200);
872        assert_eq!(get_prop_value(&compo.width_times_two), 400);
873
874        // Overshoot: Always to_value.
875        crate::animations::CURRENT_ANIMATION_DRIVER
876            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
877        assert_eq!(get_prop_value(&compo.width), 200);
878        assert_eq!(get_prop_value(&compo.width_times_two), 400);
879    }
880
881    #[test]
882    fn test_loop() {
883        let compo = Component::new_test_component();
884
885        let animation_details = PropertyAnimation {
886            duration: DURATION.as_millis() as _,
887            iteration_count: 2.,
888            ..PropertyAnimation::default()
889        };
890
891        compo.width.set(100);
892
893        let start_time = crate::animations::current_tick();
894
895        compo.width.set_animated_value(200, animation_details);
896        assert_eq!(get_prop_value(&compo.width), 100);
897
898        crate::animations::CURRENT_ANIMATION_DRIVER
899            .with(|driver| driver.update_animations(start_time + DURATION / 2));
900        assert_eq!(get_prop_value(&compo.width), 150);
901
902        crate::animations::CURRENT_ANIMATION_DRIVER
903            .with(|driver| driver.update_animations(start_time + DURATION));
904        assert_eq!(get_prop_value(&compo.width), 100);
905
906        crate::animations::CURRENT_ANIMATION_DRIVER
907            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
908        assert_eq!(get_prop_value(&compo.width), 150);
909
910        crate::animations::CURRENT_ANIMATION_DRIVER
911            .with(|driver| driver.update_animations(start_time + DURATION * 2));
912        assert_eq!(get_prop_value(&compo.width), 200);
913
914        // the binding should be removed
915        compo.width.handle.access(|binding| assert!(binding.is_none()));
916    }
917
918    #[test]
919    fn test_loop_via_binding() {
920        // Loop twice, restart the animation and still loop twice.
921
922        let compo = Component::new_test_component();
923
924        let start_time = crate::animations::current_tick();
925
926        let animation_details = PropertyAnimation {
927            duration: DURATION.as_millis() as _,
928            iteration_count: 2.,
929            ..PropertyAnimation::default()
930        };
931
932        let w = Rc::downgrade(&compo);
933        compo.width.set_animated_binding(
934            move || {
935                let compo = w.upgrade().unwrap();
936                get_prop_value(&compo.feed_property)
937            },
938            move || (animation_details.clone(), None),
939        );
940
941        compo.feed_property.set(100);
942        assert_eq!(get_prop_value(&compo.width), 100);
943
944        compo.feed_property.set(200);
945        assert_eq!(get_prop_value(&compo.width), 100);
946
947        crate::animations::CURRENT_ANIMATION_DRIVER
948            .with(|driver| driver.update_animations(start_time + DURATION / 2));
949
950        assert_eq!(get_prop_value(&compo.width), 150);
951
952        crate::animations::CURRENT_ANIMATION_DRIVER
953            .with(|driver| driver.update_animations(start_time + DURATION));
954
955        assert_eq!(get_prop_value(&compo.width), 100);
956
957        crate::animations::CURRENT_ANIMATION_DRIVER
958            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
959
960        assert_eq!(get_prop_value(&compo.width), 150);
961
962        crate::animations::CURRENT_ANIMATION_DRIVER
963            .with(|driver| driver.update_animations(start_time + 2 * DURATION));
964
965        assert_eq!(get_prop_value(&compo.width), 200);
966
967        // Overshoot a bit:
968        crate::animations::CURRENT_ANIMATION_DRIVER
969            .with(|driver| driver.update_animations(start_time + 2 * DURATION + DURATION / 2));
970
971        assert_eq!(get_prop_value(&compo.width), 200);
972
973        // Restart the animation by setting a new value.
974
975        let start_time = crate::animations::current_tick();
976
977        compo.feed_property.set(300);
978        assert_eq!(get_prop_value(&compo.width), 200);
979
980        crate::animations::CURRENT_ANIMATION_DRIVER
981            .with(|driver| driver.update_animations(start_time + DURATION / 2));
982
983        assert_eq!(get_prop_value(&compo.width), 250);
984
985        crate::animations::CURRENT_ANIMATION_DRIVER
986            .with(|driver| driver.update_animations(start_time + DURATION));
987
988        assert_eq!(get_prop_value(&compo.width), 200);
989
990        crate::animations::CURRENT_ANIMATION_DRIVER
991            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
992
993        assert_eq!(get_prop_value(&compo.width), 250);
994
995        crate::animations::CURRENT_ANIMATION_DRIVER
996            .with(|driver| driver.update_animations(start_time + 2 * DURATION));
997
998        assert_eq!(get_prop_value(&compo.width), 300);
999
1000        crate::animations::CURRENT_ANIMATION_DRIVER
1001            .with(|driver| driver.update_animations(start_time + 2 * DURATION + DURATION / 2));
1002
1003        assert_eq!(get_prop_value(&compo.width), 300);
1004    }
1005}