use super::WebElement;
use std::io;
use wasm_bindgen::{JsCast, JsValue, closure::Closure};
use web_sys::{Element, Event, MouseEvent, PointerEvent, WheelEvent};
pub struct WebEvent {
pub(super) event: Event,
}
impl WebEvent {
#[must_use]
pub fn is_trusted(&self) -> bool {
self.event.is_trusted()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum PointerType {
Mouse,
Pen,
Touch,
Other,
}
impl PointerType {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Mouse => "mouse",
Self::Pen => "pen",
Self::Touch => "touch",
Self::Other => "other",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum WheelDeltaMode {
Pixel,
Line,
Page,
Other,
}
impl WheelDeltaMode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Pixel => "pixel",
Self::Line => "line",
Self::Page => "page",
Self::Other => "other",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PointerModifiers {
pub(super) ctrl: bool,
pub(super) shift: bool,
pub(super) alt: bool,
pub(super) meta: bool,
}
impl PointerModifiers {
#[must_use]
pub const fn ctrl(self) -> bool {
self.ctrl
}
#[must_use]
pub const fn shift(self) -> bool {
self.shift
}
#[must_use]
pub const fn alt(self) -> bool {
self.alt
}
#[must_use]
pub const fn meta(self) -> bool {
self.meta
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PointerMetadata {
pointer_id: i32,
pointer_type: PointerType,
client_x: f64,
client_y: f64,
offset_x: i32,
offset_y: i32,
button: i16,
buttons: u16,
modifiers: PointerModifiers,
primary: bool,
trusted: bool,
}
impl PointerMetadata {
#[must_use]
pub const fn pointer_id(self) -> i32 {
self.pointer_id
}
#[must_use]
pub const fn pointer_type(self) -> PointerType {
self.pointer_type
}
#[must_use]
pub const fn client_x(self) -> f64 {
self.client_x
}
#[must_use]
pub const fn client_y(self) -> f64 {
self.client_y
}
#[must_use]
pub const fn offset_x(self) -> i32 {
self.offset_x
}
#[must_use]
pub const fn offset_y(self) -> i32 {
self.offset_y
}
#[must_use]
pub const fn button(self) -> i16 {
self.button
}
#[must_use]
pub const fn buttons(self) -> u16 {
self.buttons
}
#[must_use]
pub const fn modifiers(self) -> PointerModifiers {
self.modifiers
}
#[must_use]
pub const fn is_primary(self) -> bool {
self.primary
}
#[must_use]
pub const fn is_trusted(self) -> bool {
self.trusted
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WheelMetadata {
delta_x: f64,
delta_y: f64,
delta_z: f64,
delta_mode: WheelDeltaMode,
client_x: f64,
client_y: f64,
offset_x: i32,
offset_y: i32,
modifiers: PointerModifiers,
trusted: bool,
}
impl WheelMetadata {
#[must_use]
pub const fn delta_x(self) -> f64 {
self.delta_x
}
#[must_use]
pub const fn delta_y(self) -> f64 {
self.delta_y
}
#[must_use]
pub const fn delta_z(self) -> f64 {
self.delta_z
}
#[must_use]
pub const fn delta_mode(self) -> WheelDeltaMode {
self.delta_mode
}
#[must_use]
pub const fn client_x(self) -> f64 {
self.client_x
}
#[must_use]
pub const fn client_y(self) -> f64 {
self.client_y
}
#[must_use]
pub const fn offset_x(self) -> i32 {
self.offset_x
}
#[must_use]
pub const fn offset_y(self) -> i32 {
self.offset_y
}
#[must_use]
pub const fn modifiers(self) -> PointerModifiers {
self.modifiers
}
#[must_use]
pub const fn is_trusted(self) -> bool {
self.trusted
}
}
fn modifier_state(event: &MouseEvent) -> PointerModifiers {
PointerModifiers {
ctrl: MouseEvent::ctrl_key(event),
shift: MouseEvent::shift_key(event),
alt: MouseEvent::alt_key(event),
meta: MouseEvent::meta_key(event),
}
}
impl WebEvent {
#[must_use]
pub fn target(&self) -> Option<WebElement> {
self.event
.target()
.and_then(|target| target.dyn_into::<Element>().ok())
.map(|element| WebElement { element })
}
#[must_use]
pub fn value(&self) -> Option<String> {
self.target().and_then(|target| target.value())
}
#[must_use]
pub fn pointer_id(&self) -> Option<i32> {
self.event
.dyn_ref::<PointerEvent>()
.map(PointerEvent::pointer_id)
}
#[must_use]
pub fn pointer_metadata(&self) -> Option<PointerMetadata> {
let pointer = self.event.dyn_ref::<PointerEvent>()?;
let mouse = self.event.dyn_ref::<MouseEvent>()?;
let pointer_type = match PointerEvent::pointer_type(pointer).as_str() {
"mouse" => PointerType::Mouse,
"pen" => PointerType::Pen,
"touch" => PointerType::Touch,
_ => PointerType::Other,
};
Some(PointerMetadata {
pointer_id: PointerEvent::pointer_id(pointer),
pointer_type,
client_x: event_coordinate(mouse, "clientX")?,
client_y: event_coordinate(mouse, "clientY")?,
offset_x: MouseEvent::offset_x(mouse),
offset_y: MouseEvent::offset_y(mouse),
button: MouseEvent::button(mouse),
buttons: MouseEvent::buttons(mouse),
modifiers: modifier_state(mouse),
primary: PointerEvent::is_primary(pointer),
trusted: self.is_trusted(),
})
}
#[must_use]
pub fn wheel_metadata(&self) -> Option<WheelMetadata> {
let wheel = self.event.dyn_ref::<WheelEvent>()?;
let mouse = self.event.dyn_ref::<MouseEvent>()?;
let delta_mode = match WheelEvent::delta_mode(wheel) {
0 => WheelDeltaMode::Pixel,
1 => WheelDeltaMode::Line,
2 => WheelDeltaMode::Page,
_ => WheelDeltaMode::Other,
};
Some(WheelMetadata {
delta_x: WheelEvent::delta_x(wheel),
delta_y: WheelEvent::delta_y(wheel),
delta_z: WheelEvent::delta_z(wheel),
delta_mode,
client_x: event_coordinate(mouse, "clientX")?,
client_y: event_coordinate(mouse, "clientY")?,
offset_x: MouseEvent::offset_x(mouse),
offset_y: MouseEvent::offset_y(mouse),
modifiers: modifier_state(mouse),
trusted: self.is_trusted(),
})
}
pub fn prevent_default(&self) {
self.event.prevent_default();
}
}
fn event_coordinate(event: &MouseEvent, property: &str) -> Option<f64> {
let value = js_sys::Reflect::get(event.as_ref(), &JsValue::from_str(property)).ok()?;
let coordinate = value.as_f64()?;
coordinate.is_finite().then_some(coordinate)
}
pub struct WebEventListener {
target: web_sys::EventTarget,
event_name: String,
closure: Closure<dyn FnMut(Event)>,
}
pub(super) fn listen<F>(
target: web_sys::EventTarget,
event_name: &str,
mut callback: F,
) -> io::Result<WebEventListener>
where
F: FnMut(WebEvent) + 'static,
{
let closure = Closure::wrap(
Box::new(move |event: Event| callback(WebEvent { event })) as Box<dyn FnMut(Event)>
);
target
.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())
.map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Browser rejected event listener registration",
)
})?;
Ok(WebEventListener {
target,
event_name: event_name.to_owned(),
closure,
})
}
impl Drop for WebEventListener {
fn drop(&mut self) {
let _ = self.target.remove_event_listener_with_callback(
&self.event_name,
self.closure.as_ref().unchecked_ref(),
);
}
}