use std::{borrow::Cow, cell::RefCell, ops::DerefMut, pin::Pin, rc::Rc, task::Waker};
use wasm_bindgen_futures::wasm_bindgen::{JsCast, JsValue, prelude::Closure};
use crate::Str;
type Callback = Rc<Closure<dyn FnMut(JsValue)>>;
#[derive(Clone, Default)]
struct FutureEventOccurrence {
event: Rc<RefCell<Option<web_sys::Event>>>,
wakers: Rc<RefCell<Vec<Waker>>>,
}
impl std::future::Future for FutureEventOccurrence {
type Output = web_sys::Event;
fn poll(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
if let Some(event) = self.event.borrow().as_ref() {
std::task::Poll::Ready(event.clone())
} else {
self.wakers.borrow_mut().push(cx.waker().clone());
std::task::Poll::Pending
}
}
}
#[derive(Clone)]
pub struct EventListener {
target: web_sys::EventTarget,
event_name: Str,
callback: Rc<RefCell<Option<Callback>>>,
events: Rc<RefCell<FutureEventOccurrence>>,
}
impl Drop for EventListener {
fn drop(&mut self) {
if Rc::strong_count(&self.callback) == 1 {
if let Some(rc_callback) = self.callback.take() {
if let Ok(callback) = Rc::try_unwrap(rc_callback) {
self.target
.remove_event_listener_with_callback(
&self.event_name,
callback.as_ref().unchecked_ref(),
)
.unwrap();
}
}
}
}
}
impl EventListener {
pub fn new(
target: impl AsRef<web_sys::EventTarget>,
event_name: impl Into<Cow<'static, str>>,
) -> Self {
let events: Rc<RefCell<FutureEventOccurrence>> = Default::default();
let callback = Closure::wrap({
let events = events.clone();
Box::new(move |val: JsValue| {
let ev: web_sys::Event = val.unchecked_into();
let event = std::mem::take(events.borrow_mut().deref_mut());
*event.event.borrow_mut() = Some(ev);
let wakers = std::mem::take(event.wakers.borrow_mut().deref_mut());
for waker in wakers.into_iter() {
waker.wake();
}
}) as Box<dyn FnMut(JsValue)>
});
let event_name = event_name.into();
let target = target.as_ref().clone();
target
.add_event_listener_with_callback(&event_name, callback.as_ref().unchecked_ref())
.unwrap();
Self {
target,
event_name,
callback: Rc::new(RefCell::new(Some(Rc::new(callback)))),
events,
}
}
pub fn next(&self) -> impl std::future::Future<Output = web_sys::Event> {
self.events.borrow().clone()
}
}