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