winit/src/platform_impl/web/web_sys/event.rs
Kirill Chibisov c8c1eca3c7 api: move primary from FingerId to Pointer events
Whether the pointer event is primary or not generally matters for the
context where all input is done by the same event, so users can
_ignore_ non-primary events since they are likely from users clicking
something else with their other fingers.

Having it only on a FingerId made it useless, since it's usually used
to avoid multi-touch, but if you start mapping on touch event you
already can track things like that yourself.

Fixes #3943.

Co-authored-by: daxpedda <daxpedda@gmail.com>
2024-11-02 17:10:32 +03:00

279 lines
9 KiB
Rust

use std::cell::OnceCell;
use dpi::{LogicalPosition, PhysicalPosition, Position};
use smol_str::SmolStr;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{KeyboardEvent, MouseEvent, Navigator, PointerEvent, WheelEvent};
use super::super::FingerId;
use super::Engine;
use crate::event::{MouseButton, MouseScrollDelta, PointerKind};
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
bitflags::bitflags! {
// https://www.w3.org/TR/pointerevents3/#the-buttons-property
#[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> {
// https://www.w3.org/TR/pointerevents3/#the-button-property
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) -> u16 {
match self {
MouseButton::Left => 0,
MouseButton::Right => 1,
MouseButton::Middle => 2,
MouseButton::Back => 3,
MouseButton::Forward => 4,
MouseButton::Other(value) => value,
}
}
}
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() }
}
// TODO: Remove this when Firefox supports correct movement values in coalesced events and browsers
// have agreed on what coordinate space `movementX/Y` is using.
// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1753724>.
// See <https://github.com/w3c/pointerlock/issues/42>.
pub enum MouseDelta {
Chromium,
Gecko { old_position: LogicalPosition<f64>, old_delta: LogicalPosition<f64> },
Other,
}
impl MouseDelta {
pub fn init(navigator: &Navigator, event: &PointerEvent) -> Self {
match super::engine(navigator) {
Some(Engine::Chromium) => Self::Chromium,
// Firefox has wrong movement values in coalesced events.
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 pointer_type(event: &PointerEvent, pointer_id: i32) -> PointerKind {
match event.pointer_type().as_str() {
"mouse" => PointerKind::Mouse,
"touch" => PointerKind::Touch(FingerId::new(pointer_id).into()),
_ => PointerKind::Unknown,
}
}
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> {
// make a single iterator depending on the availability of coalesced events
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())
}
}
// TODO: Remove when Safari supports `getCoalescedEvents`.
// See <https://bugs.webkit.org/show_bug.cgi?id=210454>.
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()
})
})
}