2023-07-16 01:07:52 +01:00
use crate ::utils ::Pausable ;
2023-11-09 23:26:47 +00:00
use cfg_if ::cfg_if ;
2023-07-16 01:07:52 +01:00
use default_struct_builder ::DefaultBuilder ;
2024-05-07 13:24:14 +01:00
use leptos ::prelude ::diagnostics ::SpecialNonReactiveZone ;
2024-05-07 12:41:44 +01:00
use leptos ::prelude ::* ;
2023-08-15 12:31:25 +01:00
use std ::cell ::{ Cell , RefCell } ;
2023-07-16 01:07:52 +01:00
use std ::rc ::Rc ;
/// Call function on every requestAnimationFrame.
/// With controls of pausing and resuming.
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_raf_fn)
///
/// ## Usage
///
/// ```
2024-05-07 12:41:44 +01:00
/// # use leptos::prelude::*;
2023-07-16 01:07:52 +01:00
/// # use leptos_use::use_raf_fn;
/// use leptos_use::utils::Pausable;
/// #
/// # #[component]
2023-07-27 18:06:36 +01:00
/// # fn Demo() -> impl IntoView {
2024-05-07 12:41:44 +01:00
/// let (count, set_count) = signal(0);
2023-07-16 01:07:52 +01:00
///
2023-07-27 18:06:36 +01:00
/// let Pausable { pause, resume, is_active } = use_raf_fn(move |_| {
2023-07-16 01:07:52 +01:00
/// set_count.update(|count| *count += 1);
/// });
///
2023-07-27 18:06:36 +01:00
/// view! { <div>Count: { count }</div> }
2023-07-16 01:07:52 +01:00
/// }
/// ```
///
/// You can use `use_raf_fn_with_options` and set `immediate` to `false`. In that case
/// you have to call `resume()` before the `callback` is executed.
2023-11-09 23:26:47 +00:00
///
/// ## Server-Side Rendering
///
/// On the server this does basically nothing. The provided closure will never be called.
2023-07-16 01:07:52 +01:00
pub fn use_raf_fn (
2023-08-04 13:19:34 +01:00
callback : impl Fn ( UseRafFnCallbackArgs ) + 'static ,
2023-07-16 01:07:52 +01:00
) -> Pausable < impl Fn ( ) + Clone , impl Fn ( ) + Clone > {
2023-07-27 18:06:36 +01:00
use_raf_fn_with_options ( callback , UseRafFnOptions ::default ( ) )
2023-07-16 01:07:52 +01:00
}
/// Version of [`use_raf_fn`] that takes a `UseRafFnOptions`. See [`use_raf_fn`] for how to use.
pub fn use_raf_fn_with_options (
2023-08-04 13:19:34 +01:00
callback : impl Fn ( UseRafFnCallbackArgs ) + 'static ,
2023-07-16 01:07:52 +01:00
options : UseRafFnOptions ,
) -> Pausable < impl Fn ( ) + Clone , impl Fn ( ) + Clone > {
let UseRafFnOptions { immediate } = options ;
2023-08-15 12:31:25 +01:00
let raf_handle = Rc ::new ( Cell ::new ( None ::< i32 > ) ) ;
2023-07-16 01:07:52 +01:00
2024-05-07 12:41:44 +01:00
let ( is_active , set_active ) = signal ( false ) ;
2023-07-16 01:07:52 +01:00
let loop_ref = Rc ::new ( RefCell ::new ( Box ::new ( | _ : f64 | { } ) as Box < dyn Fn ( f64 ) > ) ) ;
let request_next_frame = {
2023-11-09 23:26:47 +00:00
cfg_if! { if #[ cfg(feature = " ssr " ) ] {
move | | ( )
} else {
use wasm_bindgen ::JsCast ;
use wasm_bindgen ::closure ::Closure ;
2023-07-16 01:07:52 +01:00
let loop_ref = Rc ::clone ( & loop_ref ) ;
2023-11-09 23:26:47 +00:00
let raf_handle = Rc ::clone ( & raf_handle ) ;
move | | {
let loop_ref = Rc ::clone ( & loop_ref ) ;
raf_handle . set (
window ( )
. request_animation_frame (
Closure ::once_into_js ( move | timestamp : f64 | {
loop_ref . borrow ( ) ( timestamp ) ;
} )
. as_ref ( )
. unchecked_ref ( ) ,
)
. ok ( ) ,
) ;
}
} }
2023-07-16 01:07:52 +01:00
} ;
let loop_fn = {
let request_next_frame = request_next_frame . clone ( ) ;
2023-08-16 16:23:29 +01:00
let previous_frame_timestamp = Cell ::new ( 0.0_ f64 ) ;
2023-07-16 01:07:52 +01:00
move | timestamp : f64 | {
2023-11-15 23:19:11 +00:00
if ! is_active . get_untracked ( ) {
2023-07-16 01:07:52 +01:00
return ;
}
2023-08-15 12:31:25 +01:00
let prev_timestamp = previous_frame_timestamp . get ( ) ;
2023-07-16 01:07:52 +01:00
let delta = if prev_timestamp > 0.0 {
timestamp - prev_timestamp
} else {
0.0
} ;
2024-02-27 00:02:37 +00:00
#[ cfg(debug_assertions) ]
2024-05-07 13:24:14 +01:00
let zone = SpecialNonReactiveZone ::enter ( ) ;
2024-02-27 00:02:37 +00:00
2023-07-16 01:07:52 +01:00
callback ( UseRafFnCallbackArgs { delta , timestamp } ) ;
2024-02-27 00:02:37 +00:00
#[ cfg(debug_assertions) ]
2024-05-07 13:24:14 +01:00
drop ( zone ) ;
2024-02-27 00:02:37 +00:00
2023-08-15 12:31:25 +01:00
previous_frame_timestamp . set ( timestamp ) ;
2023-07-16 01:07:52 +01:00
request_next_frame ( ) ;
}
} ;
let _ = loop_ref . replace ( Box ::new ( loop_fn ) ) ;
let resume = move | | {
2023-11-15 23:19:11 +00:00
if ! is_active . get_untracked ( ) {
2023-07-16 01:07:52 +01:00
set_active . set ( true ) ;
request_next_frame ( ) ;
}
} ;
let pause = move | | {
set_active . set ( false ) ;
2023-08-15 12:31:25 +01:00
let handle = raf_handle . get ( ) ;
2023-07-16 01:07:52 +01:00
if let Some ( handle ) = handle {
let _ = window ( ) . cancel_animation_frame ( handle ) ;
}
2023-08-15 12:31:25 +01:00
raf_handle . set ( None ) ;
2023-07-16 01:07:52 +01:00
} ;
if immediate {
resume ( ) ;
}
2024-07-19 20:22:27 -06:00
let pause_cleanup = send_wrapper ::SendWrapper ::new ( pause . clone ( ) ) ;
on_cleanup ( move | | pause_cleanup ( ) ) ;
2023-07-16 01:07:52 +01:00
Pausable {
resume ,
pause ,
is_active : is_active . into ( ) ,
}
}
/// Options for [`use_raf_fn_with_options`].
#[ derive(DefaultBuilder) ]
pub struct UseRafFnOptions {
/// Start the requestAnimationFrame loop immediately on creation. Defaults to `true`.
/// If false the loop will only start when you call `resume()`.
immediate : bool ,
}
impl Default for UseRafFnOptions {
fn default ( ) -> Self {
Self { immediate : true }
}
}
/// Type of the argument for the callback of [`use_raf_fn`].
pub struct UseRafFnCallbackArgs {
/// Time elapsed between this and the last frame.
pub delta : f64 ,
/// Time elapsed since the creation of the web page. See [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#the_time_origin Time origin).
pub timestamp : f64 ,
}