1use crate::sendwrap_fn;
2use crate::utils::Pausable;
3use cfg_if::cfg_if;
4use default_struct_builder::DefaultBuilder;
5use leptos::prelude::*;
6use std::cell::{Cell, RefCell};
7use std::rc::Rc;
8
9pub fn use_raf_fn(
47 callback: impl Fn(UseRafFnCallbackArgs) + 'static,
48) -> Pausable<impl Fn() + Clone + Send + Sync, impl Fn() + Clone + Send + Sync> {
49 use_raf_fn_with_options(callback, UseRafFnOptions::default())
50}
51
52pub fn use_raf_fn_with_options(
54 callback: impl Fn(UseRafFnCallbackArgs) + 'static,
55 options: UseRafFnOptions,
56) -> Pausable<impl Fn() + Clone + Send + Sync, impl Fn() + Clone + Send + Sync> {
57 let UseRafFnOptions { immediate } = options;
58
59 let raf_handle = Rc::new(Cell::new(None::<i32>));
60
61 let (is_active, set_active) = signal(false);
62
63 let loop_ref = Rc::new(RefCell::new(Box::new(|_: f64| {}) as Box<dyn Fn(f64)>));
64
65 let request_next_frame = {
66 cfg_if! { if #[cfg(feature = "ssr")] {
67 move || ()
68 } else {
69 use wasm_bindgen::JsCast;
70 use wasm_bindgen::closure::Closure;
71
72 let loop_ref = Rc::clone(&loop_ref);
73 let raf_handle = Rc::clone(&raf_handle);
74
75 move || {
76 let loop_ref = Rc::clone(&loop_ref);
77
78 raf_handle.set(
79 window()
80 .request_animation_frame(
81 Closure::once_into_js(move |timestamp: f64| {
82 loop_ref.borrow()(timestamp);
83 })
84 .as_ref()
85 .unchecked_ref(),
86 )
87 .ok(),
88 );
89 }
90 }}
91 };
92
93 let loop_fn = {
94 #[allow(clippy::clone_on_copy)]
95 let request_next_frame = request_next_frame.clone();
96 let previous_frame_timestamp = Cell::new(0.0_f64);
97
98 move |timestamp: f64| {
99 if !is_active.try_get_untracked().unwrap_or_default() {
100 return;
101 }
102
103 let prev_timestamp = previous_frame_timestamp.get();
104 let delta = if prev_timestamp > 0.0 {
105 timestamp - prev_timestamp
106 } else {
107 0.0
108 };
109
110 #[cfg(debug_assertions)]
111 let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
112
113 callback(UseRafFnCallbackArgs { delta, timestamp });
114
115 #[cfg(debug_assertions)]
116 drop(zone);
117
118 previous_frame_timestamp.set(timestamp);
119
120 request_next_frame();
121 }
122 };
123
124 let _ = loop_ref.replace(Box::new(loop_fn));
125
126 let resume = sendwrap_fn!(move || {
127 if !is_active.get_untracked() {
128 set_active.set(true);
129 request_next_frame();
130 }
131 });
132
133 let pause = sendwrap_fn!(move || {
134 set_active.set(false);
135
136 let handle = raf_handle.get();
137 if let Some(handle) = handle {
138 let _ = window().cancel_animation_frame(handle);
139 }
140 raf_handle.set(None);
141 });
142
143 if immediate {
144 resume();
145 }
146
147 on_cleanup({
148 let pause = pause.clone();
149 #[allow(clippy::redundant_closure)]
150 move || pause()
151 });
152
153 Pausable {
154 resume,
155 pause,
156 is_active: is_active.into(),
157 }
158}
159
160#[derive(DefaultBuilder)]
162pub struct UseRafFnOptions {
163 immediate: bool,
166}
167
168impl Default for UseRafFnOptions {
169 fn default() -> Self {
170 Self { immediate: true }
171 }
172}
173
174pub struct UseRafFnCallbackArgs {
176 pub delta: f64,
178
179 pub timestamp: f64,
181}