diff --git a/CHANGELOG.md b/CHANGELOG.md index becb07ad0d..1cb6b07ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,11 @@ And please only add new entries to the top of this list, right below the `# Unre - **Breaking:** On Web, `instant` is now replaced by `web_time`. - On Windows, port to `windows-sys` version 0.48.0. - On Web, fix pen treated as mouse input. +- On Web, send mouse position on button release as well. +- On Web, fix touch input not gaining or loosing focus. +- **Breaking:** On Web, dropped support for Safari versions below 13. +- On Web, prevent clicks on the canvas to select text. +- On Web, use high-frequency pointer input events when supported by the browser. # 0.28.6 diff --git a/Cargo.toml b/Cargo.toml index 44f1d1534e..537ab4a669 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -132,7 +132,6 @@ package = "web-sys" version = "0.3.22" features = [ 'console', - "AddEventListenerOptions", 'CssStyleDeclaration', 'BeforeUnloadEvent', 'Document', @@ -147,7 +146,6 @@ features = [ 'KeyboardEvent', 'MediaQueryList', 'MediaQueryListEvent', - 'MouseEvent', 'Node', 'PointerEvent', 'Window', diff --git a/src/platform/web.rs b/src/platform/web.rs index f78a01fb02..2ab468d889 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -26,6 +26,9 @@ pub trait WindowBuilderExtWebSys { /// /// For example, mouse wheel events are only handled by the canvas by default. This avoids /// the default behavior of scrolling the page. + /// + /// Some events are impossible to prevent. E.g. Firefox allows to access the native browser + /// context menu with Shift+Rightclick. fn with_prevent_default(self, prevent_default: bool) -> Self; /// Whether the canvas should be focusable using the tab key. This is necessary to capture diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index c28a28e576..7134088f7a 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -192,7 +192,7 @@ impl Shared { // Add a series of events to the event loop runner // // It will determine if the event should be immediately sent to the user or buffered for later - pub fn send_events(&self, events: impl Iterator>) { + pub fn send_events(&self, events: impl IntoIterator>) { // If the event loop is closed, it should discard any new events if self.is_closed() { return; diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index c21b8b0660..d1ba53e869 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -91,7 +91,6 @@ impl EventLoopWindowTarget { canvas.set_attribute("data-raw-handle", &id.0.to_string()); canvas.on_touch_start(prevent_default); - canvas.on_touch_end(prevent_default); let runner = self.runner.clone(); let has_focus_clone = has_focus.clone(); @@ -198,69 +197,124 @@ impl EventLoopWindowTarget { prevent_default, ); - let runner = self.runner.clone(); - let modifiers = self.modifiers.clone(); - let has_focus_clone = has_focus.clone(); - canvas.on_cursor_leave(move |pointer_id, active_modifiers| { - let modifiers_changed = (has_focus_clone.get() && modifiers.get() != active_modifiers) - .then(|| { - modifiers.set(active_modifiers); - Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ModifiersChanged(active_modifiers.into()), - } - }); + canvas.on_cursor_leave( + { + let runner = self.runner.clone(); + let has_focus = has_focus.clone(); + let modifiers = self.modifiers.clone(); - runner.send_events(modifiers_changed.into_iter().chain(iter::once( - Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorLeft { - device_id: RootDeviceId(DeviceId(pointer_id)), - }, - }, - ))); - }); + move |active_modifiers| { + if has_focus.get() && modifiers.get() != active_modifiers { + modifiers.set(active_modifiers); + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + }); + } + } + }, + { + let runner = self.runner.clone(); - let runner = self.runner.clone(); - let modifiers = self.modifiers.clone(); - let has_focus_clone = has_focus.clone(); - canvas.on_cursor_enter(move |pointer_id, active_modifiers| { - let modifiers_changed = (has_focus_clone.get() && modifiers.get() != active_modifiers) - .then(|| { - modifiers.set(active_modifiers); - Event::WindowEvent { + move |pointer_id| { + runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::ModifiersChanged(active_modifiers.into()), + event: WindowEvent::CursorLeft { + device_id: RootDeviceId(DeviceId(pointer_id)), + }, + }); + } + }, + ); + + canvas.on_cursor_enter( + { + let runner = self.runner.clone(); + let has_focus = has_focus.clone(); + let modifiers = self.modifiers.clone(); + + move |active_modifiers| { + if has_focus.get() && modifiers.get() != active_modifiers { + modifiers.set(active_modifiers); + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + }); } - }); + } + }, + { + let runner = self.runner.clone(); - runner.send_events(modifiers_changed.into_iter().chain(iter::once( - Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorEntered { - device_id: RootDeviceId(DeviceId(pointer_id)), - }, - }, - ))); - }); + move |pointer_id| { + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorEntered { + device_id: RootDeviceId(DeviceId(pointer_id)), + }, + }); + } + }, + ); - let runner = self.runner.clone(); - let runner_touch = self.runner.clone(); - let modifiers = self.modifiers.clone(); - let has_focus_clone = has_focus.clone(); canvas.on_cursor_move( - move |pointer_id, position, delta, active_modifiers, buttons, button| { - let modifiers_changed = - (has_focus_clone.get() && modifiers.get() != active_modifiers).then(|| { + { + let runner = self.runner.clone(); + let has_focus = has_focus.clone(); + let modifiers = self.modifiers.clone(); + + move |active_modifiers| { + if has_focus.get() && modifiers.get() != active_modifiers { modifiers.set(active_modifiers); - Event::WindowEvent { + runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), - } + }) + } + } + }, + { + let runner = self.runner.clone(); + + move |pointer_id, position, delta| { + runner.send_events([ + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorMoved { + device_id: RootDeviceId(DeviceId(pointer_id)), + position, + }, + }, + Event::DeviceEvent { + device_id: RootDeviceId(DeviceId(pointer_id)), + event: DeviceEvent::MouseMotion { + delta: (delta.x, delta.y), + }, + }, + ]); + } + }, + { + let runner = self.runner.clone(); + + move |device_id, location, force| { + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Touch(Touch { + id: device_id as u64, + device_id: RootDeviceId(DeviceId(device_id)), + phase: TouchPhase::Moved, + force: Some(force), + location, + }), }); + } + }, + { + let runner = self.runner.clone(); - let button_event = button.map(|button| { - if buttons.contains(button.into()) { + move |pointer_id, position: crate::dpi::PhysicalPosition, buttons, button| { + let button_event = if buttons.contains(button.into()) { Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { @@ -278,137 +332,143 @@ impl EventLoopWindowTarget { button, }, } - } - }); + }; - runner.send_events( - modifiers_changed - .into_iter() - .chain([ - Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { - device_id: RootDeviceId(DeviceId(pointer_id)), - position, - }, - }, - Event::DeviceEvent { + // A chorded button event may come in without any prior CursorMoved events, + // therefore we should send a CursorMoved event to make sure that the + // user code has the correct cursor position. + runner.send_events([ + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorMoved { device_id: RootDeviceId(DeviceId(pointer_id)), - event: DeviceEvent::MouseMotion { - delta: (delta.x, delta.y), - }, + position, }, - ]) - .chain(button_event), - ); - }, - move |device_id, location, force| { - runner_touch.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Touch(Touch { - id: device_id as u64, - device_id: RootDeviceId(DeviceId(device_id)), - phase: TouchPhase::Moved, - force: Some(force), - location, - }), - }); + }, + button_event, + ]); + } }, prevent_default, ); - let runner = self.runner.clone(); - let runner_touch = self.runner.clone(); - let modifiers = self.modifiers.clone(); - let has_focus_clone = has_focus.clone(); canvas.on_mouse_press( - move |pointer_id, position, button, active_modifiers| { - let focus_changed = - (!has_focus_clone.replace(true)).then_some(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Focused(true), - }); + { + let runner = self.runner.clone(); + let modifiers = self.modifiers.clone(); - let modifiers_changed = (modifiers.get() != active_modifiers).then(|| { - modifiers.set(active_modifiers); - Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ModifiersChanged(active_modifiers.into()), + move |active_modifiers| { + if modifiers.get() != active_modifiers { + modifiers.set(active_modifiers); + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + }) } - }); - - // A mouse down event may come in without any prior CursorMoved events, - // therefore we should send a CursorMoved event to make sure that the - // user code has the correct cursor position. - runner.send_events(focus_changed.into_iter().chain(modifiers_changed).chain([ - Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { - device_id: RootDeviceId(DeviceId(pointer_id)), - position, + } + }, + { + let runner = self.runner.clone(); + + move |pointer_id, position, button| { + // A mouse down event may come in without any prior CursorMoved events, + // therefore we should send a CursorMoved event to make sure that the + // user code has the correct cursor position. + runner.send_events([ + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorMoved { + device_id: RootDeviceId(DeviceId(pointer_id)), + position, + }, }, - }, - Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - device_id: RootDeviceId(DeviceId(pointer_id)), - state: ElementState::Pressed, - button, + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseInput { + device_id: RootDeviceId(DeviceId(pointer_id)), + state: ElementState::Pressed, + button, + }, }, - }, - ])); + ]); + } }, - move |device_id, location, force| { - runner_touch.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Touch(Touch { - id: device_id as u64, - device_id: RootDeviceId(DeviceId(device_id)), - phase: TouchPhase::Started, - force: Some(force), - location, - }), - }); + { + let runner = self.runner.clone(); + + move |device_id, location, force| { + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Touch(Touch { + id: device_id as u64, + device_id: RootDeviceId(DeviceId(device_id)), + phase: TouchPhase::Started, + force: Some(force), + location, + }), + }) + } }, + prevent_default, ); - let runner = self.runner.clone(); - let runner_touch = self.runner.clone(); - let modifiers = self.modifiers.clone(); - let has_focus_clone = has_focus.clone(); canvas.on_mouse_release( - move |pointer_id, button, active_modifiers| { - let modifiers_changed = - (has_focus_clone.get() && modifiers.get() != active_modifiers).then(|| { + { + let runner = self.runner.clone(); + let has_focus = has_focus.clone(); + let modifiers = self.modifiers.clone(); + + move |active_modifiers| { + if has_focus.get() && modifiers.get() != active_modifiers { modifiers.set(active_modifiers); - Event::WindowEvent { + runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), - } - }); - - runner.send_events(modifiers_changed.into_iter().chain(iter::once( - Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - device_id: RootDeviceId(DeviceId(pointer_id)), - state: ElementState::Released, - button, + }); + } + } + }, + { + let runner = self.runner.clone(); + + move |pointer_id, position, button| { + // A mouse up event may come in without any prior CursorMoved events, + // therefore we should send a CursorMoved event to make sure that the + // user code has the correct cursor position. + runner.send_events([ + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorMoved { + device_id: RootDeviceId(DeviceId(pointer_id)), + position, + }, }, - }, - ))); + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseInput { + device_id: RootDeviceId(DeviceId(pointer_id)), + state: ElementState::Released, + button, + }, + }, + ]); + } }, - move |device_id, location, force| { - runner_touch.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Touch(Touch { - id: device_id as u64, - device_id: RootDeviceId(DeviceId(device_id)), - phase: TouchPhase::Ended, - force: Some(force), - location, - }), - }); + { + let runner_touch = self.runner.clone(); + + move |device_id, location, force| { + runner_touch.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Touch(Touch { + id: device_id as u64, + device_id: RootDeviceId(DeviceId(device_id)), + phase: TouchPhase::Ended, + force: Some(force), + location, + }), + }); + } }, ); diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 5ad9456b5e..8d3d5a07ee 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -1,5 +1,6 @@ use super::event_handle::EventListenerHandle; use super::media_query_handle::MediaQueryListHandle; +use super::pointer::PointerHandler; use super::{event, ButtonsState}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; @@ -16,13 +17,9 @@ use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{closure::Closure, JsCast, JsValue}; use wasm_bindgen_futures::JsFuture; use web_sys::{ - AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, - MediaQueryListEvent, MouseEvent, WheelEvent, + Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, WheelEvent, }; -mod mouse_handler; -mod pointer_handler; - #[allow(dead_code)] pub struct Canvas { common: Common, @@ -35,12 +32,12 @@ pub struct Canvas { on_mouse_wheel: Option>, on_fullscreen_change: Option>, on_dark_mode: Option, - mouse_state: MouseState, + pointer_handler: PointerHandler, } -struct Common { +pub struct Common { /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. - raw: HtmlCanvasElement, + pub raw: HtmlCanvasElement, wants_fullscreen: Rc>, } @@ -74,12 +71,6 @@ impl Canvas { .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 { common: Common { raw: canvas, @@ -94,7 +85,7 @@ impl Canvas { on_mouse_wheel: None, on_fullscreen_change: None, on_dark_mode: None, - mouse_state, + pointer_handler: PointerHandler::new(), }) } @@ -147,14 +138,6 @@ impl Canvas { })); } - pub fn on_touch_end(&mut self, prevent_default: bool) { - self.on_touch_end = Some(self.common.add_event("touchend", move |event: Event| { - if prevent_default { - event.prevent_default(); - } - })); - } - pub fn on_blur(&mut self, mut handler: F) where F: 'static + FnMut(), @@ -221,84 +204,90 @@ impl Canvas { )); } - pub fn on_cursor_leave(&mut self, handler: F) + pub fn on_cursor_leave(&mut self, modifier_handler: MOD, mouse_handler: M) where - F: 'static + FnMut(i32, ModifiersState), + MOD: 'static + FnMut(ModifiersState), + M: 'static + FnMut(i32), { - 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), - } + self.pointer_handler + .on_cursor_leave(&self.common, modifier_handler, mouse_handler) } - pub fn on_cursor_enter(&mut self, handler: F) + pub fn on_cursor_enter(&mut self, modifier_handler: MOD, mouse_handler: M) where - F: 'static + FnMut(i32, ModifiersState), + MOD: 'static + FnMut(ModifiersState), + M: 'static + FnMut(i32), { - 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), - } + self.pointer_handler + .on_cursor_enter(&self.common, modifier_handler, mouse_handler) } - pub fn on_mouse_release(&mut self, mouse_handler: M, touch_handler: T) - where - M: 'static + FnMut(i32, MouseButton, ModifiersState), + pub fn on_mouse_release( + &mut self, + modifier_handler: MOD, + mouse_handler: M, + touch_handler: T, + ) where + MOD: 'static + FnMut(ModifiersState), + M: 'static + FnMut(i32, PhysicalPosition, MouseButton), T: 'static + FnMut(i32, PhysicalPosition, Force), { - match &mut self.mouse_state { - MouseState::HasPointerEvent(h) => { - h.on_mouse_release(&self.common, mouse_handler, touch_handler) - } - MouseState::NoPointerEvent(h) => h.on_mouse_release(&self.common, mouse_handler), - } + self.pointer_handler.on_mouse_release( + &self.common, + modifier_handler, + mouse_handler, + touch_handler, + ) } - pub fn on_mouse_press(&mut self, mouse_handler: M, touch_handler: T) - where - M: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), + pub fn on_mouse_press( + &mut self, + modifier_handler: MOD, + mouse_handler: M, + touch_handler: T, + prevent_default: bool, + ) where + MOD: 'static + FnMut(ModifiersState), + M: 'static + FnMut(i32, PhysicalPosition, MouseButton), T: 'static + FnMut(i32, PhysicalPosition, Force), { - match &mut self.mouse_state { - MouseState::HasPointerEvent(h) => { - h.on_mouse_press(&self.common, mouse_handler, touch_handler) - } - MouseState::NoPointerEvent(h) => h.on_mouse_press(&self.common, mouse_handler), - } + self.pointer_handler.on_mouse_press( + &self.common, + modifier_handler, + mouse_handler, + touch_handler, + prevent_default, + ) } - pub fn on_cursor_move( + pub fn on_cursor_move( &mut self, + modifier_handler: MOD, mouse_handler: M, touch_handler: T, + button_handler: B, prevent_default: bool, ) where - M: 'static - + FnMut( - i32, - PhysicalPosition, - PhysicalPosition, - ModifiersState, - ButtonsState, - Option, - ), + MOD: 'static + FnMut(ModifiersState), + M: 'static + FnMut(i32, PhysicalPosition, PhysicalPosition), T: 'static + FnMut(i32, PhysicalPosition, Force), + B: 'static + FnMut(i32, PhysicalPosition, ButtonsState, MouseButton), { - match &mut self.mouse_state { - MouseState::HasPointerEvent(h) => { - h.on_cursor_move(&self.common, mouse_handler, touch_handler, prevent_default) - } - MouseState::NoPointerEvent(h) => h.on_cursor_move(&self.common, mouse_handler), - } + self.pointer_handler.on_cursor_move( + &self.common, + modifier_handler, + mouse_handler, + touch_handler, + button_handler, + prevent_default, + ) } pub fn on_touch_cancel(&mut self, handler: F) where F: 'static + FnMut(i32, PhysicalPosition, Force), { - if let MouseState::HasPointerEvent(h) = &mut self.mouse_state { - h.on_touch_cancel(&self.common, handler) - } + self.pointer_handler.on_touch_cancel(&self.common, handler) } pub fn on_mouse_wheel(&mut self, mut handler: F, prevent_default: bool) @@ -355,15 +344,12 @@ impl Canvas { self.on_mouse_wheel = None; self.on_fullscreen_change = None; self.on_dark_mode = None; - match &mut self.mouse_state { - MouseState::HasPointerEvent(h) => h.remove_listeners(), - MouseState::NoPointerEvent(h) => h.remove_listeners(), - } + self.pointer_handler.remove_listeners() } } impl Common { - fn add_event( + pub fn add_event( &self, event_name: &'static str, mut handler: F, @@ -388,7 +374,7 @@ impl Common { // The difference between add_event and add_user_event is that the latter has a special meaning // for browser security. A user event is a deliberate action by the user (like a mouse or key // press) and is the only time things like a fullscreen request may be successfully completed.) - fn add_user_event( + pub fn add_user_event( &self, event_name: &'static str, mut handler: F, @@ -412,43 +398,6 @@ impl Common { }) } - // 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: &'static str, - mut handler: F, - ) -> EventListenerHandle - 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); - - let listener = EventListenerHandle::with_options( - &window, - event_name, - closure, - AddEventListenerOptions::new().capture(true), - ); - - listener - } - pub fn request_fullscreen(&self) { #[wasm_bindgen] extern "C" { @@ -480,21 +429,3 @@ impl Common { super::is_fullscreen(&self.raw) } } - -/// Pointer events are supported or not. -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 -/// doesn't support pointer events now. -fn has_pointer_event() -> bool { - if let Some(window) = web_sys::window() { - window.get("PointerEvent").is_some() - } else { - false - } -} diff --git a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs deleted file mode 100644 index 6ee70cf585..0000000000 --- a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs +++ /dev/null @@ -1,238 +0,0 @@ -use super::event; -use super::EventListenerHandle; -use crate::dpi::PhysicalPosition; -use crate::event::MouseButton; -use crate::keyboard::ModifiersState; - -use std::cell::RefCell; -use std::rc::Rc; - -use event::ButtonsState; -use web_sys::{EventTarget, MouseEvent}; - -type MouseLeaveHandler = Rc>>>; - -#[allow(dead_code)] -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: MouseLeaveHandler, - 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, ModifiersState), - { - *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 |event: 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() { - let modifiers = event::mouse_modifiers(&event); - handler(0, modifiers); - } - } - }, - )); - } - - pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) - where - F: 'static + FnMut(i32, ModifiersState), - { - let mouse_capture_state = self.mouse_capture_state.clone(); - self.on_mouse_enter = Some(canvas_common.add_event( - "mouseover", - move |event: 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 { - let modifiers = event::mouse_modifiers(&event); - handler(0, modifiers); - } - }, - )); - } - - 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).expect("no mouse button released"), - 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() { - let modifiers = event::mouse_modifiers(&event); - handler(0, modifiers); - } - } - 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).expect("no mouse button pressed"), - event::mouse_modifiers(&event), - ); - }, - )); - } - - pub fn on_cursor_move(&mut self, canvas_common: &super::Common, mut handler: F) - where - F: 'static - + FnMut( - i32, - PhysicalPosition, - PhysicalPosition, - ModifiersState, - ButtonsState, - Option, - ), - { - 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 | MouseCaptureState::OtherElement - if !is_over_canvas => {} - // 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) - }; - let mouse_delta = event::mouse_delta(&event); - handler( - 0, - mouse_pos.to_physical(super::super::scale_factor()), - mouse_delta.to_physical(super::super::scale_factor()), - event::mouse_modifiers(&event), - event::mouse_buttons(&event), - event::mouse_button(&event), - ); - } - } - }, - )); - } - - pub fn remove_listeners(&mut self) { - self.on_mouse_leave = None; - self.on_mouse_enter = None; - self.on_mouse_move = None; - self.on_mouse_press = None; - self.on_mouse_release = None; - *self.on_mouse_leave_handler.borrow_mut() = None; - } -} diff --git a/src/platform_impl/web/web_sys/event_handle.rs b/src/platform_impl/web/web_sys/event_handle.rs index 5a0a3531f1..9f9be48ad3 100644 --- a/src/platform_impl/web/web_sys/event_handle.rs +++ b/src/platform_impl/web/web_sys/event_handle.rs @@ -1,7 +1,7 @@ use wasm_bindgen::{prelude::Closure, JsCast}; -use web_sys::{AddEventListenerOptions, EventListenerOptions, EventTarget}; +use web_sys::{EventListenerOptions, EventTarget}; -pub(super) struct EventListenerHandle { +pub struct EventListenerHandle { target: EventTarget, event_type: &'static str, listener: Closure, @@ -24,31 +24,6 @@ impl EventListenerHandle { options: EventListenerOptions::new(), } } - - pub fn with_options( - target: &U, - event_type: &'static str, - listener: Closure, - options: &AddEventListenerOptions, - ) -> Self - where - U: Clone + Into, - { - let target = target.clone().into(); - target - .add_event_listener_with_callback_and_add_event_listener_options( - event_type, - listener.as_ref().unchecked_ref(), - options, - ) - .expect("Failed to add event listener"); - EventListenerHandle { - target, - event_type, - listener, - options: options.clone().unchecked_into(), - } - } } impl Drop for EventListenerHandle { diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 454204c79b..6a1f556ec0 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -2,6 +2,7 @@ mod canvas; mod event; mod event_handle; mod media_query_handle; +mod pointer; mod scaling; mod timeout; diff --git a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs b/src/platform_impl/web/web_sys/pointer.rs similarity index 60% rename from src/platform_impl/web/web_sys/canvas/pointer_handler.rs rename to src/platform_impl/web/web_sys/pointer.rs index 80713ce9a6..aab89d29ca 100644 --- a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs +++ b/src/platform_impl/web/web_sys/pointer.rs @@ -1,13 +1,15 @@ +use super::canvas::Common; use super::event; -use super::EventListenerHandle; +use super::event_handle::EventListenerHandle; use crate::dpi::PhysicalPosition; use crate::event::{Force, MouseButton}; use crate::keyboard::ModifiersState; use event::ButtonsState; +use once_cell::unsync::OnceCell; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; -use web_sys::PointerEvent; +use web_sys::{HtmlCanvasElement, PointerEvent}; #[allow(dead_code)] pub(super) struct PointerHandler { @@ -31,13 +33,20 @@ impl PointerHandler { } } - pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, mut handler: F) - where - F: 'static + FnMut(i32, ModifiersState), + pub fn on_cursor_leave( + &mut self, + canvas_common: &Common, + mut modifier_handler: MOD, + mut mouse_handler: M, + ) where + MOD: 'static + FnMut(ModifiersState), + M: 'static + FnMut(i32), { self.on_cursor_leave = Some(canvas_common.add_event( "pointerout", move |event: PointerEvent| { + modifier_handler(event::mouse_modifiers(&event)); + // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. @@ -45,18 +54,25 @@ impl PointerHandler { return; } - handler(event.pointer_id(), event::mouse_modifiers(&event)); + mouse_handler(event.pointer_id()); }, )); } - pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) - where - F: 'static + FnMut(i32, ModifiersState), + pub fn on_cursor_enter( + &mut self, + canvas_common: &Common, + mut modifier_handler: MOD, + mut mouse_handler: M, + ) where + MOD: 'static + FnMut(ModifiersState), + M: 'static + FnMut(i32), { self.on_cursor_enter = Some(canvas_common.add_event( "pointerover", move |event: PointerEvent| { + modifier_handler(event::mouse_modifiers(&event)); + // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. @@ -64,35 +80,38 @@ impl PointerHandler { return; } - handler(event.pointer_id(), event::mouse_modifiers(&event)); + mouse_handler(event.pointer_id()); }, )); } - pub fn on_mouse_release( + pub fn on_mouse_release( &mut self, - canvas_common: &super::Common, + canvas_common: &Common, + mut modifier_handler: MOD, mut mouse_handler: M, mut touch_handler: T, ) where - M: 'static + FnMut(i32, MouseButton, ModifiersState), + MOD: 'static + FnMut(ModifiersState), + M: 'static + FnMut(i32, PhysicalPosition, MouseButton), T: 'static + FnMut(i32, PhysicalPosition, Force), { let canvas = canvas_common.raw.clone(); self.on_pointer_release = Some(canvas_common.add_user_event( "pointerup", move |event: PointerEvent| { + modifier_handler(event::mouse_modifiers(&event)); + match event.pointer_type().as_str() { "touch" => touch_handler( event.pointer_id(), - event::touch_position(&event, &canvas) - .to_physical(super::super::scale_factor()), + event::touch_position(&event, &canvas).to_physical(super::scale_factor()), Force::Normalized(event.pressure() as f64), ), "mouse" => mouse_handler( event.pointer_id(), + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_button(&event).expect("no mouse button released"), - event::mouse_modifiers(&event), ), _ => (), } @@ -100,34 +119,45 @@ impl PointerHandler { )); } - pub fn on_mouse_press( + pub fn on_mouse_press( &mut self, - canvas_common: &super::Common, + canvas_common: &Common, + mut modifier_handler: MOD, mut mouse_handler: M, mut touch_handler: T, + prevent_default: bool, ) where - M: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), + MOD: 'static + FnMut(ModifiersState), + M: 'static + FnMut(i32, PhysicalPosition, MouseButton), T: 'static + FnMut(i32, PhysicalPosition, Force), { let canvas = canvas_common.raw.clone(); self.on_pointer_press = Some(canvas_common.add_user_event( "pointerdown", move |event: PointerEvent| { + if prevent_default { + // prevent text selection + event.prevent_default(); + // but still focus element + let _ = canvas.focus(); + } + + modifier_handler(event::mouse_modifiers(&event)); + match event.pointer_type().as_str() { "touch" => { touch_handler( event.pointer_id(), event::touch_position(&event, &canvas) - .to_physical(super::super::scale_factor()), + .to_physical(super::scale_factor()), Force::Normalized(event.pressure() as f64), ); } "mouse" => { mouse_handler( event.pointer_id(), - event::mouse_position(&event).to_physical(super::super::scale_factor()), + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_button(&event).expect("no mouse button pressed"), - event::mouse_modifiers(&event), ); // Error is swallowed here since the error would occur every time the mouse is @@ -141,27 +171,27 @@ impl PointerHandler { )); } - pub fn on_cursor_move( + pub fn on_cursor_move( &mut self, - canvas_common: &super::Common, + canvas_common: &Common, + mut modifier_handler: MOD, mut mouse_handler: M, mut touch_handler: T, + mut button_handler: B, prevent_default: bool, ) where - M: 'static - + FnMut( - i32, - PhysicalPosition, - PhysicalPosition, - ModifiersState, - ButtonsState, - Option, - ), + MOD: 'static + FnMut(ModifiersState), + M: 'static + FnMut(i32, PhysicalPosition, PhysicalPosition), T: 'static + FnMut(i32, PhysicalPosition, Force), + B: 'static + FnMut(i32, PhysicalPosition, ButtonsState, MouseButton), { let canvas = canvas_common.raw.clone(); self.on_cursor_move = Some(canvas_common.add_event( - "pointermove", + if has_pointer_raw_support(&canvas) { + "pointerrawupdate" + } else { + "pointermove" + }, move |event: PointerEvent| { // coalesced events are not available on Safari #[wasm_bindgen] @@ -173,34 +203,48 @@ impl PointerHandler { fn has_get_coalesced_events(this: &PointerEventExt) -> JsValue; } - match event.pointer_type().as_str() { - "touch" => { - if prevent_default { - // prevent scroll on mobile web - event.prevent_default(); - } - } - "mouse" => (), + modifier_handler(event::mouse_modifiers(&event)); + + let pointer_type = event.pointer_type(); + + match pointer_type.as_str() { + "touch" | "mouse" => (), _ => return, } - let event: PointerEventExt = event.unchecked_into(); - let id = event.pointer_id(); - // cache buttons if the pointer is a mouse - let mouse = (event.pointer_type() == "mouse").then(|| { - ( - event::mouse_modifiers(&event), + + // 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 { + // prevent text selection + event.prevent_default(); + // but still focus element + let _ = canvas.focus(); + } + + button_handler( + id, + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_buttons(&event), - event::mouse_button(&event), - ) - }); + button, + ); + + return; + } + + // pointer move event + + let event: PointerEventExt = event.unchecked_into(); // store coalesced events to extend it's lifetime let events = (!event.has_get_coalesced_events().is_undefined()) - .then(|| event.get_coalesced_events()) - // if coalesced events is empty, it's a chorded button event - .filter(|events| events.length() != 0); + .then(|| event.get_coalesced_events()); // make a single iterator depending on the availability of coalesced events let events = if let Some(events) = &events { @@ -214,37 +258,26 @@ impl PointerHandler { }; for event in events { - // coalesced events should always have the same source as the root event - debug_assert_eq!(id, event.pointer_id()); - debug_assert_eq!(mouse.is_none(), event.pointer_type() == "touch"); - - if let Some((modifiers, buttons, button)) = mouse { - // coalesced events should have the same buttons - debug_assert_eq!(modifiers, event::mouse_modifiers(&event)); - debug_assert_eq!(buttons, event::mouse_buttons(&event)); - - mouse_handler( + match pointer_type.as_str() { + "mouse" => mouse_handler( id, - event::mouse_position(&event).to_physical(super::super::scale_factor()), - event::mouse_delta(&event).to_physical(super::super::scale_factor()), - modifiers, - buttons, - button, - ); - } else { - touch_handler( + event::mouse_position(&event).to_physical(super::scale_factor()), + event::mouse_delta(&event).to_physical(super::scale_factor()), + ), + "touch" => touch_handler( id, event::touch_position(&event, &canvas) - .to_physical(super::super::scale_factor()), + .to_physical(super::scale_factor()), Force::Normalized(event.pressure() as f64), - ); + ), + _ => unreachable!("didn't return early before"), } } }, )); } - pub fn on_touch_cancel(&mut self, canvas_common: &super::Common, mut handler: F) + pub fn on_touch_cancel(&mut self, canvas_common: &Common, mut handler: F) where F: 'static + FnMut(i32, PhysicalPosition, Force), { @@ -255,8 +288,7 @@ impl PointerHandler { if event.pointer_type() == "touch" { handler( event.pointer_id(), - event::touch_position(&event, &canvas) - .to_physical(super::super::scale_factor()), + event::touch_position(&event, &canvas).to_physical(super::scale_factor()), Force::Normalized(event.pressure() as f64), ); } @@ -273,3 +305,24 @@ impl PointerHandler { self.on_touch_cancel = None; } } + +fn has_pointer_raw_support(canvas: &HtmlCanvasElement) -> bool { + thread_local! { + static POINTER_RAW_SUPPORT: OnceCell = OnceCell::new(); + } + + POINTER_RAW_SUPPORT.with(|support| { + *support.get_or_init(|| { + #[wasm_bindgen] + extern "C" { + type HtmlCanvasElementExt; + + #[wasm_bindgen(method, getter, js_name = onpointerrawupdate)] + fn has_on_pointerrawupdate(this: &HtmlCanvasElementExt) -> JsValue; + } + + let canvas: &HtmlCanvasElementExt = canvas.unchecked_ref(); + !canvas.has_on_pointerrawupdate().is_undefined() + }) + }) +}