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, value: T, animation_data: PropertyAnimation) {
314        // FIXME if the current value is a dirty binding, we must run it, but we do not have the context
315        let d = RefCell::new(properties_animations::PropertyValueAnimationData::new(
316            self.get_internal(),
317            value,
318            animation_data,
319        ));
320        // Safety: the BindingCallable will cast its argument to T
321        unsafe {
322            self.handle.set_binding(
323                move |val: &mut T| {
324                    let (value, finished) = d.borrow_mut().compute_interpolated_value();
325                    *val = value;
326                    if finished {
327                        BindingResult::RemoveBinding
328                    } else {
329                        crate::animations::CURRENT_ANIMATION_DRIVER
330                            .with(|driver| driver.set_has_active_animations());
331                        BindingResult::KeepBinding
332                    }
333                },
334                #[cfg(slint_debug_property)]
335                self.debug_name.borrow().as_str(),
336            );
337        }
338        self.handle.mark_dirty(
339            #[cfg(slint_debug_property)]
340            self.debug_name.borrow().as_str(),
341        );
342    }
343
344    /// Set a binding to this property, providing a callback for the animation and an optional
345    /// start_time (relevant for state transitions).
346    pub fn set_animated_binding(
347        &self,
348        binding: impl Binding<T> + 'static,
349        compute_animation_details: impl Fn() -> (PropertyAnimation, Option<crate::animations::Instant>)
350        + 'static,
351    ) {
352        let binding_callable = properties_animations::AnimatedBindingCallable::<T, _> {
353            original_binding: PropertyHandle {
354                handle: Cell::new(
355                    (alloc_binding_holder(move |val: &mut T| {
356                        *val = binding.evaluate(val);
357                        BindingResult::KeepBinding
358                    }) as *mut ())
359                        .map_addr(|a| a | 0b10),
360                ),
361            },
362            state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating),
363            animation_data: RefCell::new(properties_animations::PropertyValueAnimationData::new(
364                T::default(),
365                T::default(),
366                PropertyAnimation::default(),
367            )),
368            compute_animation_details,
369        };
370
371        // Safety: the `AnimatedBindingCallable`'s type match the property type
372        unsafe {
373            self.handle.set_binding(
374                binding_callable,
375                #[cfg(slint_debug_property)]
376                self.debug_name.borrow().as_str(),
377            )
378        };
379        self.handle.mark_dirty(
380            #[cfg(slint_debug_property)]
381            self.debug_name.borrow().as_str(),
382        );
383    }
384}
385
386unsafe impl<Unit, S: Simulation> BindingCallable<Length<crate::Coord, Unit>>
387    for RefCell<PropertyPhysicsAnimationData<S>>
388{
389    fn evaluate(self: Pin<&Self>, value: &mut Length<crate::Coord, Unit>) -> BindingResult {
390        let finished = self.borrow_mut().update_value(&mut value.0);
391        if finished {
392            BindingResult::RemoveBinding
393        } else {
394            crate::animations::CURRENT_ANIMATION_DRIVER
395                .with(|driver| driver.set_has_active_animations());
396            BindingResult::KeepBinding
397        }
398    }
399
400    // This binding should not be removed if the value is updated externally.
401    fn intercept_set(self: Pin<&Self>, _value: &Length<crate::Coord, Unit>) -> bool {
402        true
403    }
404}
405
406impl<Unit> Property<Length<crate::Coord, Unit>> {
407    /// Change the value by using a physics animation
408    pub fn set_physic_animation_value<
409        S: physics_simulation::Simulation + 'static,
410        AD: physics_simulation::Parameter<Output = S>,
411    >(
412        &self,
413        limit_value: Pin<Box<Property<f32>>>,
414        simulation_data: AD,
415    ) {
416        // Safety: the BindingCallable will cast its argument to T
417        unsafe {
418            self.handle.set_binding::<Length<crate::Coord, Unit>, core::cell::RefCell<PropertyPhysicsAnimationData<S>>>(RefCell::new(PropertyPhysicsAnimationData::new(
419                    simulation_data.simulation(self.get_internal().0 as f32, limit_value),
420                )),
421                #[cfg(slint_debug_property)]
422                self.debug_name.borrow().as_str()
423            );
424        }
425        self.handle.mark_dirty(
426            #[cfg(slint_debug_property)]
427            self.debug_name.borrow().as_str(),
428        );
429    }
430}
431
432#[cfg(test)]
433mod animation_tests {
434    use super::*;
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() -> Rc<Self> {
446            let compo = Rc::new(Component::default());
447            let w = Rc::downgrade(&compo);
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    #[test]
466    fn properties_test_animation_negative_delay_triggered_by_set() {
467        let compo = Component::new_test_component();
468
469        let animation_details = PropertyAnimation {
470            delay: -25,
471            duration: DURATION.as_millis() as _,
472            iteration_count: 1.,
473            ..PropertyAnimation::default()
474        };
475
476        compo.width.set(100);
477        assert_eq!(get_prop_value(&compo.width), 100);
478        assert_eq!(get_prop_value(&compo.width_times_two), 200);
479
480        let start_time = crate::animations::current_tick();
481
482        compo.width.set_animated_value(200, animation_details);
483        assert_eq!(get_prop_value(&compo.width), 100);
484        assert_eq!(get_prop_value(&compo.width_times_two), 200);
485
486        crate::animations::CURRENT_ANIMATION_DRIVER
487            .with(|driver| driver.update_animations(start_time + DURATION / 2));
488        assert_eq!(get_prop_value(&compo.width), 150);
489        assert_eq!(get_prop_value(&compo.width_times_two), 300);
490
491        crate::animations::CURRENT_ANIMATION_DRIVER
492            .with(|driver| driver.update_animations(start_time + DURATION));
493        assert_eq!(get_prop_value(&compo.width), 200);
494        assert_eq!(get_prop_value(&compo.width_times_two), 400);
495
496        // Overshoot: Always to_value.
497        crate::animations::CURRENT_ANIMATION_DRIVER
498            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
499        assert_eq!(get_prop_value(&compo.width), 200);
500        assert_eq!(get_prop_value(&compo.width_times_two), 400);
501
502        // the binding should be removed
503        compo.width.handle.access(|binding| assert!(binding.is_none()));
504    }
505
506    #[test]
507    fn properties_test_animation_triggered_by_set() {
508        let compo = Component::new_test_component();
509
510        let animation_details = PropertyAnimation {
511            duration: DURATION.as_millis() as _,
512            iteration_count: 1.,
513            ..PropertyAnimation::default()
514        };
515
516        compo.width.set(100);
517        assert_eq!(get_prop_value(&compo.width), 100);
518        assert_eq!(get_prop_value(&compo.width_times_two), 200);
519
520        let start_time = crate::animations::current_tick();
521
522        compo.width.set_animated_value(200, animation_details);
523        assert_eq!(get_prop_value(&compo.width), 100);
524        assert_eq!(get_prop_value(&compo.width_times_two), 200);
525
526        crate::animations::CURRENT_ANIMATION_DRIVER
527            .with(|driver| driver.update_animations(start_time + DURATION / 2));
528        assert_eq!(get_prop_value(&compo.width), 150);
529        assert_eq!(get_prop_value(&compo.width_times_two), 300);
530
531        crate::animations::CURRENT_ANIMATION_DRIVER
532            .with(|driver| driver.update_animations(start_time + DURATION));
533        assert_eq!(get_prop_value(&compo.width), 200);
534        assert_eq!(get_prop_value(&compo.width_times_two), 400);
535
536        // Overshoot: Always to_value.
537        crate::animations::CURRENT_ANIMATION_DRIVER
538            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
539        assert_eq!(get_prop_value(&compo.width), 200);
540        assert_eq!(get_prop_value(&compo.width_times_two), 400);
541
542        // the binding should be removed
543        compo.width.handle.access(|binding| assert!(binding.is_none()));
544    }
545
546    #[test]
547    fn properties_test_delayed_animation_triggered_by_set() {
548        let compo = Component::new_test_component();
549
550        let animation_details = PropertyAnimation {
551            delay: DELAY.as_millis() as _,
552            iteration_count: 1.,
553            duration: DURATION.as_millis() as _,
554            ..PropertyAnimation::default()
555        };
556
557        compo.width.set(100);
558        assert_eq!(get_prop_value(&compo.width), 100);
559        assert_eq!(get_prop_value(&compo.width_times_two), 200);
560
561        let start_time = crate::animations::current_tick();
562
563        compo.width.set_animated_value(200, animation_details);
564        assert_eq!(get_prop_value(&compo.width), 100);
565        assert_eq!(get_prop_value(&compo.width_times_two), 200);
566
567        // In delay:
568        crate::animations::CURRENT_ANIMATION_DRIVER
569            .with(|driver| driver.update_animations(start_time + DELAY / 2));
570        assert_eq!(get_prop_value(&compo.width), 100);
571        assert_eq!(get_prop_value(&compo.width_times_two), 200);
572
573        // In animation:
574        crate::animations::CURRENT_ANIMATION_DRIVER
575            .with(|driver| driver.update_animations(start_time + DELAY));
576        assert_eq!(get_prop_value(&compo.width), 100);
577        assert_eq!(get_prop_value(&compo.width_times_two), 200);
578
579        crate::animations::CURRENT_ANIMATION_DRIVER
580            .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
581        assert_eq!(get_prop_value(&compo.width), 150);
582        assert_eq!(get_prop_value(&compo.width_times_two), 300);
583
584        crate::animations::CURRENT_ANIMATION_DRIVER
585            .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
586        assert_eq!(get_prop_value(&compo.width), 200);
587        assert_eq!(get_prop_value(&compo.width_times_two), 400);
588
589        // Overshoot: Always to_value.
590        crate::animations::CURRENT_ANIMATION_DRIVER
591            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
592        assert_eq!(get_prop_value(&compo.width), 200);
593        assert_eq!(get_prop_value(&compo.width_times_two), 400);
594
595        // the binding should be removed
596        compo.width.handle.access(|binding| assert!(binding.is_none()));
597    }
598
599    #[test]
600    fn properties_test_delayed_animation_fractal_iteration_triggered_by_set() {
601        let compo = Component::new_test_component();
602
603        let animation_details = PropertyAnimation {
604            delay: DELAY.as_millis() as _,
605            iteration_count: 1.5,
606            duration: DURATION.as_millis() as _,
607            ..PropertyAnimation::default()
608        };
609
610        compo.width.set(100);
611        assert_eq!(get_prop_value(&compo.width), 100);
612        assert_eq!(get_prop_value(&compo.width_times_two), 200);
613
614        let start_time = crate::animations::current_tick();
615
616        compo.width.set_animated_value(200, animation_details);
617        assert_eq!(get_prop_value(&compo.width), 100);
618        assert_eq!(get_prop_value(&compo.width_times_two), 200);
619
620        // In delay:
621        crate::animations::CURRENT_ANIMATION_DRIVER
622            .with(|driver| driver.update_animations(start_time + DELAY / 2));
623        assert_eq!(get_prop_value(&compo.width), 100);
624        assert_eq!(get_prop_value(&compo.width_times_two), 200);
625
626        // In animation:
627        crate::animations::CURRENT_ANIMATION_DRIVER
628            .with(|driver| driver.update_animations(start_time + DELAY));
629        assert_eq!(get_prop_value(&compo.width), 100);
630        assert_eq!(get_prop_value(&compo.width_times_two), 200);
631
632        crate::animations::CURRENT_ANIMATION_DRIVER
633            .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
634        assert_eq!(get_prop_value(&compo.width), 150);
635        assert_eq!(get_prop_value(&compo.width_times_two), 300);
636
637        crate::animations::CURRENT_ANIMATION_DRIVER
638            .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
639        assert_eq!(get_prop_value(&compo.width), 100);
640        assert_eq!(get_prop_value(&compo.width_times_two), 200);
641
642        // (fractal) end of animation
643        crate::animations::CURRENT_ANIMATION_DRIVER
644            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 4));
645        assert_eq!(get_prop_value(&compo.width), 125);
646        assert_eq!(get_prop_value(&compo.width_times_two), 250);
647
648        // End of animation:
649        crate::animations::CURRENT_ANIMATION_DRIVER
650            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
651        assert_eq!(get_prop_value(&compo.width), 200);
652        assert_eq!(get_prop_value(&compo.width_times_two), 400);
653
654        // the binding should be removed
655        compo.width.handle.access(|binding| assert!(binding.is_none()));
656    }
657    #[test]
658    fn properties_test_delayed_animation_null_duration_triggered_by_set() {
659        let compo = Component::new_test_component();
660
661        let animation_details = PropertyAnimation {
662            delay: DELAY.as_millis() as _,
663            iteration_count: 1.0,
664            duration: 0,
665            ..PropertyAnimation::default()
666        };
667
668        compo.width.set(100);
669        assert_eq!(get_prop_value(&compo.width), 100);
670        assert_eq!(get_prop_value(&compo.width_times_two), 200);
671
672        let start_time = crate::animations::current_tick();
673
674        compo.width.set_animated_value(200, animation_details);
675        assert_eq!(get_prop_value(&compo.width), 100);
676        assert_eq!(get_prop_value(&compo.width_times_two), 200);
677
678        // In delay:
679        crate::animations::CURRENT_ANIMATION_DRIVER
680            .with(|driver| driver.update_animations(start_time + DELAY / 2));
681        assert_eq!(get_prop_value(&compo.width), 100);
682        assert_eq!(get_prop_value(&compo.width_times_two), 200);
683
684        // No animation:
685        crate::animations::CURRENT_ANIMATION_DRIVER
686            .with(|driver| driver.update_animations(start_time + DELAY));
687        assert_eq!(get_prop_value(&compo.width), 200);
688        assert_eq!(get_prop_value(&compo.width_times_two), 400);
689
690        // Overshoot: Always to_value.
691        crate::animations::CURRENT_ANIMATION_DRIVER
692            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
693        assert_eq!(get_prop_value(&compo.width), 200);
694        assert_eq!(get_prop_value(&compo.width_times_two), 400);
695
696        // the binding should be removed
697        compo.width.handle.access(|binding| assert!(binding.is_none()));
698    }
699
700    #[test]
701    fn properties_test_delayed_animation_negative_duration_triggered_by_set() {
702        let compo = Component::new_test_component();
703
704        let animation_details = PropertyAnimation {
705            delay: DELAY.as_millis() as _,
706            iteration_count: 1.0,
707            duration: -25,
708            ..PropertyAnimation::default()
709        };
710
711        compo.width.set(100);
712        assert_eq!(get_prop_value(&compo.width), 100);
713        assert_eq!(get_prop_value(&compo.width_times_two), 200);
714
715        let start_time = crate::animations::current_tick();
716
717        compo.width.set_animated_value(200, animation_details);
718        assert_eq!(get_prop_value(&compo.width), 100);
719        assert_eq!(get_prop_value(&compo.width_times_two), 200);
720
721        // In delay:
722        crate::animations::CURRENT_ANIMATION_DRIVER
723            .with(|driver| driver.update_animations(start_time + DELAY / 2));
724        assert_eq!(get_prop_value(&compo.width), 100);
725        assert_eq!(get_prop_value(&compo.width_times_two), 200);
726
727        // No animation:
728        crate::animations::CURRENT_ANIMATION_DRIVER
729            .with(|driver| driver.update_animations(start_time + DELAY));
730        assert_eq!(get_prop_value(&compo.width), 200);
731        assert_eq!(get_prop_value(&compo.width_times_two), 400);
732
733        // Overshoot: Always to_value.
734        crate::animations::CURRENT_ANIMATION_DRIVER
735            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
736        assert_eq!(get_prop_value(&compo.width), 200);
737        assert_eq!(get_prop_value(&compo.width_times_two), 400);
738
739        // the binding should be removed
740        compo.width.handle.access(|binding| assert!(binding.is_none()));
741    }
742
743    #[test]
744    fn properties_test_delayed_animation_no_iteration_triggered_by_set() {
745        let compo = Component::new_test_component();
746
747        let animation_details = PropertyAnimation {
748            delay: DELAY.as_millis() as _,
749            iteration_count: 0.0,
750            duration: DURATION.as_millis() as _,
751            ..PropertyAnimation::default()
752        };
753
754        compo.width.set(100);
755        assert_eq!(get_prop_value(&compo.width), 100);
756        assert_eq!(get_prop_value(&compo.width_times_two), 200);
757
758        let start_time = crate::animations::current_tick();
759
760        compo.width.set_animated_value(200, animation_details);
761        assert_eq!(get_prop_value(&compo.width), 100);
762        assert_eq!(get_prop_value(&compo.width_times_two), 200);
763
764        // In delay:
765        crate::animations::CURRENT_ANIMATION_DRIVER
766            .with(|driver| driver.update_animations(start_time + DELAY / 2));
767        assert_eq!(get_prop_value(&compo.width), 100);
768        assert_eq!(get_prop_value(&compo.width_times_two), 200);
769
770        // No animation:
771        crate::animations::CURRENT_ANIMATION_DRIVER
772            .with(|driver| driver.update_animations(start_time + DELAY));
773        assert_eq!(get_prop_value(&compo.width), 200);
774        assert_eq!(get_prop_value(&compo.width_times_two), 400);
775
776        // Overshoot: Always to_value.
777        crate::animations::CURRENT_ANIMATION_DRIVER
778            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
779        assert_eq!(get_prop_value(&compo.width), 200);
780        assert_eq!(get_prop_value(&compo.width_times_two), 400);
781
782        // the binding should be removed
783        compo.width.handle.access(|binding| assert!(binding.is_none()));
784    }
785
786    #[test]
787    fn properties_test_delayed_animation_negative_iteration_triggered_by_set() {
788        let compo = Component::new_test_component();
789
790        let animation_details = PropertyAnimation {
791            delay: DELAY.as_millis() as _,
792            iteration_count: -42., // loop forever!
793            duration: DURATION.as_millis() as _,
794            ..PropertyAnimation::default()
795        };
796
797        compo.width.set(100);
798        assert_eq!(get_prop_value(&compo.width), 100);
799        assert_eq!(get_prop_value(&compo.width_times_two), 200);
800
801        let start_time = crate::animations::current_tick();
802
803        compo.width.set_animated_value(200, animation_details);
804        assert_eq!(get_prop_value(&compo.width), 100);
805        assert_eq!(get_prop_value(&compo.width_times_two), 200);
806
807        // In delay:
808        crate::animations::CURRENT_ANIMATION_DRIVER
809            .with(|driver| driver.update_animations(start_time + DELAY / 2));
810        assert_eq!(get_prop_value(&compo.width), 100);
811        assert_eq!(get_prop_value(&compo.width_times_two), 200);
812
813        // In animation:
814        crate::animations::CURRENT_ANIMATION_DRIVER
815            .with(|driver| driver.update_animations(start_time + DELAY));
816        assert_eq!(get_prop_value(&compo.width), 100);
817        assert_eq!(get_prop_value(&compo.width_times_two), 200);
818
819        crate::animations::CURRENT_ANIMATION_DRIVER
820            .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
821        assert_eq!(get_prop_value(&compo.width), 150);
822        assert_eq!(get_prop_value(&compo.width_times_two), 300);
823
824        crate::animations::CURRENT_ANIMATION_DRIVER
825            .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
826        assert_eq!(get_prop_value(&compo.width), 100);
827        assert_eq!(get_prop_value(&compo.width_times_two), 200);
828
829        // In animation (again):
830        crate::animations::CURRENT_ANIMATION_DRIVER
831            .with(|driver| driver.update_animations(start_time + DELAY + 500 * DURATION));
832        assert_eq!(get_prop_value(&compo.width), 100);
833        assert_eq!(get_prop_value(&compo.width_times_two), 200);
834
835        crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
836            driver.update_animations(start_time + DELAY + 50000 * DURATION + DURATION / 2)
837        });
838        assert_eq!(get_prop_value(&compo.width), 150);
839        assert_eq!(get_prop_value(&compo.width_times_two), 300);
840
841        // the binding should not be removed as it is still animating!
842        compo.width.handle.access(|binding| assert!(binding.is_some()));
843    }
844
845    #[test]
846    fn properties_test_animation_direction_triggered_by_set() {
847        let compo = Component::new_test_component();
848
849        let animation_details = PropertyAnimation {
850            delay: -25,
851            duration: DURATION.as_millis() as _,
852            direction: AnimationDirection::AlternateReverse,
853            iteration_count: 1.,
854            ..PropertyAnimation::default()
855        };
856
857        compo.width.set(100);
858        assert_eq!(get_prop_value(&compo.width), 100);
859        assert_eq!(get_prop_value(&compo.width_times_two), 200);
860
861        let start_time = crate::animations::current_tick();
862
863        compo.width.set_animated_value(200, animation_details);
864        assert_eq!(get_prop_value(&compo.width), 200);
865        assert_eq!(get_prop_value(&compo.width_times_two), 400);
866
867        crate::animations::CURRENT_ANIMATION_DRIVER
868            .with(|driver| driver.update_animations(start_time + DURATION / 2));
869        assert_eq!(get_prop_value(&compo.width), 150);
870        assert_eq!(get_prop_value(&compo.width_times_two), 300);
871
872        crate::animations::CURRENT_ANIMATION_DRIVER
873            .with(|driver| driver.update_animations(start_time + DURATION));
874        assert_eq!(get_prop_value(&compo.width), 100);
875        assert_eq!(get_prop_value(&compo.width_times_two), 200);
876
877        // Overshoot: Always from_value.
878        crate::animations::CURRENT_ANIMATION_DRIVER
879            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
880        assert_eq!(get_prop_value(&compo.width), 100);
881        assert_eq!(get_prop_value(&compo.width_times_two), 200);
882
883        // the binding should be removed
884        compo.width.handle.access(|binding| assert!(binding.is_none()));
885    }
886
887    #[test]
888    fn properties_test_animation_triggered_by_binding() {
889        let compo = Component::new_test_component();
890
891        let start_time = crate::animations::current_tick();
892
893        let animation_details = PropertyAnimation {
894            duration: DURATION.as_millis() as _,
895            iteration_count: 1.,
896            ..PropertyAnimation::default()
897        };
898
899        let w = Rc::downgrade(&compo);
900        compo.width.set_animated_binding(
901            move || {
902                let compo = w.upgrade().unwrap();
903                get_prop_value(&compo.feed_property)
904            },
905            move || (animation_details.clone(), None),
906        );
907
908        compo.feed_property.set(100);
909        assert_eq!(get_prop_value(&compo.width), 100);
910        assert_eq!(get_prop_value(&compo.width_times_two), 200);
911
912        compo.feed_property.set(200);
913        assert_eq!(get_prop_value(&compo.width), 100);
914        assert_eq!(get_prop_value(&compo.width_times_two), 200);
915
916        crate::animations::CURRENT_ANIMATION_DRIVER
917            .with(|driver| driver.update_animations(start_time + DURATION / 2));
918        assert_eq!(get_prop_value(&compo.width), 150);
919        assert_eq!(get_prop_value(&compo.width_times_two), 300);
920
921        crate::animations::CURRENT_ANIMATION_DRIVER
922            .with(|driver| driver.update_animations(start_time + DURATION));
923        assert_eq!(get_prop_value(&compo.width), 200);
924        assert_eq!(get_prop_value(&compo.width_times_two), 400);
925    }
926
927    #[test]
928    fn properties_test_delayed_animation_triggered_by_binding() {
929        let compo = Component::new_test_component();
930
931        let start_time = crate::animations::current_tick();
932
933        let animation_details = PropertyAnimation {
934            delay: DELAY.as_millis() as _,
935            duration: DURATION.as_millis() as _,
936            iteration_count: 1.0,
937            ..PropertyAnimation::default()
938        };
939
940        let w = Rc::downgrade(&compo);
941        compo.width.set_animated_binding(
942            move || {
943                let compo = w.upgrade().unwrap();
944                get_prop_value(&compo.feed_property)
945            },
946            move || (animation_details.clone(), None),
947        );
948
949        compo.feed_property.set(100);
950        assert_eq!(get_prop_value(&compo.width), 100);
951        assert_eq!(get_prop_value(&compo.width_times_two), 200);
952
953        compo.feed_property.set(200);
954        assert_eq!(get_prop_value(&compo.width), 100);
955        assert_eq!(get_prop_value(&compo.width_times_two), 200);
956
957        // In delay:
958        crate::animations::CURRENT_ANIMATION_DRIVER
959            .with(|driver| driver.update_animations(start_time + DELAY / 2));
960        assert_eq!(get_prop_value(&compo.width), 100);
961        assert_eq!(get_prop_value(&compo.width_times_two), 200);
962
963        // In animation:
964        crate::animations::CURRENT_ANIMATION_DRIVER
965            .with(|driver| driver.update_animations(start_time + DELAY));
966        assert_eq!(get_prop_value(&compo.width), 100);
967        assert_eq!(get_prop_value(&compo.width_times_two), 200);
968
969        crate::animations::CURRENT_ANIMATION_DRIVER
970            .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
971        assert_eq!(get_prop_value(&compo.width), 150);
972        assert_eq!(get_prop_value(&compo.width_times_two), 300);
973
974        crate::animations::CURRENT_ANIMATION_DRIVER
975            .with(|driver| driver.update_animations(start_time + DELAY + DURATION));
976        assert_eq!(get_prop_value(&compo.width), 200);
977        assert_eq!(get_prop_value(&compo.width_times_two), 400);
978
979        // Overshoot: Always to_value.
980        crate::animations::CURRENT_ANIMATION_DRIVER
981            .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
982        assert_eq!(get_prop_value(&compo.width), 200);
983        assert_eq!(get_prop_value(&compo.width_times_two), 400);
984    }
985
986    #[test]
987    fn test_loop() {
988        let compo = Component::new_test_component();
989
990        let animation_details = PropertyAnimation {
991            duration: DURATION.as_millis() as _,
992            iteration_count: 2.,
993            ..PropertyAnimation::default()
994        };
995
996        compo.width.set(100);
997
998        let start_time = crate::animations::current_tick();
999
1000        compo.width.set_animated_value(200, animation_details);
1001        assert_eq!(get_prop_value(&compo.width), 100);
1002
1003        crate::animations::CURRENT_ANIMATION_DRIVER
1004            .with(|driver| driver.update_animations(start_time + DURATION / 2));
1005        assert_eq!(get_prop_value(&compo.width), 150);
1006
1007        crate::animations::CURRENT_ANIMATION_DRIVER
1008            .with(|driver| driver.update_animations(start_time + DURATION));
1009        assert_eq!(get_prop_value(&compo.width), 100);
1010
1011        crate::animations::CURRENT_ANIMATION_DRIVER
1012            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
1013        assert_eq!(get_prop_value(&compo.width), 150);
1014
1015        crate::animations::CURRENT_ANIMATION_DRIVER
1016            .with(|driver| driver.update_animations(start_time + DURATION * 2));
1017        assert_eq!(get_prop_value(&compo.width), 200);
1018
1019        // the binding should be removed
1020        compo.width.handle.access(|binding| assert!(binding.is_none()));
1021    }
1022
1023    #[test]
1024    fn test_loop_via_binding() {
1025        // Loop twice, restart the animation and still loop twice.
1026
1027        let compo = Component::new_test_component();
1028
1029        let start_time = crate::animations::current_tick();
1030
1031        let animation_details = PropertyAnimation {
1032            duration: DURATION.as_millis() as _,
1033            iteration_count: 2.,
1034            ..PropertyAnimation::default()
1035        };
1036
1037        let w = Rc::downgrade(&compo);
1038        compo.width.set_animated_binding(
1039            move || {
1040                let compo = w.upgrade().unwrap();
1041                get_prop_value(&compo.feed_property)
1042            },
1043            move || (animation_details.clone(), None),
1044        );
1045
1046        compo.feed_property.set(100);
1047        assert_eq!(get_prop_value(&compo.width), 100);
1048
1049        compo.feed_property.set(200);
1050        assert_eq!(get_prop_value(&compo.width), 100);
1051
1052        crate::animations::CURRENT_ANIMATION_DRIVER
1053            .with(|driver| driver.update_animations(start_time + DURATION / 2));
1054
1055        assert_eq!(get_prop_value(&compo.width), 150);
1056
1057        crate::animations::CURRENT_ANIMATION_DRIVER
1058            .with(|driver| driver.update_animations(start_time + DURATION));
1059
1060        assert_eq!(get_prop_value(&compo.width), 100);
1061
1062        crate::animations::CURRENT_ANIMATION_DRIVER
1063            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
1064
1065        assert_eq!(get_prop_value(&compo.width), 150);
1066
1067        crate::animations::CURRENT_ANIMATION_DRIVER
1068            .with(|driver| driver.update_animations(start_time + 2 * DURATION));
1069
1070        assert_eq!(get_prop_value(&compo.width), 200);
1071
1072        // Overshoot a bit:
1073        crate::animations::CURRENT_ANIMATION_DRIVER
1074            .with(|driver| driver.update_animations(start_time + 2 * DURATION + DURATION / 2));
1075
1076        assert_eq!(get_prop_value(&compo.width), 200);
1077
1078        // Restart the animation by setting a new value.
1079
1080        let start_time = crate::animations::current_tick();
1081
1082        compo.feed_property.set(300);
1083        assert_eq!(get_prop_value(&compo.width), 200);
1084
1085        crate::animations::CURRENT_ANIMATION_DRIVER
1086            .with(|driver| driver.update_animations(start_time + DURATION / 2));
1087
1088        assert_eq!(get_prop_value(&compo.width), 250);
1089
1090        crate::animations::CURRENT_ANIMATION_DRIVER
1091            .with(|driver| driver.update_animations(start_time + DURATION));
1092
1093        assert_eq!(get_prop_value(&compo.width), 200);
1094
1095        crate::animations::CURRENT_ANIMATION_DRIVER
1096            .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
1097
1098        assert_eq!(get_prop_value(&compo.width), 250);
1099
1100        crate::animations::CURRENT_ANIMATION_DRIVER
1101            .with(|driver| driver.update_animations(start_time + 2 * DURATION));
1102
1103        assert_eq!(get_prop_value(&compo.width), 300);
1104
1105        crate::animations::CURRENT_ANIMATION_DRIVER
1106            .with(|driver| driver.update_animations(start_time + 2 * DURATION + DURATION / 2));
1107
1108        assert_eq!(get_prop_value(&compo.width), 300);
1109    }
1110}