From 02a34a167ab281d7cca9908f67928b659d428b39 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Sat, 29 Aug 2020 21:34:33 +0800 Subject: [PATCH] Impl. mouse capturing on web target (#1672) * Impl. mouse capturing for web-sys with PointerEvent * Impl. mouse capturing for web-sys with MouseEvent by manual tracking * Reorganize web-sys backend mouse and pointer handling code * Impl. mouse capturing for stdweb with PointerEvent * Add mouse capturing for web target to changelog --- CHANGELOG.md | 1 + Cargo.toml | 1 + src/platform_impl/web/stdweb/canvas.rs | 4 + src/platform_impl/web/web_sys/canvas.rs | 250 +++++++++--------- .../web/web_sys/canvas/mouse_handler.rs | 203 ++++++++++++++ .../web/web_sys/canvas/pointer_handler.rs | 103 ++++++++ src/platform_impl/web/web_sys/event.rs | 13 +- 7 files changed, 446 insertions(+), 129 deletions(-) create mode 100644 src/platform_impl/web/web_sys/canvas/mouse_handler.rs create mode 100644 src/platform_impl/web/web_sys/canvas/pointer_handler.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bb45197..b8077d4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - On Android, fix `ControlFlow::Poll` not polling the Android event queue. - On macOS, add `NSWindow.hasShadow` support. - On Web, fix vertical mouse wheel scrolling being inverted. +- On Web, implement mouse capturing for click-dragging out of the canvas. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. diff --git a/Cargo.toml b/Cargo.toml index 017e5927..9659f7cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,7 @@ version = "0.3.22" optional = true features = [ 'console', + "AddEventListenerOptions", 'CssStyleDeclaration', 'BeforeUnloadEvent', 'Document', diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 51221837..1fdc7182 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -202,6 +202,7 @@ impl Canvas { where F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), { + let canvas = self.raw.clone(); self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| { handler( event.pointer_id(), @@ -209,6 +210,9 @@ impl Canvas { event::mouse_button(&event), event::mouse_modifiers(&event), ); + canvas + .set_pointer_capture(event.pointer_id()) + .expect("Failed to set pointer capture"); })); } diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index ae7e8091..81e152c1 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -9,36 +9,33 @@ use std::rc::Rc; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{ - Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, MouseEvent, - PointerEvent, WheelEvent, + AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, + MediaQueryListEvent, MouseEvent, WheelEvent, }; +mod mouse_handler; +mod pointer_handler; + pub struct Canvas { - /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. - raw: HtmlCanvasElement, + common: Common, on_focus: Option>, on_blur: Option>, on_keyboard_release: Option>, on_keyboard_press: Option>, on_received_character: Option>, - on_cursor_leave: Option>, - on_cursor_enter: Option>, - on_cursor_move: Option>, - on_pointer_press: Option>, - on_pointer_release: Option>, - // Fallback events when pointer event support is missing - on_mouse_leave: Option>, - on_mouse_enter: Option>, - on_mouse_move: Option>, - on_mouse_press: Option>, - on_mouse_release: Option>, on_mouse_wheel: Option>, on_fullscreen_change: Option>, - wants_fullscreen: Rc>, on_dark_mode: Option>, + mouse_state: MouseState, } -impl Drop for Canvas { +struct Common { + /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. + raw: HtmlCanvasElement, + wants_fullscreen: Rc>, +} + +impl Drop for Common { fn drop(&mut self) { self.raw.remove(); } @@ -72,38 +69,38 @@ impl Canvas { .set_attribute("tabindex", "0") .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; + let mouse_state = if has_pointer_event() { + MouseState::HasPointerEvent(pointer_handler::PointerHandler::new()) + } else { + MouseState::NoPointerEvent(mouse_handler::MouseHandler::new()) + }; + Ok(Canvas { - raw: canvas, + common: Common { + raw: canvas, + wants_fullscreen: Rc::new(RefCell::new(false)), + }, on_blur: None, on_focus: None, on_keyboard_release: None, on_keyboard_press: None, on_received_character: None, - on_cursor_leave: None, - on_cursor_enter: None, - on_cursor_move: None, - on_pointer_release: None, - on_pointer_press: None, - on_mouse_leave: None, - on_mouse_enter: None, - on_mouse_move: None, - on_mouse_press: None, - on_mouse_release: None, on_mouse_wheel: None, on_fullscreen_change: None, - wants_fullscreen: Rc::new(RefCell::new(false)), on_dark_mode: None, + mouse_state, }) } pub fn set_attribute(&self, attribute: &str, value: &str) { - self.raw + self.common + .raw .set_attribute(attribute, value) .expect(&format!("Set attribute: {}", attribute)); } pub fn position(&self) -> LogicalPosition { - let bounds = self.raw.get_bounding_client_rect(); + let bounds = self.common.raw.get_bounding_client_rect(); LogicalPosition { x: bounds.x(), @@ -113,20 +110,20 @@ impl Canvas { pub fn size(&self) -> PhysicalSize { PhysicalSize { - width: self.raw.width(), - height: self.raw.height(), + width: self.common.raw.width(), + height: self.common.raw.height(), } } pub fn raw(&self) -> &HtmlCanvasElement { - &self.raw + &self.common.raw } pub fn on_blur(&mut self, mut handler: F) where F: 'static + FnMut(), { - self.on_blur = Some(self.add_event("blur", move |_: FocusEvent| { + self.on_blur = Some(self.common.add_event("blur", move |_: FocusEvent| { handler(); })); } @@ -135,7 +132,7 @@ impl Canvas { where F: 'static + FnMut(), { - self.on_focus = Some(self.add_event("focus", move |_: FocusEvent| { + self.on_focus = Some(self.common.add_event("focus", move |_: FocusEvent| { handler(); })); } @@ -144,30 +141,34 @@ impl Canvas { where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_release = - Some(self.add_user_event("keyup", move |event: KeyboardEvent| { + self.on_keyboard_release = Some(self.common.add_user_event( + "keyup", + move |event: KeyboardEvent| { event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), event::keyboard_modifiers(&event), ); - })); + }, + )); } pub fn on_keyboard_press(&mut self, mut handler: F) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_press = - Some(self.add_user_event("keydown", move |event: KeyboardEvent| { + self.on_keyboard_press = Some(self.common.add_user_event( + "keydown", + move |event: KeyboardEvent| { event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), event::keyboard_modifiers(&event), ); - })); + }, + )); } pub fn on_received_character(&mut self, mut handler: F) @@ -179,7 +180,7 @@ impl Canvas { // The `keypress` event is deprecated, but there does not seem to be a // viable/compatible alternative as of now. `beforeinput` is still widely // unsupported. - self.on_received_character = Some(self.add_user_event( + self.on_received_character = Some(self.common.add_user_event( "keypress", move |event: KeyboardEvent| { handler(event::codepoint(&event)); @@ -187,115 +188,53 @@ impl Canvas { )); } - pub fn on_cursor_leave(&mut self, mut handler: F) + pub fn on_cursor_leave(&mut self, handler: F) where F: 'static + FnMut(i32), { - if has_pointer_event() { - self.on_cursor_leave = - Some(self.add_event("pointerout", move |event: PointerEvent| { - handler(event.pointer_id()); - })); - } else { - self.on_mouse_leave = Some(self.add_event("mouseout", move |_: MouseEvent| { - handler(0); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_cursor_leave(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_cursor_leave(&self.common, handler), } } - pub fn on_cursor_enter(&mut self, mut handler: F) + pub fn on_cursor_enter(&mut self, handler: F) where F: 'static + FnMut(i32), { - if has_pointer_event() { - self.on_cursor_enter = - Some(self.add_event("pointerover", move |event: PointerEvent| { - handler(event.pointer_id()); - })); - } else { - self.on_mouse_enter = Some(self.add_event("mouseover", move |_: MouseEvent| { - handler(0); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_cursor_enter(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_cursor_enter(&self.common, handler), } } - pub fn on_mouse_release(&mut self, mut handler: F) + pub fn on_mouse_release(&mut self, handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - if has_pointer_event() { - self.on_pointer_release = Some(self.add_user_event( - "pointerup", - move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - }, - )); - } else { - self.on_mouse_release = - Some(self.add_user_event("mouseup", move |event: MouseEvent| { - handler( - 0, - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_mouse_release(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_mouse_release(&self.common, handler), } } - pub fn on_mouse_press(&mut self, mut handler: F) + pub fn on_mouse_press(&mut self, handler: F) where F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), { - if has_pointer_event() { - self.on_pointer_press = Some(self.add_user_event( - "pointerdown", - move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - }, - )); - } else { - self.on_mouse_press = - Some(self.add_user_event("mousedown", move |event: MouseEvent| { - handler( - 0, - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_mouse_press(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_mouse_press(&self.common, handler), } } - pub fn on_cursor_move(&mut self, mut handler: F) + pub fn on_cursor_move(&mut self, handler: F) where F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), { - if has_pointer_event() { - self.on_cursor_move = - Some(self.add_event("pointermove", move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_modifiers(&event), - ); - })); - } else { - self.on_mouse_move = Some(self.add_event("mousemove", move |event: MouseEvent| { - handler( - 0, - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_modifiers(&event), - ); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_cursor_move(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_cursor_move(&self.common, handler), } } @@ -303,7 +242,7 @@ impl Canvas { where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { - self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| { + self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { event.prevent_default(); if let Some(delta) = event::mouse_scroll_delta(&event) { handler(0, delta, event::mouse_modifiers(&event)); @@ -315,8 +254,10 @@ impl Canvas { where F: 'static + FnMut(), { - self.on_fullscreen_change = - Some(self.add_event("fullscreenchange", move |_: Event| handler())); + self.on_fullscreen_change = Some( + self.common + .add_event("fullscreenchange", move |_: Event| handler()), + ); } pub fn on_dark_mode(&mut self, mut handler: F) @@ -341,6 +282,16 @@ impl Canvas { }); } + pub fn request_fullscreen(&self) { + self.common.request_fullscreen() + } + + pub fn is_fullscreen(&self) -> bool { + self.common.is_fullscreen() + } +} + +impl Common { fn add_event(&self, event_name: &str, mut handler: F) -> Closure where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, @@ -386,6 +337,44 @@ impl Canvas { }) } + // This function is used exclusively for mouse events (not pointer events). + // Due to the need for mouse capturing, the mouse event handlers are added + // to the window instead of the canvas element, which requires special + // handling to control event propagation. + fn add_window_mouse_event( + &self, + event_name: &str, + mut handler: F, + ) -> Closure + where + F: 'static + FnMut(MouseEvent), + { + let wants_fullscreen = self.wants_fullscreen.clone(); + let canvas = self.raw.clone(); + let window = web_sys::window().expect("Failed to obtain window"); + + let closure = Closure::wrap(Box::new(move |event: MouseEvent| { + handler(event); + + if *wants_fullscreen.borrow() { + canvas + .request_fullscreen() + .expect("Failed to enter fullscreen"); + *wants_fullscreen.borrow_mut() = false; + } + }) as Box); + + window + .add_event_listener_with_callback_and_add_event_listener_options( + event_name, + &closure.as_ref().unchecked_ref(), + AddEventListenerOptions::new().capture(true), + ) + .expect("Failed to add event listener with callback and options"); + + closure + } + pub fn request_fullscreen(&self) { *self.wants_fullscreen.borrow_mut() = true; } @@ -395,6 +384,11 @@ impl Canvas { } } +enum MouseState { + HasPointerEvent(pointer_handler::PointerHandler), + NoPointerEvent(mouse_handler::MouseHandler), +} + /// Returns whether pointer events are supported. /// Used to decide whether to use pointer events /// or plain mouse events. Note that Safari diff --git a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs new file mode 100644 index 00000000..37fdd018 --- /dev/null +++ b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs @@ -0,0 +1,203 @@ +use super::event; +use crate::dpi::PhysicalPosition; +use crate::event::{ModifiersState, MouseButton}; + +use std::cell::RefCell; +use std::rc::Rc; + +use wasm_bindgen::closure::Closure; +use web_sys::{EventTarget, MouseEvent}; + +pub(super) struct MouseHandler { + on_mouse_leave: Option>, + on_mouse_enter: Option>, + on_mouse_move: Option>, + on_mouse_press: Option>, + on_mouse_release: Option>, + on_mouse_leave_handler: Rc>>>, + mouse_capture_state: Rc>, +} + +#[derive(PartialEq, Eq)] +pub(super) enum MouseCaptureState { + NotCaptured, + Captured, + OtherElement, +} + +impl MouseHandler { + pub fn new() -> Self { + Self { + on_mouse_leave: None, + on_mouse_enter: None, + on_mouse_move: None, + on_mouse_press: None, + on_mouse_release: None, + on_mouse_leave_handler: Rc::new(RefCell::new(None)), + mouse_capture_state: Rc::new(RefCell::new(MouseCaptureState::NotCaptured)), + } + } + pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, handler: F) + where + F: 'static + FnMut(i32), + { + *self.on_mouse_leave_handler.borrow_mut() = Some(Box::new(handler)); + let on_mouse_leave_handler = self.on_mouse_leave_handler.clone(); + let mouse_capture_state = self.mouse_capture_state.clone(); + self.on_mouse_leave = Some(canvas_common.add_event("mouseout", move |_: MouseEvent| { + // If the mouse is being captured, it is always considered + // to be "within" the the canvas, until the capture has been + // released, therefore we don't send cursor leave events. + if *mouse_capture_state.borrow() != MouseCaptureState::Captured { + if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { + handler(0); + } + } + })); + } + + pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32), + { + let mouse_capture_state = self.mouse_capture_state.clone(); + self.on_mouse_enter = Some(canvas_common.add_event("mouseover", move |_: MouseEvent| { + // We don't send cursor leave events when the mouse is being + // captured, therefore we do the same with cursor enter events. + if *mouse_capture_state.borrow() != MouseCaptureState::Captured { + handler(0); + } + })); + } + + pub fn on_mouse_release(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, MouseButton, ModifiersState), + { + let on_mouse_leave_handler = self.on_mouse_leave_handler.clone(); + let mouse_capture_state = self.mouse_capture_state.clone(); + let canvas = canvas_common.raw.clone(); + self.on_mouse_release = Some(canvas_common.add_window_mouse_event( + "mouseup", + move |event: MouseEvent| { + let canvas = canvas.clone(); + let mut mouse_capture_state = mouse_capture_state.borrow_mut(); + match &*mouse_capture_state { + // Shouldn't happen but we'll just ignore it. + MouseCaptureState::NotCaptured => return, + MouseCaptureState::OtherElement => { + if event.buttons() == 0 { + // No buttons are pressed anymore so reset + // the capturing state. + *mouse_capture_state = MouseCaptureState::NotCaptured; + } + return; + } + MouseCaptureState::Captured => {} + } + event.stop_propagation(); + handler( + 0, + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + if event + .target() + .map_or(false, |target| target != EventTarget::from(canvas)) + { + // Since we do not send cursor leave events while the + // cursor is being captured, we instead send it after + // the capture has been released. + if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { + handler(0); + } + } + if event.buttons() == 0 { + // No buttons are pressed anymore so reset + // the capturing state. + *mouse_capture_state = MouseCaptureState::NotCaptured; + } + }, + )); + } + + pub fn on_mouse_press(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), + { + let mouse_capture_state = self.mouse_capture_state.clone(); + let canvas = canvas_common.raw.clone(); + self.on_mouse_press = Some(canvas_common.add_window_mouse_event( + "mousedown", + move |event: MouseEvent| { + let canvas = canvas.clone(); + let mut mouse_capture_state = mouse_capture_state.borrow_mut(); + match &*mouse_capture_state { + MouseCaptureState::NotCaptured + if event + .target() + .map_or(false, |target| target != EventTarget::from(canvas)) => + { + // The target isn't our canvas which means the + // mouse is pressed outside of it. + *mouse_capture_state = MouseCaptureState::OtherElement; + return; + } + MouseCaptureState::OtherElement => return, + _ => {} + } + *mouse_capture_state = MouseCaptureState::Captured; + event.stop_propagation(); + handler( + 0, + event::mouse_position(&event).to_physical(super::super::scale_factor()), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); + } + + pub fn on_cursor_move(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + { + let mouse_capture_state = self.mouse_capture_state.clone(); + let canvas = canvas_common.raw.clone(); + self.on_mouse_move = Some(canvas_common.add_window_mouse_event( + "mousemove", + move |event: MouseEvent| { + let canvas = canvas.clone(); + let mouse_capture_state = mouse_capture_state.borrow(); + let is_over_canvas = event + .target() + .map_or(false, |target| target == EventTarget::from(canvas.clone())); + match &*mouse_capture_state { + // Don't handle hover events outside of canvas. + MouseCaptureState::NotCaptured if !is_over_canvas => return, + MouseCaptureState::OtherElement if !is_over_canvas => return, + // If hovering over the canvas, just send the cursor move event. + MouseCaptureState::NotCaptured + | MouseCaptureState::OtherElement + | MouseCaptureState::Captured => { + if *mouse_capture_state == MouseCaptureState::Captured { + event.stop_propagation(); + } + let mouse_pos = if is_over_canvas { + event::mouse_position(&event) + } else { + // Since the mouse is not on the canvas, we cannot + // use `offsetX`/`offsetY`. + event::mouse_position_by_client(&event, &canvas) + }; + handler( + 0, + mouse_pos.to_physical(super::super::scale_factor()), + event::mouse_modifiers(&event), + ); + } + } + }, + )); + } +} diff --git a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs new file mode 100644 index 00000000..f9787dd5 --- /dev/null +++ b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs @@ -0,0 +1,103 @@ +use super::event; +use crate::dpi::PhysicalPosition; +use crate::event::{ModifiersState, MouseButton}; + +use wasm_bindgen::closure::Closure; +use web_sys::PointerEvent; + +pub(super) struct PointerHandler { + on_cursor_leave: Option>, + on_cursor_enter: Option>, + on_cursor_move: Option>, + on_pointer_press: Option>, + on_pointer_release: Option>, +} + +impl PointerHandler { + pub fn new() -> Self { + Self { + on_cursor_leave: None, + on_cursor_enter: None, + on_cursor_move: None, + on_pointer_press: None, + on_pointer_release: None, + } + } + + pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32), + { + self.on_cursor_leave = Some(canvas_common.add_event( + "pointerout", + move |event: PointerEvent| { + handler(event.pointer_id()); + }, + )); + } + + pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32), + { + self.on_cursor_enter = Some(canvas_common.add_event( + "pointerover", + move |event: PointerEvent| { + handler(event.pointer_id()); + }, + )); + } + + pub fn on_mouse_release(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, MouseButton, ModifiersState), + { + self.on_pointer_release = Some(canvas_common.add_user_event( + "pointerup", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); + } + + pub fn on_mouse_press(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), + { + let canvas = canvas_common.raw.clone(); + self.on_pointer_press = Some(canvas_common.add_user_event( + "pointerdown", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_position(&event).to_physical(super::super::scale_factor()), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + canvas + .set_pointer_capture(event.pointer_id()) + .expect("Failed to set pointer capture"); + }, + )); + } + + pub fn on_cursor_move(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + { + self.on_cursor_move = Some(canvas_common.add_event( + "pointermove", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_position(&event).to_physical(super::super::scale_factor()), + event::mouse_modifiers(&event), + ); + }, + )); + } +} diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 316b4f27..18927d40 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -2,7 +2,7 @@ use crate::dpi::LogicalPosition; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use std::convert::TryInto; -use web_sys::{KeyboardEvent, MouseEvent, WheelEvent}; +use web_sys::{HtmlCanvasElement, KeyboardEvent, MouseEvent, WheelEvent}; pub fn mouse_button(event: &MouseEvent) -> MouseButton { match event.button() { @@ -29,6 +29,17 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { } } +pub fn mouse_position_by_client( + event: &MouseEvent, + canvas: &HtmlCanvasElement, +) -> LogicalPosition { + let bounding_client_rect = canvas.get_bounding_client_rect(); + LogicalPosition { + x: event.client_x() as f64 - bounding_client_rect.x(), + y: event.client_y() as f64 - bounding_client_rect.y(), + } +} + pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { let x = event.delta_x(); let y = -event.delta_y();