zng_app/widget/
easing.rs

1use std::{any::Any, sync::Arc, time::Duration};
2
3use super::builder::*;
4use zng_layout::unit::*;
5use zng_var::{
6    BoxedVar, Var, VarValue,
7    animation::{
8        Transitionable,
9        easing::{EasingStep, EasingTime},
10    },
11    types::{ArcWhenVar, ContextualizedVar},
12};
13
14pub use zng_app_proc_macros::easing;
15
16type EasingFn = Arc<dyn Fn(EasingTime) -> EasingStep + Send + Sync>;
17
18#[doc(hidden)]
19#[expect(non_camel_case_types)]
20pub trait easing_property: Send + Sync + Clone + Copy {
21    fn easing_property_unset(self);
22    fn easing_property(self, duration: Duration, easing: EasingFn) -> Vec<Box<dyn AnyPropertyBuildAction>>;
23    fn easing_when_data(self, duration: Duration, easing: EasingFn) -> WhenBuildAction;
24}
25
26#[doc(hidden)]
27#[expect(non_camel_case_types)]
28#[diagnostic::on_unimplemented(note = "property type must be `Transitionable` to support `#[easing]`")]
29pub trait easing_property_input_Transitionable: Any + Send {
30    fn easing(self, duration: Duration, easing: EasingFn, when_conditions_data: &[Option<Arc<dyn Any + Send + Sync>>]) -> Self;
31}
32impl<T: VarValue + Transitionable> easing_property_input_Transitionable for BoxedVar<T> {
33    fn easing(self, duration: Duration, easing: EasingFn, when_conditions_data: &[Option<Arc<dyn Any + Send + Sync>>]) -> Self {
34        if let Some(when) = (*self).as_unboxed_any().downcast_ref::<ContextualizedVar<T>>() {
35            let conditions: Vec<_> = when_conditions_data
36                .iter()
37                .map(|d| d.as_ref().and_then(|d| d.downcast_ref::<(Duration, EasingFn)>().cloned()))
38                .collect();
39
40            if conditions.iter().any(|c| c.is_some()) {
41                let when = when.clone();
42                return ContextualizedVar::new(move || {
43                    when.borrow_init()
44                        .as_any()
45                        .downcast_ref::<ArcWhenVar<T>>()
46                        .expect("expected `ArcWhenVar`")
47                        .easing_when(conditions.clone(), (duration, easing.clone()))
48                })
49                .boxed();
50            }
51        } else if let Some(when) = (*self).as_unboxed_any().downcast_ref::<ArcWhenVar<T>>() {
52            let conditions: Vec<_> = when_conditions_data
53                .iter()
54                .map(|d| d.as_ref().and_then(|d| d.downcast_ref::<(Duration, EasingFn)>().cloned()))
55                .collect();
56
57            if conditions.iter().any(|c| c.is_some()) {
58                return when.easing_when(conditions.clone(), (duration, easing.clone())).boxed();
59            }
60        }
61        Var::easing(&self, duration, move |t| easing(t)).boxed()
62    }
63}
64
65macro_rules! impl_easing_property_inputs {
66    ($T0:ident, $($T:ident,)*) => {
67        impl_easing_property_inputs! {
68            $($T,)*
69        }
70
71        impl<
72            $T0: easing_property_input_Transitionable,
73            $($T: easing_property_input_Transitionable),*
74        > easing_property for PropertyInputTypes<($T0, $($T,)*)> {
75            fn easing_property_unset(self) { }
76            fn easing_property(self, duration: Duration, easing: EasingFn) -> Vec<Box<dyn AnyPropertyBuildAction>> {
77                if duration == Duration::ZERO {
78                    vec![]
79                } else {
80                    vec![
81                        Box::new(PropertyBuildAction::<$T0>::new($crate::handler::clmv!(easing, |a| easing_property_input_Transitionable::easing(a.input, duration, easing.clone(), &a.when_conditions_data)))),
82                        $(Box::new(PropertyBuildAction::<$T>::new($crate::handler::clmv!(easing, |a| easing_property_input_Transitionable::easing(a.input, duration, easing.clone(), &a.when_conditions_data)))),)*
83                    ]
84                }
85            }
86            fn easing_when_data(self, duration: Duration, easing: EasingFn) -> WhenBuildAction {
87                if duration == Duration::ZERO {
88                    WhenBuildAction::new_no_default((duration, easing))
89                } else {
90                    WhenBuildAction::new(
91                        (duration, easing),
92                        || {
93                            let easing = Arc::new($crate::var::animation::easing::linear) as EasingFn;
94                            vec![
95                                Box::new(PropertyBuildAction::<$T0>::new($crate::handler::clmv!(easing, |a| easing_property_input_Transitionable::easing(a.input, 0.ms(), easing.clone(), &a.when_conditions_data)))),
96                                $(Box::new(PropertyBuildAction::<$T>::new($crate::handler::clmv!(easing, |a| easing_property_input_Transitionable::easing(a.input, 0.ms(), easing.clone(), &a.when_conditions_data)))),)*
97                            ]
98                        }
99                    )
100                }
101            }
102        }
103    };
104    () => { };
105}
106impl_easing_property_inputs! {
107    I0, I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15,
108}