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