diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb080d3..dbc289ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ And please only add new entries to the top of this list, right below the `# Unre - Added `From` for `WindowId` and `From` for `u64`. - Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate. - **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision. +- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated. # 0.26.1 (2022-01-05) diff --git a/src/platform/web.rs b/src/platform/web.rs index 56017c8a..82e42db3 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -22,6 +22,17 @@ pub trait WindowExtWebSys { pub trait WindowBuilderExtWebSys { fn with_canvas(self, canvas: Option) -> Self; + + /// Whether `event.preventDefault` should be automatically called to prevent event propagation + /// when appropriate. + /// + /// For example, mouse wheel events are only handled by the canvas by default. This avoids + /// the default behavior of scrolling the page. + fn with_prevent_default(self, prevent_default: bool) -> Self; + + /// Whether the canvas should be focusable using the tab key. This is necessary to capture + /// canvas keyboard events. + fn with_focusable(self, focusable: bool) -> Self; } impl WindowBuilderExtWebSys for WindowBuilder { @@ -30,6 +41,18 @@ impl WindowBuilderExtWebSys for WindowBuilder { self } + + fn with_prevent_default(mut self, prevent_default: bool) -> Self { + self.platform_specific.prevent_default = prevent_default; + + self + } + + fn with_focusable(mut self, focusable: bool) -> Self { + self.platform_specific.focusable = focusable; + + self + } } /// Additional methods on `EventLoop` that are specific to the web. diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 43d30341..b9367629 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -50,7 +50,12 @@ impl EventLoopWindowTarget { WindowId(self.runner.generate_id()) } - pub fn register(&self, canvas: &Rc>, id: WindowId) { + pub fn register( + &self, + canvas: &Rc>, + id: WindowId, + prevent_default: bool, + ) { self.runner.add_canvas(RootWindowId(id), canvas); let mut canvas = canvas.borrow_mut(); canvas.set_attribute("data-raw-handle", &id.0.to_string()); @@ -72,48 +77,57 @@ impl EventLoopWindowTarget { }); let runner = self.runner.clone(); - canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| { - #[allow(deprecated)] - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::KeyboardInput { - device_id: RootDeviceId(unsafe { DeviceId::dummy() }), - input: KeyboardInput { - scancode, - state: ElementState::Pressed, - virtual_keycode, - modifiers, + canvas.on_keyboard_press( + move |scancode, virtual_keycode, modifiers| { + #[allow(deprecated)] + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::KeyboardInput { + device_id: RootDeviceId(unsafe { DeviceId::dummy() }), + input: KeyboardInput { + scancode, + state: ElementState::Pressed, + virtual_keycode, + modifiers, + }, + is_synthetic: false, }, - is_synthetic: false, - }, - }); - }); + }); + }, + prevent_default, + ); let runner = self.runner.clone(); - canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| { - #[allow(deprecated)] - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::KeyboardInput { - device_id: RootDeviceId(unsafe { DeviceId::dummy() }), - input: KeyboardInput { - scancode, - state: ElementState::Released, - virtual_keycode, - modifiers, + canvas.on_keyboard_release( + move |scancode, virtual_keycode, modifiers| { + #[allow(deprecated)] + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::KeyboardInput { + device_id: RootDeviceId(unsafe { DeviceId::dummy() }), + input: KeyboardInput { + scancode, + state: ElementState::Released, + virtual_keycode, + modifiers, + }, + is_synthetic: false, }, - is_synthetic: false, - }, - }); - }); + }); + }, + prevent_default, + ); let runner = self.runner.clone(); - canvas.on_received_character(move |char_code| { - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ReceivedCharacter(char_code), - }); - }); + canvas.on_received_character( + move |char_code| { + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ReceivedCharacter(char_code), + }); + }, + prevent_default, + ); let runner = self.runner.clone(); canvas.on_cursor_leave(move |pointer_id| { @@ -197,17 +211,20 @@ impl EventLoopWindowTarget { }); let runner = self.runner.clone(); - canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| { - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseWheel { - device_id: RootDeviceId(DeviceId(pointer_id)), - delta, - phase: TouchPhase::Moved, - modifiers, - }, - }); - }); + canvas.on_mouse_wheel( + move |pointer_id, delta, modifiers| { + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseWheel { + device_id: RootDeviceId(DeviceId(pointer_id)), + delta, + phase: TouchPhase::Moved, + modifiers, + }, + }); + }, + prevent_default, + ); let runner = self.runner.clone(); let raw = canvas.raw().clone(); diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 521e88a8..ebae6722 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -62,9 +62,11 @@ impl Canvas { // sequential keyboard navigation, but its order is defined by the // document's source order. // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex - canvas - .set_attribute("tabindex", "0") - .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; + if attr.focusable { + 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()) @@ -148,14 +150,17 @@ impl Canvas { })); } - pub fn on_keyboard_release(&mut self, mut handler: F) + pub fn on_keyboard_release(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { self.on_keyboard_release = Some(self.common.add_user_event( "keyup", move |event: KeyboardEvent| { - event.prevent_default(); + if prevent_default { + event.prevent_default(); + } + handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -165,7 +170,7 @@ impl Canvas { )); } - pub fn on_keyboard_press(&mut self, mut handler: F) + pub fn on_keyboard_press(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { @@ -173,16 +178,19 @@ impl Canvas { "keydown", move |event: KeyboardEvent| { // event.prevent_default() would suppress subsequent on_received_character() calls. That - // supression is correct for key sequences like Tab/Shift-Tab, Ctrl+R, PgUp/Down to + // suppression is correct for key sequences like Tab/Shift-Tab, Ctrl+R, PgUp/Down to // scroll, etc. We should not do it for key sequences that result in meaningful character // input though. - let event_key = &event.key(); - let is_key_string = event_key.len() == 1 || !event_key.is_ascii(); - let is_shortcut_modifiers = - (event.ctrl_key() || event.alt_key()) && !event.get_modifier_state("AltGr"); - if !is_key_string || is_shortcut_modifiers { - event.prevent_default(); + if prevent_default { + let event_key = &event.key(); + let is_key_string = event_key.len() == 1 || !event_key.is_ascii(); + let is_shortcut_modifiers = + (event.ctrl_key() || event.alt_key()) && !event.get_modifier_state("AltGr"); + if !is_key_string || is_shortcut_modifiers { + event.prevent_default(); + } } + handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -192,7 +200,7 @@ impl Canvas { )); } - pub fn on_received_character(&mut self, mut handler: F) + pub fn on_received_character(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(char), { @@ -204,8 +212,11 @@ impl Canvas { self.on_received_character = Some(self.common.add_user_event( "keypress", move |event: KeyboardEvent| { - // Supress further handling to stop keys like the space key from scrolling the page. - event.prevent_default(); + // Suppress further handling to stop keys like the space key from scrolling the page. + if prevent_default { + event.prevent_default(); + } + handler(event::codepoint(&event)); }, )); @@ -261,12 +272,15 @@ impl Canvas { } } - pub fn on_mouse_wheel(&mut self, mut handler: F) + pub fn on_mouse_wheel(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { - event.prevent_default(); + if prevent_default { + event.prevent_default(); + } + if let Some(delta) = event::mouse_scroll_delta(&event) { handler(0, delta, event::mouse_modifiers(&event)); } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 78299c8c..fe1d4b22 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -35,12 +35,14 @@ impl Window { let id = target.generate_id(); + let prevent_default = platform_attr.prevent_default; + let canvas = backend::Canvas::create(platform_attr)?; let canvas = Rc::new(RefCell::new(canvas)); let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); - target.register(&canvas, id); + target.register(&canvas, id, prevent_default); let runner = target.runner.clone(); let resize_notify_fn = Box::new(move |new_size| { @@ -392,7 +394,19 @@ impl From for WindowId { } } -#[derive(Default, Clone)] +#[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub(crate) canvas: Option, + pub(crate) prevent_default: bool, + pub(crate) focusable: bool, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + fn default() -> Self { + Self { + canvas: None, + prevent_default: true, + focusable: true, + } + } }