Skip to main content

dioxus_animate/
lib.rs

1use dioxus::{prelude::*, web::WebEventExt};
2use std::rc::Rc;
3
4#[derive(Clone, PartialEq)]
5pub enum Operation {
6    Add(String),
7    Remove(String),
8    Group(Vec<Operation>),
9}
10
11fn operate(patient: &web_sys::Element, operation: &Operation) {
12    match operation {
13        Operation::Add(class) => {
14            let classes = class.split_whitespace().collect::<Vec<&str>>();
15
16            for class in classes {
17                let _ = patient.class_list().add_1(class);
18            }
19        }
20        Operation::Remove(class) => {
21            let classes = class.split_whitespace().collect::<Vec<&str>>();
22
23            for class in classes {
24                let _ = patient.class_list().remove_1(class);
25            }
26        }
27        Operation::Group(operations) => {
28            for operation in operations {
29                operate(patient, operation);
30            }
31        }
32    }
33}
34
35#[macro_export]
36macro_rules! use_animate {
37    ( $($input:tt)* ) => {{
38        let operations = use_signal(|| {
39            let mut ops = Vec::new();
40            $crate::__parse_animations!(ops, $($input)*);
41            ops
42        });
43
44        $crate::_use_animate(operations.into())
45    }};
46}
47
48#[macro_export]
49macro_rules! __parse_animations {
50    ($ops:ident, $t:expr => add($s:literal), $($rest:tt)*) => {
51        $ops.push(($t, $crate::Operation::Add($s.to_string())));
52        $crate::__parse_animations!($ops, $($rest)*);
53    };
54
55    ($ops:ident, $t:expr => remove($s:literal), $($rest:tt)*) => {
56        $ops.push(($t, $crate::Operation::Remove($s.to_string())));
57        $crate::__parse_animations!($ops, $($rest)*);
58    };
59
60    ($ops:ident, $t:expr => ($($inner:tt)*), $($rest:tt)*) => {
61        $ops.push(($t, {
62            let mut group_ops = Vec::new();
63            $crate::__expand_inner!(group_ops, $($inner)*);
64            $crate::Operation::Group(group_ops)
65        }));
66        $crate::__parse_animations!($ops, $($rest)*);
67    };
68
69    ($ops:ident, $t:expr => add($s:literal)) => {
70        $ops.push(($t, $crate::Operation::Add($s.to_string())));
71    };
72
73    ($ops:ident, $t:expr => remove($s:literal)) => {
74        $ops.push(($t, $crate::Operation::Remove($s.to_string())));
75    };
76
77    ($ops:ident, $t:expr => ($($inner:tt)*)) => {
78        $ops.push(($t, {
79            let mut group_ops = Vec::new();
80            $crate::__expand_inner!(group_ops, $($inner)*);
81            $crate::Operation::Group(group_ops)
82        }));
83    };
84
85    ($ops:ident,) => {};
86}
87
88#[macro_export]
89macro_rules! __expand_inner {
90    ( $ops:ident, add($s:literal); $($rest:tt)* ) => {
91        $ops.push($crate::Operation::Add($s.to_string()));
92        $crate::__expand_inner!($ops, $($rest)*);
93    };
94    ( $ops:ident, remove($s:literal); $($rest:tt)* ) => {
95        $ops.push($crate::Operation::Remove($s.to_string()));
96        $crate::__expand_inner!($ops, $($rest)*);
97    };
98    ( $ops:ident, add($s:literal) ) => {
99        $ops.push($crate::Operation::Add($s.to_string()));
100    };
101    ( $ops:ident, remove($s:literal) ) => {
102        $ops.push($crate::Operation::Remove($s.to_string()));
103    };
104    ( $ops:ident, ) => {};
105}
106
107pub struct UseAnimate {
108    ops: Vec<(u64, Operation)>,
109}
110
111impl UseAnimate {
112    pub fn start(&self, patient: ReadSignal<Option<Rc<MountedData>>>) {
113        let ops = self.ops.clone();
114
115        spawn(async move {
116            let Some(element) = patient() else {
117                return;
118            };
119
120            let mut time_elapsed = 0;
121
122            for (duration, operation) in ops.into_iter() {
123                let interval = duration - time_elapsed;
124
125                async_std::task::sleep(instant::Duration::from_millis(interval)).await;
126                time_elapsed += interval;
127
128                let web_element = element.as_web_event();
129                operate(&web_element, &operation);
130            }
131        });
132    }
133
134    pub fn start_for_id(&self, id: &str) {
135        let ops = self.ops.clone();
136        use wasm_bindgen::JsCast;
137
138        let element = gloo::utils::document()
139            .get_element_by_id(id)
140            .and_then(|element| element.dyn_into::<web_sys::Element>().ok())
141            .expect("Element not found");
142
143        spawn(async move {
144            let mut time_elapsed = 0;
145
146            for (duration, operation) in ops.into_iter() {
147                let interval = duration - time_elapsed;
148
149                async_std::task::sleep(instant::Duration::from_millis(interval)).await;
150                time_elapsed += interval;
151
152                operate(&element, &operation);
153            }
154        });
155    }
156}
157
158pub fn _use_animate(ops: ReadSignal<Vec<(u64, Operation)>>) -> UseAnimate {
159    UseAnimate { ops: ops() }
160}
161
162pub mod prelude {
163    pub use crate::use_animate;
164}