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