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