From 843d7904d6df39fc84ece025967ec239438f0924 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 25 Dec 2023 09:37:35 +0100 Subject: [PATCH] On Web, add `Window::(set_)prevent_default()` (#3307) --- CHANGELOG.md | 1 + src/platform/web.rs | 31 ++++++++--- .../web/event_loop/window_target.rs | 54 ++++++++----------- src/platform_impl/web/web_sys/canvas.rs | 33 +++++++----- src/platform_impl/web/web_sys/pointer.rs | 11 ++-- src/platform_impl/web/window.rs | 14 +++-- 6 files changed, 84 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05d70e07..491adf51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Unreleased` header. - **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`. - **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available. - **Breaking:** Bump MSRV from `1.65` to `1.70`. +- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`. # 0.29.6 diff --git a/src/platform/web.rs b/src/platform/web.rs index 8062166a..7fcfe240 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -42,6 +42,21 @@ pub trait WindowExtWebSys { /// Only returns the canvas if called from inside the window context (the /// main thread). fn canvas(&self) -> Option; + + /// Returns [`true`] if calling `event.preventDefault()` is enabled. + /// + /// See [`Window::set_prevent_default()`] for more details. + fn prevent_default(&self) -> bool; + + /// Sets whether `event.preventDefault()` should be called on events on the + /// canvas that have side effects. + /// + /// For example, by default using the mouse wheel would cause the page to scroll, enabling this + /// would prevent that. + /// + /// Some events are impossible to prevent. E.g. Firefox allows to access the native browser + /// context menu with Shift+Rightclick. + fn set_prevent_default(&self, prevent_default: bool); } impl WindowExtWebSys for Window { @@ -49,6 +64,14 @@ impl WindowExtWebSys for Window { fn canvas(&self) -> Option { self.window.canvas() } + + fn prevent_default(&self) -> bool { + self.window.prevent_default() + } + + fn set_prevent_default(&self, prevent_default: bool) { + self.window.set_prevent_default(prevent_default) + } } pub trait WindowBuilderExtWebSys { @@ -60,14 +83,10 @@ pub trait WindowBuilderExtWebSys { /// [`None`] by default. fn with_canvas(self, canvas: Option) -> Self; - /// Whether `event.preventDefault()` should be called on events on the + /// Sets whether `event.preventDefault()` should be called on events on the /// canvas that have side effects. /// - /// For example, by default using the mouse wheel would cause the page to scroll, enabling this - /// would prevent that. - /// - /// Some events are impossible to prevent. E.g. Firefox allows to access the native browser - /// context menu with Shift+Rightclick. + /// See [`Window::set_prevent_default()`] for more details. /// /// Enabled by default. fn with_prevent_default(self, prevent_default: bool) -> Self; diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 1921c6a7..f586643d 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -75,18 +75,13 @@ impl EventLoopWindowTarget { WindowId(self.runner.generate_id()) } - pub fn register( - &self, - canvas: &Rc>, - id: WindowId, - prevent_default: bool, - ) { + pub fn register(&self, canvas: &Rc>, id: WindowId) { let canvas_clone = canvas.clone(); let mut canvas = canvas.borrow_mut(); #[cfg(any(feature = "rwh_04", feature = "rwh_05"))] canvas.set_attribute("data-raw-handle", &id.0.to_string()); - canvas.on_touch_start(prevent_default); + canvas.on_touch_start(); let runner = self.runner.clone(); let has_focus = canvas.has_focus.clone(); @@ -157,7 +152,6 @@ impl EventLoopWindowTarget { .chain(modifiers_changed), ); }, - prevent_default, ); let runner = self.runner.clone(); @@ -194,7 +188,6 @@ impl EventLoopWindowTarget { .chain(modifiers_changed), ) }, - prevent_default, ); let has_focus = canvas.has_focus.clone(); @@ -374,7 +367,6 @@ impl EventLoopWindowTarget { ])); } }, - prevent_default, ); canvas.on_mouse_press( @@ -456,7 +448,6 @@ impl EventLoopWindowTarget { ))) } }, - prevent_default, ); canvas.on_mouse_release( @@ -547,30 +538,27 @@ impl EventLoopWindowTarget { let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); - canvas.on_mouse_wheel( - move |pointer_id, delta, active_modifiers| { - let modifiers_changed = (has_focus.get() && modifiers.get() != active_modifiers) - .then(|| { - modifiers.set(active_modifiers); - Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ModifiersChanged(active_modifiers.into()), - } - }); - - runner.send_events(modifiers_changed.into_iter().chain(iter::once( + canvas.on_mouse_wheel(move |pointer_id, delta, active_modifiers| { + let modifiers_changed = + (has_focus.get() && modifiers.get() != active_modifiers).then(|| { + modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::MouseWheel { - device_id: RootDeviceId(DeviceId(pointer_id)), - delta, - phase: TouchPhase::Moved, - }, + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + } + }); + + runner.send_events(modifiers_changed.into_iter().chain(iter::once( + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseWheel { + device_id: RootDeviceId(DeviceId(pointer_id)), + delta, + phase: TouchPhase::Moved, }, - ))); - }, - prevent_default, - ); + }, + ))); + }); let runner = self.runner.clone(); canvas.on_touch_cancel(move |device_id, location, force| { @@ -649,7 +637,7 @@ impl EventLoopWindowTarget { let runner = self.runner.clone(); canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id))); - canvas.on_context_menu(prevent_default); + canvas.on_context_menu(); } pub fn available_monitors(&self) -> VecDequeIter { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 8d60f49a..360c9895 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -30,6 +30,7 @@ pub struct Canvas { common: Common, id: WindowId, pub has_focus: Rc>, + pub prevent_default: Rc>, pub is_intersecting: Option, on_touch_start: Option>, on_focus: Option>, @@ -141,6 +142,7 @@ impl Canvas { common, id, has_focus: Rc::new(Cell::new(false)), + prevent_default: Rc::new(Cell::new(platform_attr.prevent_default)), is_intersecting: None, on_touch_start: None, on_blur: None, @@ -231,9 +233,10 @@ impl Canvas { &self.common.style } - pub fn on_touch_start(&mut self, prevent_default: bool) { + pub fn on_touch_start(&mut self) { + let prevent_default = Rc::clone(&self.prevent_default); self.on_touch_start = Some(self.common.add_event("touchstart", move |event: Event| { - if prevent_default { + if prevent_default.get() { event.prevent_default(); } })); @@ -257,13 +260,14 @@ impl Canvas { })); } - pub fn on_keyboard_release(&mut self, mut handler: F, prevent_default: bool) + pub fn on_keyboard_release(&mut self, mut handler: F) where F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, ModifiersState), { + let prevent_default = Rc::clone(&self.prevent_default); self.on_keyboard_release = Some(self.common.add_event("keyup", move |event: KeyboardEvent| { - if prevent_default { + if prevent_default.get() { event.prevent_default(); } let key = event::key(&event); @@ -279,14 +283,15 @@ impl Canvas { })); } - pub fn on_keyboard_press(&mut self, mut handler: F, prevent_default: bool) + pub fn on_keyboard_press(&mut self, mut handler: F) where F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, ModifiersState), { + let prevent_default = Rc::clone(&self.prevent_default); self.on_keyboard_press = Some(self.common.add_event( "keydown", move |event: KeyboardEvent| { - if prevent_default { + if prevent_default.get() { event.prevent_default(); } let key = event::key(&event); @@ -340,7 +345,6 @@ impl Canvas { modifier_handler: MOD, mouse_handler: M, touch_handler: T, - prevent_default: bool, ) where MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), @@ -351,7 +355,7 @@ impl Canvas { modifier_handler, mouse_handler, touch_handler, - prevent_default, + Rc::clone(&self.prevent_default), ) } @@ -361,7 +365,6 @@ impl Canvas { mouse_handler: M, touch_handler: T, button_handler: B, - prevent_default: bool, ) where MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), @@ -375,7 +378,7 @@ impl Canvas { mouse_handler, touch_handler, button_handler, - prevent_default, + Rc::clone(&self.prevent_default), ) } @@ -386,13 +389,14 @@ impl Canvas { self.pointer_handler.on_touch_cancel(&self.common, handler) } - pub fn on_mouse_wheel(&mut self, mut handler: F, prevent_default: bool) + pub fn on_mouse_wheel(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { let window = self.common.window.clone(); + let prevent_default = Rc::clone(&self.prevent_default); self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { - if prevent_default { + if prevent_default.get() { event.prevent_default(); } @@ -443,11 +447,12 @@ impl Canvas { self.animation_frame_handler.on_animation_frame(f) } - pub(crate) fn on_context_menu(&mut self, prevent_default: bool) { + pub(crate) fn on_context_menu(&mut self) { + let prevent_default = Rc::clone(&self.prevent_default); self.on_context_menu = Some(self.common.add_event( "contextmenu", move |event: PointerEvent| { - if prevent_default { + if prevent_default.get() { event.prevent_default(); } }, diff --git a/src/platform_impl/web/web_sys/pointer.rs b/src/platform_impl/web/web_sys/pointer.rs index dbfe3476..0c07c4ab 100644 --- a/src/platform_impl/web/web_sys/pointer.rs +++ b/src/platform_impl/web/web_sys/pointer.rs @@ -1,3 +1,6 @@ +use std::cell::Cell; +use std::rc::Rc; + use super::canvas::Common; use super::event; use super::event_handle::EventListenerHandle; @@ -110,7 +113,7 @@ impl PointerHandler { mut modifier_handler: MOD, mut mouse_handler: M, mut touch_handler: T, - prevent_default: bool, + prevent_default: Rc>, ) where MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), @@ -121,7 +124,7 @@ impl PointerHandler { self.on_pointer_press = Some(canvas_common.add_event( "pointerdown", move |event: PointerEvent| { - if prevent_default { + if prevent_default.get() { // prevent text selection event.prevent_default(); // but still focus element @@ -165,7 +168,7 @@ impl PointerHandler { mut mouse_handler: M, mut touch_handler: T, mut button_handler: B, - prevent_default: bool, + prevent_default: Rc>, ) where MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), @@ -197,7 +200,7 @@ impl PointerHandler { "expect pointer type of a chorded button event to be a mouse" ); - if prevent_default { + if prevent_default.get() { // prevent text selection event.prevent_default(); // but still focus element diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 56bfa49a..c47fc3c6 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -36,8 +36,6 @@ impl Window { ) -> Result { let id = target.generate_id(); - let prevent_default = platform_attr.prevent_default; - let window = target.runner.window(); let document = target.runner.document(); let canvas = @@ -45,7 +43,7 @@ impl Window { let canvas = Rc::new(RefCell::new(canvas)); let cursor = CursorState::new(canvas.borrow().style().clone()); - target.register(&canvas, id, prevent_default); + target.register(&canvas, id); let runner = target.runner.clone(); let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id))); @@ -84,6 +82,16 @@ impl Window { .map(|inner| inner.canvas.borrow().raw().clone()) } + pub(crate) fn prevent_default(&self) -> bool { + self.inner + .queue(|inner| inner.canvas.borrow().prevent_default.get()) + } + + pub(crate) fn set_prevent_default(&self, prevent_default: bool) { + self.inner + .dispatch(move |inner| inner.canvas.borrow().prevent_default.set(prevent_default)) + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result {