use std::fmt::Formatter;
use web_sys::{
wasm_bindgen::{convert::FromWasmAbi, prelude::Closure, JsCast},
Element, EventTarget,
};
use crate::{
error::Error,
event::{MouseButton, MouseEvent, MouseEventKind},
};
pub(super) struct EventCallback<T: 'static> {
event_types: &'static [&'static str],
target: EventTarget,
#[allow(dead_code)]
closure: Closure<dyn FnMut(T)>,
}
impl<T: 'static> EventCallback<T> {
pub fn new<F>(
target: impl Into<EventTarget>,
event_types: &'static [&'static str],
callback: F,
) -> Result<Self, Error>
where
F: FnMut(T) + 'static,
T: JsCast + FromWasmAbi,
{
let target = target.into();
let closure = Closure::<dyn FnMut(T)>::new(callback);
for event_type in event_types {
target
.add_event_listener_with_callback(event_type, closure.as_ref().unchecked_ref())?;
}
Ok(Self {
event_types,
target,
closure,
})
}
}
impl<T: 'static> Drop for EventCallback<T> {
fn drop(&mut self) {
for event_type in self.event_types {
let _ = self.target.remove_event_listener_with_callback(
event_type,
self.closure.as_ref().unchecked_ref(),
);
}
}
}
impl<T: 'static> std::fmt::Debug for EventCallback<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EventCallback")
.field("event_types", &self.event_types)
.field("target", &self.target)
.finish()
}
}
#[derive(Debug, Clone)]
pub(super) struct MouseConfig {
pub grid_width: u16,
pub grid_height: u16,
pub offset: Option<f64>,
pub cell_dimensions: Option<(f64, f64)>,
}
impl MouseConfig {
pub fn new(grid_width: u16, grid_height: u16) -> Self {
Self {
grid_width,
grid_height,
offset: None,
cell_dimensions: None,
}
}
pub fn with_offset(mut self, offset: f64) -> Self {
self.offset = Some(offset);
self
}
pub fn with_cell_dimensions(mut self, width: f64, height: f64) -> Self {
self.cell_dimensions = Some((width, height));
self
}
}
pub(super) const KEY_EVENT_TYPES: &[&str] = &["keydown"];
pub(super) const MOUSE_EVENT_TYPES: &[&str] = &[
"mousemove",
"mousedown",
"mouseup",
"click",
"dblclick",
"mouseenter",
"mouseleave",
];
fn mouse_to_grid_coords(
event: &web_sys::MouseEvent,
element: &Element,
config: &MouseConfig,
) -> (u16, u16) {
let rect = element.get_bounding_client_rect();
let offset = config.offset.unwrap_or(0.0);
let relative_x = (event.client_x() as f64 - rect.left() - offset).max(0.0);
let relative_y = (event.client_y() as f64 - rect.top() - offset).max(0.0);
let (drawable_width, drawable_height) = match config.cell_dimensions {
Some((cw, ch)) => (
config.grid_width as f64 * cw,
config.grid_height as f64 * ch,
),
None => (rect.width() - 2.0 * offset, rect.height() - 2.0 * offset),
};
if drawable_width <= 0.0 || drawable_height <= 0.0 {
return (0, 0);
}
let col = ((relative_x / drawable_width) * config.grid_width as f64) as u16;
let row = ((relative_y / drawable_height) * config.grid_height as f64) as u16;
(
col.min(config.grid_width.saturating_sub(1)),
row.min(config.grid_height.saturating_sub(1)),
)
}
fn event_type_to_kind(event_type: &str, button: MouseButton) -> MouseEventKind {
match event_type {
"mousemove" => MouseEventKind::Moved,
"mousedown" => MouseEventKind::ButtonDown(button),
"mouseup" => MouseEventKind::ButtonUp(button),
"click" => MouseEventKind::SingleClick(button),
"dblclick" => MouseEventKind::DoubleClick(button),
"mouseenter" => MouseEventKind::Entered,
"mouseleave" => MouseEventKind::Exited,
_ => MouseEventKind::Unidentified,
}
}
pub(super) fn create_mouse_event(
event: &web_sys::MouseEvent,
element: &Element,
config: &MouseConfig,
) -> MouseEvent {
let (col, row) = mouse_to_grid_coords(event, element, config);
let button: MouseButton = event.button().into();
let event_type = event.type_();
MouseEvent {
kind: event_type_to_kind(&event_type, button),
col,
row,
ctrl: event.ctrl_key(),
alt: event.alt_key(),
shift: event.shift_key(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mouse_config_builder() {
let config = MouseConfig::new(80, 24)
.with_offset(5.0)
.with_cell_dimensions(10.0, 19.0);
assert_eq!(config.grid_width, 80);
assert_eq!(config.grid_height, 24);
assert_eq!(config.offset, Some(5.0));
assert_eq!(config.cell_dimensions, Some((10.0, 19.0)));
}
}