diff --git a/clippy.toml b/clippy.toml index 84bf0d5e01..fdb6c7b57e 100644 --- a/clippy.toml +++ b/clippy.toml @@ -10,6 +10,9 @@ disallowed-methods = [ { path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" }, + { path = "web_sys::PointerEvent::pointer_type", reason = "Use `WebPointerType` to emit warnings" }, + { path = "web_sys::MouseEvent::button", reason = "Use `backend::event::cursor_button()` to avoid wrong conversions" }, + { path = "web_sys::MouseEvent::buttons", reason = "Use `backend::event::cursor_buttons()` to avoid wrong conversions" }, { path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." }, { path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" }, ] diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 34445b05d2..32d9d27131 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -45,6 +45,7 @@ changelog entry. - Add `ActiveEventLoop::create_proxy()`. - On Web, implement `Error` for `platform::web::CustomCursorError`. - Add `WindowEvent::CursorMoved::type` with a new type `CursorType` introducing pen/stylus support. + Currently only implemented on Web. ### Changed @@ -91,3 +92,4 @@ changelog entry. ### Fixed - On MacOS, fix building with `feature = "rwh_04"`. +- On Web, device events are emitted regardless of cursor type. diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 317ff077fa..43bc32a1cf 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -11,8 +11,9 @@ use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelE use web_time::{Duration, Instant}; use super::super::main_thread::MainThreadMarker; +use super::super::web_sys::pointer::{PointerEventExt, WebPointerType}; use super::super::DeviceId; -use super::backend; +use super::backend::{self, ButtonsState, EventListenerHandle}; use super::state::State; use crate::dpi::PhysicalSize; use crate::event::{ @@ -21,7 +22,6 @@ use crate::event::{ }; use crate::event_loop::{ControlFlow, DeviceEvents}; use crate::platform::web::{PollStrategy, WaitUntilStrategy}; -use crate::platform_impl::platform::backend::EventListenerHandle; use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner}; use crate::platform_impl::platform::window::Inner; use crate::window::WindowId; @@ -58,7 +58,7 @@ pub struct Execution { destroy_pending: RefCell>, page_transition_event_handle: RefCell>, device_events: Cell, - on_mouse_move: OnEventHandle, + on_mouse_move: OnEventHandle, on_wheel: OnEventHandle, on_mouse_press: OnEventHandle, on_mouse_release: OnEventHandle, @@ -239,27 +239,18 @@ impl Shared { *self.0.on_mouse_move.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "pointermove", - Closure::new(move |event: PointerEvent| { + Closure::new(move |event: PointerEventExt| { if !runner.device_events() { return; } - let pointer_type = event.pointer_type(); - - if pointer_type != "mouse" { - return; - } - // chorded button event let device_id = RootDeviceId(DeviceId(event.pointer_id())); - if let Some(button) = backend::event::mouse_button(&event) { - debug_assert_eq!( - pointer_type, "mouse", - "expect pointer type of a chorded button event to be a mouse" - ); - - let state = if backend::event::mouse_buttons(&event).contains(button.into()) { + if let Some(button) = backend::event::raw_button(&event) { + let state = if backend::event::cursor_buttons(&event) + .contains(ButtonsState::from_bits_retain(button)) + { ElementState::Pressed } else { ElementState::Released @@ -267,7 +258,7 @@ impl Shared { runner.send_event(Event::DeviceEvent { device_id, - event: DeviceEvent::Button { button: button.to_id(), state }, + event: DeviceEvent::Button { button: button.into(), state }, }); return; @@ -288,10 +279,13 @@ impl Shared { event: DeviceEvent::Motion { axis: 1, value: delta.y }, }); - x_motion.into_iter().chain(y_motion).chain(iter::once(Event::DeviceEvent { - device_id, - event: DeviceEvent::MouseMotion { delta: (delta.x, delta.y) }, - })) + x_motion.into_iter().chain(y_motion).chain( + matches!(WebPointerType::from_event(&event), Some(WebPointerType::Mouse)) + .then_some(Event::DeviceEvent { + device_id, + event: DeviceEvent::MouseMotion { delta: (delta.x, delta.y) }, + }), + ) })); }), )); @@ -322,15 +316,11 @@ impl Shared { return; } - if event.pointer_type() != "mouse" { - return; - } - - let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); + let button = backend::event::raw_button(&event).expect("no pointer button pressed"); runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(event.pointer_id())), event: DeviceEvent::Button { - button: button.to_id(), + button: button.into(), state: ElementState::Pressed, }, }); @@ -345,15 +335,11 @@ impl Shared { return; } - if event.pointer_type() != "mouse" { - return; - } - - let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); + let button = backend::event::raw_button(&event).expect("no pointer button pressed"); runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(event.pointer_id())), event: DeviceEvent::Button { - button: button.to_id(), + button: button.into(), state: ElementState::Released, }, }); diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index e8ab4f6638..41cdee4539 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -14,8 +14,7 @@ use super::runner::{EventWrapper, Execution}; use super::window::WindowId; use super::{backend, runner, EventLoopProxy}; use crate::event::{ - CursorType, DeviceId as RootDeviceId, ElementState, Event, KeyEvent, Touch, TouchPhase, - WindowEvent, + DeviceId as RootDeviceId, ElementState, Event, KeyEvent, Touch, TouchPhase, WindowEvent, }; use crate::event_loop::{ControlFlow, DeviceEvents}; use crate::keyboard::ModifiersState; @@ -297,18 +296,16 @@ impl ActiveEventLoop { } }); - runner.send_events(modifiers.into_iter().chain(events.flat_map(|position| { - let device_id = RootDeviceId(DeviceId(pointer_id)); + runner.send_events(modifiers.into_iter().chain(events.flat_map( + |(position, r#type)| { + let device_id = RootDeviceId(DeviceId(pointer_id)); - iter::once(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { - device_id, - position, - r#type: CursorType::Mouse, - }, - }) - }))); + iter::once(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorMoved { device_id, position, r#type }, + }) + }, + ))); } }, { @@ -348,6 +345,7 @@ impl ActiveEventLoop { move |active_modifiers, pointer_id, position: crate::dpi::PhysicalPosition, + r#type, buttons, button| { let modifiers = @@ -373,11 +371,7 @@ impl ActiveEventLoop { runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { - device_id, - position, - r#type: CursorType::Mouse, - }, + event: WindowEvent::CursorMoved { device_id, position, r#type }, }, Event::WindowEvent { window_id: RootWindowId(id), @@ -407,7 +401,7 @@ impl ActiveEventLoop { let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); - move |active_modifiers, pointer_id, position, button| { + move |active_modifiers, pointer_id, position, r#type, button| { let modifiers = (modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { @@ -424,18 +418,14 @@ impl ActiveEventLoop { runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { - device_id, - position, - r#type: CursorType::Mouse, - }, + event: WindowEvent::CursorMoved { device_id, position, r#type }, }, Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorInput { device_id, state: ElementState::Pressed, - button: button.into(), + button, }, }, ])); @@ -491,7 +481,7 @@ impl ActiveEventLoop { let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); - move |active_modifiers, pointer_id, position, button| { + move |active_modifiers, pointer_id, position, r#type, button| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); @@ -509,18 +499,14 @@ impl ActiveEventLoop { runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { - device_id, - position, - r#type: CursorType::Mouse, - }, + event: WindowEvent::CursorMoved { device_id, position, r#type }, }, Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorInput { device_id, state: ElementState::Released, - button: button.into(), + button, }, }, ])); diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 5c070627a9..036ae6db11 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -22,7 +22,7 @@ use super::pointer::PointerHandler; use super::{event, fullscreen, ButtonsState, ResizeScaleHandle}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; -use crate::event::{CursorButton, Force, InnerSizeWriter, MouseButton, MouseScrollDelta}; +use crate::event::{CursorButton, CursorType, Force, InnerSizeWriter, MouseScrollDelta}; use crate::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey}; use crate::platform_impl::OsError; use crate::window::{WindowAttributes, WindowId as RootWindowId}; @@ -328,60 +328,73 @@ impl Canvas { self.pointer_handler.on_cursor_enter(&self.common, handler) } - pub fn on_mouse_release( + pub fn on_mouse_release( &mut self, modifier_handler: MOD, - mouse_handler: M, + cursor_handler: C, touch_handler: T, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), + C: 'static + FnMut(ModifiersState, i32, PhysicalPosition, CursorType, CursorButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { self.pointer_handler.on_mouse_release( &self.common, modifier_handler, - mouse_handler, + cursor_handler, touch_handler, ) } - pub fn on_mouse_press( + pub fn on_mouse_press( &mut self, modifier_handler: MOD, - mouse_handler: M, + cursor_handler: C, touch_handler: T, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), + C: 'static + FnMut(ModifiersState, i32, PhysicalPosition, CursorType, CursorButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { self.pointer_handler.on_mouse_press( &self.common, modifier_handler, - mouse_handler, + cursor_handler, touch_handler, Rc::clone(&self.prevent_default), ) } - pub fn on_cursor_move( + pub fn on_cursor_move( &mut self, modifier_handler: MOD, - mouse_handler: M, + cursor_handler: C, touch_handler: T, button_handler: B, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), + C: 'static + + FnMut( + ModifiersState, + i32, + &mut dyn Iterator, CursorType)>, + ), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), - B: 'static + FnMut(ModifiersState, i32, PhysicalPosition, ButtonsState, CursorButton), + B: 'static + + FnMut( + ModifiersState, + i32, + PhysicalPosition, + CursorType, + ButtonsState, + CursorButton, + ), { self.pointer_handler.on_cursor_move( &self.common, modifier_handler, - mouse_handler, + cursor_handler, touch_handler, button_handler, Rc::clone(&self.prevent_default), diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 68aaee0aa9..ae92e48af4 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -1,4 +1,5 @@ use std::cell::OnceCell; +use std::f64; use dpi::{LogicalPosition, PhysicalPosition, Position}; use smol_str::SmolStr; @@ -6,8 +7,12 @@ use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent}; +use super::pointer::{PointerEventExt, WebPointerType}; use super::Engine; -use crate::event::{CursorButton, MouseButton, MouseScrollDelta, ToolButton}; +use crate::event::{ + CursorButton, CursorType, Force, MouseButton, MouseScrollDelta, ToolAngle, ToolButton, + ToolState, ToolTilt, +}; use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey}; bitflags::bitflags! { @@ -67,22 +72,88 @@ impl From for ButtonsState { } } -pub fn mouse_buttons(event: &MouseEvent) -> ButtonsState { +pub fn cursor_buttons(event: &MouseEvent) -> ButtonsState { + #[allow(clippy::disallowed_methods)] ButtonsState::from_bits_retain(event.buttons()) } -pub fn mouse_button(event: &MouseEvent) -> Option { +pub fn raw_button(event: &MouseEvent) -> Option { // 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"))) + #[allow(clippy::disallowed_methods)] + let button = event.button(); + + if button == -1 { + None + } else { + Some(button.try_into().expect("unexpected negative mouse button value")) + } +} + +pub fn cursor_button(event: &PointerEventExt, r#type: WebPointerType) -> Option { + // https://www.w3.org/TR/pointerevents3/#the-button-property + + let button = raw_button(event)?; + + let button = match r#type { + WebPointerType::Mouse => { + let button = match button { + 0 => MouseButton::Left, + 1 => MouseButton::Middle, + 2 => MouseButton::Right, + 3 => MouseButton::Back, + 4 => MouseButton::Forward, + button => MouseButton::Other(button), + }; + + CursorButton::Mouse(button) + }, + WebPointerType::Pen => { + if button == 5 { + return Some(CursorButton::Eraser(ToolButton::Contact)); + }; + + let button = match button { + 0 => ToolButton::Contact, + 2 => ToolButton::Barrel, + button => ToolButton::Other(button), + }; + + CursorButton::Pen(button) + }, + WebPointerType::Touch => unreachable!(), + }; + + Some(button) +} + +pub fn cursor_type( + event: &PointerEventExt, + r#type: WebPointerType, + button: Option<&CursorButton>, +) -> CursorType { + match r#type { + WebPointerType::Mouse => CursorType::Mouse, + WebPointerType::Pen => { + let force = Force::Normalized(event.pressure().into()); + let tangential_force = Some(event.tangential_pressure()); + let twist = Some(event.twist().try_into().expect("found invalid `twist`")); + let tilt = Some(ToolTilt { + x: event.tilt_x().try_into().expect("found invalid `tiltX`"), + y: event.tilt_y().try_into().expect("found invalid `tiltY`"), + }); + let angle = event + .altitude_angle() + .map(|altitude| ToolAngle { altitude, azimuth: event.azimuth_angle() }); + + let state = ToolState { force, tangential_force, twist, tilt, angle }; + + match button { + Some(CursorButton::Eraser(_)) => CursorType::Eraser(state), + Some(CursorButton::Pen(_)) | None => CursorType::Pen(state), + _ => unreachable!(), + } }, + WebPointerType::Touch => unreachable!(), } } @@ -99,7 +170,7 @@ impl MouseButton { } } -pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { +pub fn cursor_position(event: &MouseEvent) -> LogicalPosition { #[wasm_bindgen] extern "C" { type MouseEventExt; @@ -132,7 +203,7 @@ impl MouseDelta { 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_position: cursor_position(event), old_delta: LogicalPosition::new( event.movement_x() as f64, event.movement_y() as f64, @@ -148,7 +219,7 @@ impl MouseDelta { PhysicalPosition::new(event.movement_x(), event.movement_y()).into() }, MouseDelta::Gecko { old_position, old_delta } => { - let new_position = mouse_position(event); + let new_position = cursor_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; @@ -252,11 +323,11 @@ pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState { state } -pub fn pointer_move_event(event: PointerEvent) -> impl Iterator { +pub fn pointer_move_event(event: PointerEventExt) -> impl Iterator { // 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)) + Some(event.get_coalesced_events().into_iter().map(PointerEventExt::unchecked_from_js)) .into_iter() .flatten(), ) diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 97019666b6..77f56a219c 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -5,7 +5,7 @@ mod event_handle; mod fullscreen; mod intersection_handle; mod media_query_handle; -mod pointer; +pub mod pointer; mod resize_scaling; mod schedule; diff --git a/src/platform_impl/web/web_sys/pointer.rs b/src/platform_impl/web/web_sys/pointer.rs index 002a6efeb5..4c127e8d39 100644 --- a/src/platform_impl/web/web_sys/pointer.rs +++ b/src/platform_impl/web/web_sys/pointer.rs @@ -2,22 +2,24 @@ use std::cell::Cell; use std::rc::Rc; use event::ButtonsState; -use web_sys::PointerEvent; +use tracing::warn; +use wasm_bindgen::prelude::wasm_bindgen; +use web_sys::{Event, MouseEvent, PointerEvent}; use super::canvas::Common; use super::event; use super::event_handle::EventListenerHandle; use crate::dpi::PhysicalPosition; -use crate::event::{CursorButton, Force, MouseButton}; +use crate::event::{CursorButton, CursorType, Force}; use crate::keyboard::ModifiersState; #[allow(dead_code)] pub(super) struct PointerHandler { on_cursor_leave: Option>, on_cursor_enter: Option>, - on_cursor_move: Option>, - on_pointer_press: Option>, - on_pointer_release: Option>, + on_cursor_move: Option>, + on_pointer_press: Option>, + on_pointer_release: Option>, on_touch_cancel: Option>, } @@ -44,7 +46,9 @@ impl PointerHandler { // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. - let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id()); + let pointer_id = + matches!(WebPointerType::from_event(&event), Some(WebPointerType::Mouse)) + .then(|| event.pointer_id()); handler(modifiers, pointer_id); })); @@ -61,62 +65,73 @@ impl PointerHandler { // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. - let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id()); + let pointer_id = + matches!(WebPointerType::from_event(&event), Some(WebPointerType::Mouse)) + .then(|| event.pointer_id()); handler(modifiers, pointer_id); })); } - pub fn on_mouse_release( + pub fn on_mouse_release( &mut self, canvas_common: &Common, mut modifier_handler: MOD, - mut mouse_handler: M, + mut cursor_handler: C, mut touch_handler: T, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), + C: 'static + FnMut(ModifiersState, i32, PhysicalPosition, CursorType, CursorButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { let window = canvas_common.window.clone(); self.on_pointer_release = - Some(canvas_common.add_event("pointerup", move |event: PointerEvent| { + Some(canvas_common.add_event("pointerup", move |event: PointerEventExt| { let modifiers = event::mouse_modifiers(&event); + let Some(r#type) = WebPointerType::from_event(&event) else { + modifier_handler(modifiers); + return; + }; - match event.pointer_type().as_str() { - "touch" => touch_handler( + match r#type { + WebPointerType::Mouse | WebPointerType::Pen => { + let button = event::cursor_button(&event, r#type); + let r#type = event::cursor_type(&event, r#type, button.as_ref()); + cursor_handler( + modifiers, + event.pointer_id(), + event::cursor_position(&event) + .to_physical(super::scale_factor(&window)), + r#type, + button.expect("no cursor button released"), + ) + }, + WebPointerType::Touch => touch_handler( modifiers, event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), + event::cursor_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ), - "mouse" => mouse_handler( - modifiers, - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), - event::mouse_button(&event).expect("no mouse button released"), - ), - _ => modifier_handler(modifiers), } })); } - pub fn on_mouse_press( + pub fn on_mouse_press( &mut self, canvas_common: &Common, mut modifier_handler: MOD, - mut mouse_handler: M, + mut cursor_handler: C, mut touch_handler: T, prevent_default: Rc>, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), + C: 'static + FnMut(ModifiersState, i32, PhysicalPosition, CursorType, CursorButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { let window = canvas_common.window.clone(); let canvas = canvas_common.raw().clone(); self.on_pointer_press = - Some(canvas_common.add_event("pointerdown", move |event: PointerEvent| { + Some(canvas_common.add_event("pointerdown", move |event: PointerEventExt| { if prevent_default.get() { // prevent text selection event.prevent_default(); @@ -125,112 +140,138 @@ impl PointerHandler { } let modifiers = event::mouse_modifiers(&event); + let Some(r#type) = WebPointerType::from_event(&event) else { + modifier_handler(modifiers); + return; + }; - match event.pointer_type().as_str() { - "mouse" => { - mouse_handler( - modifiers, - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), - event::mouse_button(&event).expect("no mouse button pressed"), - ); - + match r#type { + WebPointerType::Mouse | WebPointerType::Pen => { // Error is swallowed here since the error would occur every time the mouse // is clicked when the cursor is grabbed, and there // is probably not a situation where this could // fail, that we care if it fails. let _e = canvas.set_pointer_capture(event.pointer_id()); - }, - "touch" => { - touch_handler( + + let button = event::cursor_button(&event, r#type); + let r#type = event::cursor_type(&event, r#type, button.as_ref()); + cursor_handler( modifiers, event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), - Force::Normalized(event.pressure() as f64), - ); + event::cursor_position(&event) + .to_physical(super::scale_factor(&window)), + r#type, + button.expect("no cursor button released"), + ) }, - _ => modifier_handler(modifiers), + WebPointerType::Touch => touch_handler( + modifiers, + event.pointer_id(), + event::cursor_position(&event).to_physical(super::scale_factor(&window)), + Force::Normalized(event.pressure() as f64), + ), } })); } - pub fn on_cursor_move( + pub fn on_cursor_move( &mut self, canvas_common: &Common, mut modifier_handler: MOD, - mut mouse_handler: M, + mut cursor_handler: C, mut touch_handler: T, mut button_handler: B, prevent_default: Rc>, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), + C: 'static + + FnMut( + ModifiersState, + i32, + &mut dyn Iterator, CursorType)>, + ), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), - B: 'static + FnMut(ModifiersState, i32, PhysicalPosition, ButtonsState, CursorButton), + B: 'static + + FnMut( + ModifiersState, + i32, + PhysicalPosition, + CursorType, + ButtonsState, + CursorButton, + ), { let window = canvas_common.window.clone(); let canvas = canvas_common.raw().clone(); self.on_cursor_move = - Some(canvas_common.add_event("pointermove", move |event: PointerEvent| { + Some(canvas_common.add_event("pointermove", move |event: PointerEventExt| { let modifiers = event::mouse_modifiers(&event); - - let pointer_type = event.pointer_type(); - - if let "touch" | "mouse" = pointer_type.as_str() { - } else { + let Some(r#type) = WebPointerType::from_event(&event) else { modifier_handler(modifiers); return; - } + }; let id = event.pointer_id(); - // chorded button event - if let Some(button) = event::mouse_button(&event) { - debug_assert_eq!( - pointer_type, "mouse", - "expect pointer type of a chorded button event to be a mouse" - ); - - if prevent_default.get() { - // prevent text selection - event.prevent_default(); - // but still focus element - let _ = canvas.focus(); - } + match r#type { + WebPointerType::Mouse | WebPointerType::Pen => { + let button = event::cursor_button(&event, r#type); - button_handler( - modifiers, - id, - event::mouse_position(&event).to_physical(super::scale_factor(&window)), - event::mouse_buttons(&event), - button.into(), - ); + // chorded button event + if let Some(button) = button { + if prevent_default.get() { + // prevent text selection + event.prevent_default(); + // but still focus element + let _ = canvas.focus(); + } - return; - } + let r#type = event::cursor_type(&event, r#type, Some(&button)); + button_handler( + modifiers, + id, + event::cursor_position(&event) + .to_physical(super::scale_factor(&window)), + r#type, + event::cursor_buttons(&event), + button, + ); + } else { + cursor_handler( + modifiers, + id, + &mut event::pointer_move_event(event).map(|event| { + let position = event::cursor_position(&event) + .to_physical(super::scale_factor(&window)); + let r#type = + event::cursor_type(&event, r#type, button.as_ref()); - // pointer move event - let scale = super::scale_factor(&window); - match pointer_type.as_str() { - "mouse" => mouse_handler( - modifiers, - id, - &mut event::pointer_move_event(event) - .map(|event| event::mouse_position(&event).to_physical(scale)), - ), - "touch" => touch_handler( - modifiers, - id, - &mut event::pointer_move_event(event).map(|event| { - ( - event::mouse_position(&event).to_physical(scale), - Force::Normalized(event.pressure() as f64), + (position, r#type) + }), ) - }), - ), - _ => unreachable!("didn't return early before"), - }; + } + }, + WebPointerType::Touch => { + debug_assert_eq!( + event::raw_button(&event), + None, + "expect pointer type of a chorded button event to be mouse or pen" + ); + + touch_handler( + modifiers, + id, + &mut event::pointer_move_event(event).map(|event| { + let position = event::cursor_position(&event) + .to_physical(super::scale_factor(&window)); + let pressure = event.pressure().into(); + + (position, Force::Normalized(pressure)) + }), + ) + }, + } })); } @@ -241,10 +282,10 @@ impl PointerHandler { let window = canvas_common.window.clone(); self.on_touch_cancel = Some(canvas_common.add_event("pointercancel", move |event: PointerEvent| { - if event.pointer_type() == "touch" { + if matches!(WebPointerType::from_event(&event), Some(WebPointerType::Touch)) { handler( event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), + event::cursor_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ); } @@ -260,3 +301,39 @@ impl PointerHandler { self.on_touch_cancel = None; } } + +#[derive(Clone, Copy)] +pub enum WebPointerType { + Mouse, + Touch, + Pen, +} + +impl WebPointerType { + pub fn from_event(event: &PointerEvent) -> Option { + #[allow(clippy::disallowed_methods)] + let r#type = event.pointer_type(); + + match r#type.as_ref() { + "mouse" => Some(Self::Mouse), + "touch" => Some(Self::Touch), + "pen" => Some(Self::Pen), + r#type => { + warn!("found unknown pointer typ: {type}"); + None + }, + } + } +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = PointerEvent, extends = MouseEvent, extends = Event)] + pub type PointerEventExt; + + #[wasm_bindgen(method, getter, js_name = altitudeAngle)] + pub fn altitude_angle(this: &PointerEventExt) -> Option; + + #[wasm_bindgen(method, getter, js_name = azimuthAngle)] + pub fn azimuth_angle(this: &PointerEventExt) -> f64; +}