use crate::event::{MouseButton, MouseScrollDelta};
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
use dpi::{LogicalPosition, PhysicalPosition, Position};
use smol_str::SmolStr;
use std::cell::OnceCell;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent};
use super::Engine;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ButtonsState: u16 {
const LEFT = 0b00001;
const RIGHT = 0b00010;
const MIDDLE = 0b00100;
const BACK = 0b01000;
const FORWARD = 0b10000;
}
}
impl From<ButtonsState> for MouseButton {
fn from(value: ButtonsState) -> Self {
match value {
ButtonsState::LEFT => MouseButton::Left,
ButtonsState::RIGHT => MouseButton::Right,
ButtonsState::MIDDLE => MouseButton::Middle,
ButtonsState::BACK => MouseButton::Back,
ButtonsState::FORWARD => MouseButton::Forward,
_ => MouseButton::Other(value.bits()),
}
}
}
impl From<MouseButton> for ButtonsState {
fn from(value: MouseButton) -> Self {
match value {
MouseButton::Left => ButtonsState::LEFT,
MouseButton::Right => ButtonsState::RIGHT,
MouseButton::Middle => ButtonsState::MIDDLE,
MouseButton::Back => ButtonsState::BACK,
MouseButton::Forward => ButtonsState::FORWARD,
MouseButton::Other(value) => ButtonsState::from_bits_retain(value),
}
}
}
pub fn mouse_buttons(event: &MouseEvent) -> ButtonsState {
ButtonsState::from_bits_retain(event.buttons())
}
pub fn mouse_button(event: &MouseEvent) -> Option<MouseButton> {
match event.button() {
-1 => None,
0 => Some(MouseButton::Left),
1 => Some(MouseButton::Middle),
2 => Some(MouseButton::Right),
3 => Some(MouseButton::Back),
4 => Some(MouseButton::Forward),
i => {
Some(MouseButton::Other(i.try_into().expect("unexpected negative mouse button value")))
},
}
}
impl MouseButton {
pub fn to_id(self) -> u32 {
match self {
MouseButton::Left => 0,
MouseButton::Right => 1,
MouseButton::Middle => 2,
MouseButton::Back => 3,
MouseButton::Forward => 4,
MouseButton::Other(value) => value.into(),
}
}
}
pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
#[wasm_bindgen]
extern "C" {
type MouseEventExt;
#[wasm_bindgen(method, getter, js_name = offsetX)]
fn offset_x(this: &MouseEventExt) -> f64;
#[wasm_bindgen(method, getter, js_name = offsetY)]
fn offset_y(this: &MouseEventExt) -> f64;
}
let event: &MouseEventExt = event.unchecked_ref();
LogicalPosition { x: event.offset_x(), y: event.offset_y() }
}
pub enum MouseDelta {
Chromium,
Gecko { old_position: LogicalPosition<f64>, old_delta: LogicalPosition<f64> },
Other,
}
impl MouseDelta {
pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self {
match super::engine(window) {
Some(Engine::Chromium) => Self::Chromium,
Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko {
old_position: mouse_position(event),
old_delta: LogicalPosition::new(
event.movement_x() as f64,
event.movement_y() as f64,
),
},
_ => Self::Other,
}
}
pub fn delta(&mut self, event: &MouseEvent) -> Position {
match self {
MouseDelta::Chromium => {
PhysicalPosition::new(event.movement_x(), event.movement_y()).into()
},
MouseDelta::Gecko { old_position, old_delta } => {
let new_position = mouse_position(event);
let x = new_position.x - old_position.x + old_delta.x;
let y = new_position.y - old_position.y + old_delta.y;
*old_position = new_position;
*old_delta = LogicalPosition::new(0., 0.);
LogicalPosition::new(x, y).into()
},
MouseDelta::Other => {
LogicalPosition::new(event.movement_x(), event.movement_y()).into()
},
}
}
}
pub fn mouse_scroll_delta(
window: &web_sys::Window,
event: &WheelEvent,
) -> Option<MouseScrollDelta> {
let x = -event.delta_x();
let y = -event.delta_y();
match event.delta_mode() {
WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)),
WheelEvent::DOM_DELTA_PIXEL => {
let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor(window));
Some(MouseScrollDelta::PixelDelta(delta))
},
_ => None,
}
}
pub fn key_code(event: &KeyboardEvent) -> PhysicalKey {
let code = event.code();
PhysicalKey::from_key_code_attribute_value(&code)
}
pub fn key(event: &KeyboardEvent) -> Key {
Key::from_key_attribute_value(&event.key())
}
pub fn key_text(event: &KeyboardEvent) -> Option<SmolStr> {
let key = event.key();
let key = Key::from_key_attribute_value(&key);
match &key {
Key::Character(text) => Some(text.clone()),
Key::Named(NamedKey::Tab) => Some(SmolStr::new("\t")),
Key::Named(NamedKey::Enter) => Some(SmolStr::new("\r")),
Key::Named(NamedKey::Space) => Some(SmolStr::new(" ")),
_ => None,
}
.map(SmolStr::new)
}
pub fn key_location(event: &KeyboardEvent) -> KeyLocation {
match event.location() {
KeyboardEvent::DOM_KEY_LOCATION_LEFT => KeyLocation::Left,
KeyboardEvent::DOM_KEY_LOCATION_RIGHT => KeyLocation::Right,
KeyboardEvent::DOM_KEY_LOCATION_NUMPAD => KeyLocation::Numpad,
KeyboardEvent::DOM_KEY_LOCATION_STANDARD => KeyLocation::Standard,
location => {
tracing::warn!("Unexpected key location: {location}");
KeyLocation::Standard
},
}
}
pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState {
let mut state = ModifiersState::empty();
if event.shift_key() {
state |= ModifiersState::SHIFT;
}
if event.ctrl_key() {
state |= ModifiersState::CONTROL;
}
if event.alt_key() {
state |= ModifiersState::ALT;
}
if event.meta_key() {
state |= ModifiersState::SUPER;
}
state
}
pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState {
let mut state = ModifiersState::empty();
if event.shift_key() {
state |= ModifiersState::SHIFT;
}
if event.ctrl_key() {
state |= ModifiersState::CONTROL;
}
if event.alt_key() {
state |= ModifiersState::ALT;
}
if event.meta_key() {
state |= ModifiersState::SUPER;
}
state
}
pub fn pointer_move_event(event: PointerEvent) -> impl Iterator<Item = PointerEvent> {
if has_coalesced_events_support(&event) {
None.into_iter().chain(
Some(event.get_coalesced_events().into_iter().map(PointerEvent::unchecked_from_js))
.into_iter()
.flatten(),
)
} else {
Some(event).into_iter().chain(None.into_iter().flatten())
}
}
pub fn has_coalesced_events_support(event: &PointerEvent) -> bool {
thread_local! {
static COALESCED_EVENTS_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
}
COALESCED_EVENTS_SUPPORT.with(|support| {
*support.get_or_init(|| {
#[wasm_bindgen]
extern "C" {
type PointerCoalescedEventsSupport;
#[wasm_bindgen(method, getter, js_name = getCoalescedEvents)]
fn has_get_coalesced_events(this: &PointerCoalescedEventsSupport) -> JsValue;
}
let support: &PointerCoalescedEventsSupport = event.unchecked_ref();
!support.has_get_coalesced_events().is_undefined()
})
})
}