use crate::{
dom_types::MessageMapper,
util::{self, ClosureNew},
};
use enclose::enclose;
use serde::de::DeserializeOwned;
use std::{fmt, mem};
use wasm_bindgen::{closure::Closure, JsCast};
pub type Event = web_sys::Event;
#[deprecated]
pub const UPDATE_TRIGGER_EVENT_ID: &str = "triggerupdate";
macro_rules! make_events {
{ $($event_camel:ident => $event:expr),+ } => {
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Ev {
$(
$event_camel,
)+
}
impl Ev {
pub fn as_str(&self) -> &str {
match self {
$ (
Ev::$event_camel => $event,
) +
}
}
}
impl From<&str> for Ev {
fn from(event: &str) -> Self {
match event {
$ (
$event => Ev::$event_camel,
) +
_ => {
crate::error(&format!("Can't find this event: {}", event));
Ev::Click
}
}
}
}
impl From<String> for Ev {
fn from(event: String) -> Self {
match event.as_ref(){
$ (
$event => Ev::$event_camel,
) +
_ => {
crate::error(&format!("Can't find this event: {}", event));
Ev::Click
}
}
}
}
impl ToString for Ev {
fn to_string( & self ) -> String {
match self {
$ (
Ev::$ event_camel => $ event.into(),
) +
}
}
}
}
}
make_events! {
Cached => "cached", Error => "error", Abort => "abort", Load => "load", BeforeUnload => "beforeunload",
Unload => "unload", Online => "online", Offline => "offline", Focus => "focus", Blur => "blur",
Open => "open", Message => "message", Close => "close", PageHide => "pagehide",
PageShow => "pageshow", PopState => "popstate", AnimationStart => "animationstart", AnimationEnd => "animationend",
AnimationIteration => "animationiteration", TransitionStart => "transtionstart", TransitionEnd => "transitionend",
TranstionRun => "transitionrun",
Rest => "rest", Submit => "submit", BeforePrint => "beforeprint", AfterPrint => "afterprint",
CompositionStart => "compositionstart", CompositionUpdate => "compositionupdate", CompositionEnd => "compositionend",
FullScreenChange => "fullscreenchange", FullScreenError => "fullscreenerror", Resize => "resize",
Scroll => "scroll", Cut => "cut", Copy => "copy", Paste => "paste",
KeyDown => "keydown", KeyUp => "keyup",
KeyPress => "keypress", AuxClick => "auxclick", Click => "click", ContextMenu => "contextmenu", DblClick => "dblclick",
MouseDown => "mousedown", MouseEnter => "mouseenter", MouseLeave => "mouseleave",
MouseMove => "mousemove", MouseOver => "mouseover", MouseOut => "mouseout", MouseUp => "mouseup",
PointerLockChange => "pointerlockchange", PointerLockError => "pointerlockerror", Select => "select",
Wheel => "wheel",
PointerOver => "pointerover", PointerEnter => "pointerenter",
PointerDown => "pointerdown", PointerMove => "pointermove", PointerUp => "pointerup",
PointerCancel => "pointercancel", PointerOut => "pointerout", PointerLeave => "pointerleave",
GotPointerCapture => "gotpointercapture", LostPointerCapture => "lostpointercapture",
Drag => "drag", DragEnd => "dragend", DragEnter => "dragenter", DragStart => "dragstart", DragLeave => "dragleave",
DragOver => "dragover", Drop => "drop",
AudioProcess => "audioprocess", CanPlay => "canplay", CanPlayThrough => "canplaythrough", Complete => "complete",
DurationChange => "durationchange", Emptied => "emptied", Ended => "ended", LoadedData => "loadeddata",
LoadedMetaData => "loadedmetadata", Pause => "pause", Play => "play", Playing => "playing", RateChange => "ratechange",
Seeked => "seeked", Seeking => "seeking", Stalled => "stalled", Suspend => "suspend", TimeUpdate => "timeupdate",
VolumeChange => "volumechange",
Change => "change",
Input => "input",
TriggerUpdate => "triggerupdate"
}
type EventHandler<Ms> = Box<dyn FnMut(web_sys::Event) -> Ms>;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Category {
Custom,
Input,
Keyboard,
Mouse,
Pointer,
Raw,
Simple,
}
pub struct Listener<Ms> {
pub trigger: Ev,
pub handler: Option<EventHandler<Ms>>,
pub closure: Option<Closure<dyn FnMut(web_sys::Event)>>,
pub control_val: Option<String>,
pub control_checked: Option<bool>,
category: Option<Category>,
message: Option<Ms>,
}
impl<Ms> fmt::Debug for Listener<Ms> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Listener {{ trigger:{:#?}, handler:{:#?}, closure:{:#?}, control:{:#?}{:#?}, category:{:#?}",
self.trigger,
fmt_hook_fn(&self.handler),
fmt_hook_fn(&self.closure),
self.control_val,
self.control_checked,
self.category,
)
}
}
impl<Ms> Listener<Ms> {
pub fn new(
trigger: &str,
handler: Option<EventHandler<Ms>>,
category: Option<Category>,
message: Option<Ms>,
) -> Self {
Self {
trigger: trigger.into(),
handler,
closure: None,
control_val: None,
control_checked: None,
category,
message,
}
}
pub fn new_control(val: String) -> Self {
Self {
trigger: Ev::Input,
handler: None,
closure: None,
control_val: Some(val),
control_checked: None,
category: None,
message: None,
}
}
pub fn new_control_check(checked: bool) -> Self {
Self {
trigger: Ev::Click,
handler: None,
closure: None,
control_val: None,
control_checked: Some(checked),
category: None,
message: None,
}
}
pub fn attach<T>(&mut self, el_ws: &T, mailbox: crate::vdom::Mailbox<Ms>)
where
T: AsRef<web_sys::EventTarget>,
{
let mut handler = self.handler.take().expect("Can't find old handler");
let closure = Closure::new(move |event: web_sys::Event| {
let msg = handler(event);
mailbox.send(msg);
});
(el_ws.as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback(
self.trigger.as_str(),
closure.as_ref().unchecked_ref(),
)
.expect("Problem adding listener to element");
if self.closure.replace(closure).is_some() {
panic!("self.closure already set in attach");
}
}
pub fn detach<T>(&mut self, el_ws: &T)
where
T: AsRef<web_sys::EventTarget>,
{
let closure = self.closure.take().expect("Can't find closure to detach");
(el_ws.as_ref() as &web_sys::EventTarget)
.remove_event_listener_with_callback(
self.trigger.as_str(),
closure.as_ref().unchecked_ref(),
)
.expect("Problem removing listener from element");
}
}
impl<Ms> PartialEq for Listener<Ms> {
fn eq(&self, other: &Self) -> bool {
self.trigger == other.trigger
&& self.category == other.category
&& mem::discriminant(&self.message) == mem::discriminant(&other.message)
}
}
impl<Ms: 'static, OtherMs: 'static> MessageMapper<Ms, OtherMs> for Listener<Ms> {
type SelfWithOtherMs = Listener<OtherMs>;
fn map_message(self, f: impl FnOnce(Ms) -> OtherMs + 'static + Clone) -> Listener<OtherMs> {
Listener {
trigger: self.trigger,
handler: self.handler.map(enclose!((f) |mut eh| {
Box::new(move |event| {
let m = (*eh)(event);
(f.clone())(m)
}) as EventHandler<OtherMs>
})),
closure: self.closure,
control_val: self.control_val,
control_checked: self.control_checked,
category: self.category,
message: self.message.map(f),
}
}
}
pub fn input_ev<Ms, T: ToString + Copy>(
trigger: T,
handler: impl FnOnce(String) -> Ms + 'static + Clone,
) -> Listener<Ms> {
let closure = move |event: web_sys::Event| {
let value = event
.target()
.as_ref()
.ok_or("Can't get event target reference")
.and_then(util::get_value)
.map_err(crate::error)
.unwrap_or_default();
(handler.clone())(value)
};
Listener::new(
&trigger.to_string(),
Some(Box::new(closure)),
Some(Category::Input),
None,
)
}
pub fn keyboard_ev<Ms, T: ToString + Copy>(
trigger: T,
handler: impl FnOnce(web_sys::KeyboardEvent) -> Ms + 'static + Clone,
) -> Listener<Ms> {
let closure = move |event: web_sys::Event| {
(handler.clone())(event.dyn_ref::<web_sys::KeyboardEvent>().unwrap().clone())
};
Listener::new(
&trigger.to_string(),
Some(Box::new(closure)),
Some(Category::Keyboard),
None,
)
}
pub fn mouse_ev<Ms, T: ToString + Copy>(
trigger: T,
handler: impl FnOnce(web_sys::MouseEvent) -> Ms + 'static + Clone,
) -> Listener<Ms> {
let closure = move |event: web_sys::Event| {
(handler.clone())(event.dyn_ref::<web_sys::MouseEvent>().unwrap().clone())
};
Listener::new(
&trigger.to_string(),
Some(Box::new(closure)),
Some(Category::Mouse),
None,
)
}
pub fn pointer_ev<Ms, T: ToString + Copy>(
trigger: T,
handler: impl FnOnce(web_sys::PointerEvent) -> Ms + 'static + Clone,
) -> Listener<Ms> {
let closure = move |event: web_sys::Event| {
(handler.clone())(event.dyn_ref::<web_sys::PointerEvent>().unwrap().clone())
};
Listener::new(
&trigger.to_string(),
Some(Box::new(closure)),
Some(Category::Pointer),
None,
)
}
pub fn raw_ev<Ms, T: ToString + Copy>(
trigger: T,
handler: impl FnOnce(web_sys::Event) -> Ms + 'static + Clone,
) -> Listener<Ms> {
let closure = move |event: web_sys::Event| (handler.clone())(event);
Listener::new(
&trigger.to_string(),
Some(Box::new(closure)),
Some(Category::Raw),
None,
)
}
pub fn simple_ev<Ms: Clone, T>(trigger: T, message: Ms) -> Listener<Ms>
where
Ms: 'static,
T: ToString + Copy,
{
let msg_closure = message.clone();
let handler = || msg_closure;
let closure = move |_| handler.clone()();
Listener::new(
&trigger.to_string(),
Some(Box::new(closure)),
Some(Category::Simple),
Some(message),
)
}
#[deprecated]
pub fn trigger_update_ev<Ms>(
handler: impl FnOnce(web_sys::CustomEvent) -> Ms + 'static + Clone,
) -> Listener<Ms> {
let closure = move |event: web_sys::Event| {
(handler.clone())(event.dyn_ref::<web_sys::CustomEvent>().unwrap().clone())
};
Listener::new(
UPDATE_TRIGGER_EVENT_ID,
Some(Box::new(closure)),
Some(Category::Custom),
None,
)
}
pub(crate) fn fmt_hook_fn<T>(h: &Option<T>) -> &'static str {
match h {
Some(_) => "Some(.. a dynamic handler ..)",
None => "None",
}
}
#[deprecated]
pub fn trigger_update_handler<Ms: DeserializeOwned>() -> Listener<Ms> {
trigger_update_ev(|ev| {
ev.detail()
.into_serde()
.expect("trigger_update_handler: Deserialization failed!")
})
}