sycamore/
motion.rs
1use std::cell::OnceCell;
4use std::rc::Rc;
5
6use crate::reactive::*;
7
8type RafState = (Signal<bool>, Rc<dyn Fn() + 'static>, Rc<dyn Fn() + 'static>);
10
11pub fn create_raf(mut cb: impl FnMut() + 'static) -> RafState {
20 let running = create_signal(false);
21 let start: Rc<dyn Fn()>;
22 let stop: Rc<dyn Fn()>;
23 let _ = &mut cb;
24
25 #[cfg(all(target_arch = "wasm32", feature = "web"))]
27 {
28 use std::cell::RefCell;
29
30 use wasm_bindgen::prelude::*;
31
32 use crate::web::window;
33
34 let f = Rc::new(RefCell::new(None::<Closure<dyn FnMut()>>));
35 let g = Rc::clone(&f);
36
37 *g.borrow_mut() = Some(Closure::new(move || {
38 if running.get() {
39 cb();
41 window()
43 .request_animation_frame(
44 f.borrow().as_ref().unwrap_throw().as_ref().unchecked_ref(),
45 )
46 .unwrap_throw();
47 }
48 }));
49 start = Rc::new(move || {
50 if !running.get() {
51 running.set(true);
52 window()
53 .request_animation_frame(
54 g.borrow().as_ref().unwrap_throw().as_ref().unchecked_ref(),
55 )
56 .unwrap_throw();
57 }
58 });
59 stop = Rc::new(move || running.set(false));
60 }
61 #[cfg(not(all(target_arch = "wasm32", feature = "web")))]
62 {
63 start = Rc::new(move || running.set(true));
64 stop = Rc::new(move || running.set(false));
65 }
66
67 (running, start, stop)
68}
69
70pub fn create_raf_loop(mut f: impl FnMut() -> bool + 'static) -> RafState {
79 let stop_shared = Rc::new(OnceCell::new());
80 let (running, start, stop) = create_raf({
81 let stop_shared = Rc::clone(&stop_shared);
82 move || {
83 if !f() {
84 stop_shared.get();
85 }
86 }
87 });
88 stop_shared.set(Rc::clone(&stop)).ok().unwrap();
89 (running, start, stop)
90}
91
92pub fn create_tweened_signal<T: Lerp + Clone>(
94 initial: T,
95 transition_duration: std::time::Duration,
96 easing_fn: impl Fn(f32) -> f32 + 'static,
97) -> Tweened<T> {
98 Tweened::new(initial, transition_duration, easing_fn)
99}
100
101pub trait Lerp {
103 fn lerp(&self, other: &Self, scalar: f32) -> Self;
107}
108
109macro_rules! impl_lerp_for_float {
110 ($($f: path),*) => {
111 $(
112 impl Lerp for $f {
113 fn lerp(&self, other: &Self, scalar: f32) -> Self {
114 self + (other - self) * scalar as $f
115 }
116 }
117 )*
118 };
119}
120
121impl_lerp_for_float!(f32, f64);
122
123macro_rules! impl_lerp_for_int {
124 ($($i: path),*) => {
125 $(
126 impl Lerp for $i {
127 fn lerp(&self, other: &Self, scalar: f32) -> Self {
128 (*self as f32 + (other - self) as f32 * scalar).round() as $i
129 }
130 }
131 )*
132 };
133}
134
135impl_lerp_for_int!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
136
137impl<T: Lerp + Clone, const N: usize> Lerp for [T; N] {
138 fn lerp(&self, other: &Self, scalar: f32) -> Self {
139 let mut tmp = (*self).clone();
140
141 for (t, other) in tmp.iter_mut().zip(other) {
142 *t = t.lerp(other, scalar);
143 }
144
145 tmp
146 }
147}
148
149pub struct Tweened<T: Lerp + Clone + 'static>(Signal<TweenedInner<T>>);
151impl<T: Lerp + Clone> std::fmt::Debug for Tweened<T> {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 f.debug_struct("Tweened").finish()
154 }
155}
156
157struct TweenedInner<T: Lerp + Clone + 'static> {
158 value: Signal<T>,
159 is_tweening: Signal<bool>,
160 raf_state: Option<RafState>,
161 transition_duration_ms: f32,
162 easing_fn: Rc<dyn Fn(f32) -> f32>,
163}
164
165impl<T: Lerp + Clone> Tweened<T> {
166 pub(crate) fn new(
170 initial: T,
171 transition_duration: std::time::Duration,
172 easing_fn: impl Fn(f32) -> f32 + 'static,
173 ) -> Self {
174 let value = create_signal(initial);
175 Self(create_signal(TweenedInner {
176 value,
177 is_tweening: create_signal(false),
178 raf_state: None,
179 transition_duration_ms: transition_duration.as_millis() as f32,
180 easing_fn: Rc::new(easing_fn),
181 }))
182 }
183
184 pub fn set(&self, _new_value: T) {
195 #[cfg(all(target_arch = "wasm32", feature = "web"))]
196 {
197 use web_sys::js_sys::Date;
198
199 let start = self.signal().get_clone_untracked();
200 let easing_fn = Rc::clone(&self.0.with(|this| this.easing_fn.clone()));
201
202 let start_time = Date::now();
203 let signal = self.0.with(|this| this.value.clone());
204 let is_tweening = self.0.with(|this| this.is_tweening.clone());
205 let transition_duration_ms = self.0.with(|this| this.transition_duration_ms);
206
207 if let Some((running, _, stop)) = &self.0.with(|this| this.raf_state.clone()) {
209 if running.get_untracked() {
210 stop();
211 }
212 }
213
214 let (running, start, stop) = create_raf_loop(move || {
215 let now = Date::now();
216
217 let since_start = now - start_time;
218 let scalar = since_start as f32 / transition_duration_ms;
219
220 if now < start_time + transition_duration_ms as f64 {
221 signal.set(start.lerp(&_new_value, easing_fn(scalar)));
222 true
223 } else {
224 signal.set(_new_value.clone());
225 is_tweening.set(false);
226 false
227 }
228 });
229 start();
230 is_tweening.set(true);
231 self.0
232 .update(|this| this.raf_state = Some((running, start, stop)));
233 }
234 }
235
236 pub fn get(&self) -> T
238 where
239 T: Copy,
240 {
241 self.signal().get()
242 }
243
244 pub fn get_untracked(&self) -> T
246 where
247 T: Copy,
248 {
249 self.signal().get_untracked()
250 }
251
252 pub fn signal(&self) -> Signal<T> {
254 self.0.with(|this| this.value)
255 }
256
257 pub fn is_tweening(&self) -> bool {
260 self.0.with(|this| this.is_tweening.get())
261 }
262}
263
264impl<T: Lerp + Clone + 'static> Clone for Tweened<T> {
265 fn clone(&self) -> Self {
266 *self
267 }
268}
269impl<T: Lerp + Clone + 'static> Copy for Tweened<T> {}
270
271impl<T: Lerp + Clone + 'static> Clone for TweenedInner<T> {
272 fn clone(&self) -> Self {
273 Self {
274 value: self.value,
275 is_tweening: self.is_tweening,
276 raf_state: self.raf_state.clone(),
277 transition_duration_ms: self.transition_duration_ms,
278 easing_fn: Rc::clone(&self.easing_fn),
279 }
280 }
281}