1#![deny(missing_docs)]
12#![deny(missing_debug_implementations)]
13
14use dioxus::prelude::{ReadableExt, WritableExt};
15
16#[cfg(feature = "css")]
17mod css;
18#[cfg(feature = "gesture")]
19mod gesture;
20mod hooks;
21#[cfg(feature = "list")]
22mod list;
23#[cfg(feature = "motion")]
24mod motion;
25#[cfg(feature = "native")]
26mod native;
27#[cfg(feature = "platform")]
28mod platform;
29#[cfg(feature = "presence")]
30mod presence;
31#[cfg(feature = "scroll")]
32mod scroll;
33#[cfg(feature = "transition")]
34mod transition;
35
36#[cfg(feature = "css")]
37pub use css::{AnimatedStyle, css_spring, css_tween};
38#[cfg(feature = "gesture")]
39pub use gesture::{
40 DragAxis, DragConfig, DragConstraints, DragHandle, Gesture, GestureConfig, PinchHandle,
41 SwipeConfig, SwipeDirection, SwipeEvent, use_drag, use_gesture, use_pinch, use_swipe,
42};
43pub use hooks::{
44 KeyframeHandle, SpringHandle, TimelineHandle, TweenHandle, use_keyframes, use_spring,
45 use_timeline, use_tween,
46};
47#[cfg(feature = "list")]
48pub use list::{AnimatedFor, stable_key};
49#[cfg(feature = "motion")]
50pub use motion::{MotionConfig, MotionHandle, use_motion};
51#[cfg(feature = "native")]
52pub use native::{
53 NativeWindowState, WindowAnimationHandle, WindowSpringHandle, use_window_animation,
54 use_window_spring,
55};
56#[cfg(feature = "platform")]
57pub use platform::{AnimationBackend, PlatformAdapter};
58#[cfg(feature = "presence")]
59pub use presence::{AnimatePresence, PresenceAnimation};
60#[cfg(feature = "scroll")]
61pub use scroll::{
62 ScrollAxis, ScrollConfig, ScrollProgressCalculator, ScrollTriggerConfig, ScrollTriggerHandle,
63 use_scroll_progress, use_scroll_trigger, use_scroll_velocity,
64};
65#[cfg(feature = "transition")]
66pub use transition::{PageTransition, TransitionMode, route_transition_key};
67
68pub(crate) fn finite_or(value: f32, fallback: f32) -> f32 {
69 if value.is_finite() { value } else { fallback }
70}
71
72pub(crate) fn set_signal<T: 'static>(signal: dioxus::prelude::Signal<T>, value: T) {
73 let mut signal = signal;
74 signal.set(value);
75}
76
77#[allow(dead_code)]
78pub(crate) fn read_signal<T: Clone + 'static>(signal: dioxus::prelude::Signal<T>) -> T {
79 signal.read().clone()
80}
81
82pub(crate) fn with_lock<T, R>(
83 value: &std::sync::Arc<std::sync::Mutex<T>>,
84 f: impl FnOnce(&mut T) -> R,
85) -> R {
86 let mut guard = value
87 .lock()
88 .unwrap_or_else(|poisoned| poisoned.into_inner());
89 f(&mut guard)
90}
91
92pub(crate) fn spawn_animation_loop(tick: impl FnMut(f32) -> bool + 'static) {
93 #[cfg(all(target_arch = "wasm32", feature = "web"))]
94 {
95 spawn_raf_loop(tick);
96 return;
97 }
98
99 #[cfg(not(target_arch = "wasm32"))]
100 {
101 use dioxus::prelude::use_future;
102 use std::time::{Duration, Instant};
103
104 let mut tick = Some(tick);
105 use_future(move || {
106 let mut tick = tick.take();
107 async move {
108 let Some(mut tick) = tick.take() else {
109 return;
110 };
111 let mut last = Instant::now();
112 loop {
113 let now = Instant::now();
114 let dt = now.duration_since(last).as_secs_f32().min(0.25);
115 last = now;
116 if !tick(dt) {
117 break;
118 }
119 std::thread::sleep(Duration::from_millis(16));
120 }
121 }
122 });
123 }
124
125 #[cfg(all(target_arch = "wasm32", not(feature = "web")))]
126 {
127 let _ = tick;
128 }
129}
130
131#[cfg(all(target_arch = "wasm32", feature = "web"))]
132fn spawn_raf_loop(tick: impl FnMut(f32) -> bool + 'static) {
133 use std::cell::{Cell, RefCell};
134 use std::rc::Rc;
135 use wasm_bindgen::JsCast;
136 use wasm_bindgen::closure::Closure;
137
138 let Some(window) = web_sys::window() else {
139 return;
140 };
141
142 let tick = Rc::new(RefCell::new(Box::new(tick) as Box<dyn FnMut(f32) -> bool>));
143 let last_timestamp = Rc::new(Cell::new(None::<f64>));
144 let callback: Rc<RefCell<Option<Closure<dyn FnMut()>>>> = Rc::new(RefCell::new(None));
145 let callback_ref = Rc::clone(&callback);
146 let tick_ref = Rc::clone(&tick);
147 let last_ref = Rc::clone(&last_timestamp);
148 let window_ref = window.clone();
149
150 *callback.borrow_mut() = Some(Closure::wrap(Box::new(move || {
151 let now = window_ref
152 .performance()
153 .map(|performance| performance.now())
154 .unwrap_or(0.0);
155 let dt = last_ref
156 .replace(Some(now))
157 .map(|last| ((now - last) / 1000.0).max(0.0) as f32)
158 .unwrap_or(0.0)
159 .min(0.25);
160
161 if (tick_ref.borrow_mut())(dt)
162 && let Some(callback) = callback_ref.borrow().as_ref()
163 {
164 let _ = window_ref.request_animation_frame(callback.as_ref().unchecked_ref());
165 }
166 }) as Box<dyn FnMut()>));
167
168 if let Some(callback) = callback.borrow().as_ref() {
169 let _ = window.request_animation_frame(callback.as_ref().unchecked_ref());
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use dioxus::prelude::*;
177 use std::cell::RefCell;
178 use std::sync::{Arc, Mutex};
179
180 thread_local! {
181 static SIGNAL_CAPTURE: RefCell<Option<Signal<i32>>> = const { RefCell::new(None) };
182 }
183
184 #[allow(non_snake_case)]
185 fn SignalHelperApp() -> Element {
186 let signal = use_signal(|| 1_i32);
187 SIGNAL_CAPTURE.with(|slot| *slot.borrow_mut() = Some(signal));
188
189 rsx! { div {} }
190 }
191
192 #[test]
193 fn finite_or_replaces_non_finite_values() {
194 assert_eq!(finite_or(2.0, 1.0), 2.0);
195 assert_eq!(finite_or(f32::NAN, 1.0), 1.0);
196 assert_eq!(finite_or(f32::INFINITY, 1.0), 1.0);
197 }
198
199 #[test]
200 fn signal_helpers_read_and_write_values() {
201 SIGNAL_CAPTURE.with(|slot| *slot.borrow_mut() = None);
202 let mut dom = VirtualDom::new(SignalHelperApp);
203 dom.rebuild_in_place();
204 let signal =
205 SIGNAL_CAPTURE.with(|slot| slot.borrow().as_ref().copied().expect("signal captured"));
206
207 assert_eq!(read_signal(signal), 1);
208 set_signal(signal, 4);
209 assert_eq!(read_signal(signal), 4);
210 }
211
212 #[test]
213 fn with_lock_updates_inner_value() {
214 let value = Arc::new(Mutex::new(1_i32));
215 let updated = with_lock(&value, |inner| {
216 *inner += 2;
217 *inner
218 });
219
220 assert_eq!(updated, 3);
221 assert_eq!(*value.lock().expect("lock"), 3);
222 }
223}