use std::cell::Cell;
use std::rc::Rc;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use crate::events::on_scope_unmount;
pub struct TimeoutHandle {
id: Rc<Cell<Option<i32>>>,
_closure: Closure<dyn FnMut()>,
}
impl TimeoutHandle {
pub fn cancel(&self) {
if let Some(id) = self.id.take() {
if let Some(w) = web_sys::window() {
w.clear_timeout_with_handle(id);
}
}
}
pub fn is_pending(&self) -> bool {
self.id.get().is_some()
}
}
impl Drop for TimeoutHandle {
fn drop(&mut self) {
self.cancel();
}
}
pub fn after<F>(delay_ms: u32, f: F) -> TimeoutHandle
where
F: FnOnce() + 'static,
{
let id_cell: Rc<Cell<Option<i32>>> = Rc::new(Cell::new(None));
let id_for_cb = id_cell.clone();
let mut user_fn = Some(f);
let closure = Closure::wrap(Box::new(move || {
id_for_cb.set(None);
if let Some(f) = user_fn.take() {
f();
}
}) as Box<dyn FnMut()>);
if let Some(w) = web_sys::window() {
if let Ok(id) = w.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
delay_ms as i32,
) {
id_cell.set(Some(id));
}
}
TimeoutHandle {
id: id_cell,
_closure: closure,
}
}
pub fn after_scoped<F>(delay_ms: u32, f: F)
where
F: FnOnce() + 'static,
{
let handle = after(delay_ms, f);
on_scope_unmount(move || drop(handle));
}
pub struct IntervalHandle {
id: Cell<Option<i32>>,
_closure: Closure<dyn FnMut()>,
}
impl IntervalHandle {
pub fn cancel(&self) {
if let Some(id) = self.id.take() {
if let Some(w) = web_sys::window() {
w.clear_interval_with_handle(id);
}
}
}
pub fn is_active(&self) -> bool {
self.id.get().is_some()
}
}
impl Drop for IntervalHandle {
fn drop(&mut self) {
self.cancel();
}
}
pub fn every<F>(period_ms: u32, f: F) -> IntervalHandle
where
F: FnMut() + 'static,
{
let closure = Closure::wrap(Box::new(f) as Box<dyn FnMut()>);
let id = web_sys::window().and_then(|w| {
w.set_interval_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
period_ms as i32,
)
.ok()
});
IntervalHandle {
id: Cell::new(id),
_closure: closure,
}
}
pub fn every_scoped<F>(period_ms: u32, f: F)
where
F: FnMut() + 'static,
{
let handle = every(period_ms, f);
on_scope_unmount(move || drop(handle));
}
pub struct Debounced {
id: Cell<Option<i32>>,
closure: Cell<Option<Closure<dyn FnMut()>>>,
}
impl Debounced {
pub fn new() -> Rc<Self> {
Rc::new(Self {
id: Cell::new(None),
closure: Cell::new(None),
})
}
pub fn new_scoped() -> Rc<Self> {
let slot = Self::new();
let slot_for_cleanup = slot.clone();
on_scope_unmount(move || slot_for_cleanup.cancel());
slot
}
pub fn schedule<F>(self: &Rc<Self>, delay_ms: u32, f: F)
where
F: FnOnce() + 'static,
{
self.cancel();
let Some(w) = web_sys::window() else { return };
let me = self.clone();
let mut user_fn = Some(f);
let closure = Closure::wrap(Box::new(move || {
me.id.set(None);
me.closure.take();
if let Some(f) = user_fn.take() {
f();
}
}) as Box<dyn FnMut()>);
if let Ok(id) = w.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
delay_ms as i32,
) {
self.id.set(Some(id));
self.closure.set(Some(closure));
}
}
pub fn cancel(&self) {
if let Some(id) = self.id.take() {
if let Some(w) = web_sys::window() {
w.clear_timeout_with_handle(id);
}
}
self.closure.take();
}
pub fn is_pending(&self) -> bool {
self.id.get().is_some()
}
}
pub async fn sleep(delay_ms: u32) {
let p = js_sys::Promise::new(&mut |resolve, _reject| {
if let Some(w) = web_sys::window() {
let _ =
w.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, delay_ms as i32);
}
});
let _ = JsFuture::from(p).await;
}
pub async fn next_frame() -> f64 {
let p = js_sys::Promise::new(&mut |resolve, _reject| {
if let Some(w) = web_sys::window() {
let _ = w.request_animation_frame(&resolve);
}
});
JsFuture::from(p)
.await
.ok()
.and_then(|v| v.as_f64())
.unwrap_or(0.0)
}
pub async fn next_tick() {
let _ = JsFuture::from(js_sys::Promise::resolve(&JsValue::NULL)).await;
}