web: add with_prevent_default, with_focusable (#2365)

* web: add `with_prevent_default`, `with_focusable`

`with_prevent_default` controls whether `event.preventDefault` is called

`with_focusable` controls whether `tabindex` is added

Fixes #1768

* Remove extra space from CHANGELOG
This commit is contained in:
Josh Groves 2022-07-14 13:22:31 -02:30 committed by GitHub
parent 472d7b9376
commit 990e34a129
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 137 additions and 68 deletions

View file

@ -70,6 +70,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- Added `From<u64>` for `WindowId` and `From<WindowId>` for `u64`. - Added `From<u64>` for `WindowId` and `From<WindowId>` for `u64`.
- Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate. - Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate.
- **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision. - **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) # 0.26.1 (2022-01-05)

View file

@ -22,6 +22,17 @@ pub trait WindowExtWebSys {
pub trait WindowBuilderExtWebSys { pub trait WindowBuilderExtWebSys {
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self; fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> 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 { impl WindowBuilderExtWebSys for WindowBuilder {
@ -30,6 +41,18 @@ impl WindowBuilderExtWebSys for WindowBuilder {
self 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. /// Additional methods on `EventLoop` that are specific to the web.

View file

@ -50,7 +50,12 @@ impl<T> EventLoopWindowTarget<T> {
WindowId(self.runner.generate_id()) WindowId(self.runner.generate_id())
} }
pub fn register(&self, canvas: &Rc<RefCell<backend::Canvas>>, id: WindowId) { pub fn register(
&self,
canvas: &Rc<RefCell<backend::Canvas>>,
id: WindowId,
prevent_default: bool,
) {
self.runner.add_canvas(RootWindowId(id), canvas); self.runner.add_canvas(RootWindowId(id), canvas);
let mut canvas = canvas.borrow_mut(); let mut canvas = canvas.borrow_mut();
canvas.set_attribute("data-raw-handle", &id.0.to_string()); canvas.set_attribute("data-raw-handle", &id.0.to_string());
@ -72,48 +77,57 @@ impl<T> EventLoopWindowTarget<T> {
}); });
let runner = self.runner.clone(); let runner = self.runner.clone();
canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| { canvas.on_keyboard_press(
#[allow(deprecated)] move |scancode, virtual_keycode, modifiers| {
runner.send_event(Event::WindowEvent { #[allow(deprecated)]
window_id: RootWindowId(id), runner.send_event(Event::WindowEvent {
event: WindowEvent::KeyboardInput { window_id: RootWindowId(id),
device_id: RootDeviceId(unsafe { DeviceId::dummy() }), event: WindowEvent::KeyboardInput {
input: KeyboardInput { device_id: RootDeviceId(unsafe { DeviceId::dummy() }),
scancode, input: KeyboardInput {
state: ElementState::Pressed, scancode,
virtual_keycode, state: ElementState::Pressed,
modifiers, virtual_keycode,
modifiers,
},
is_synthetic: false,
}, },
is_synthetic: false, });
}, },
}); prevent_default,
}); );
let runner = self.runner.clone(); let runner = self.runner.clone();
canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| { canvas.on_keyboard_release(
#[allow(deprecated)] move |scancode, virtual_keycode, modifiers| {
runner.send_event(Event::WindowEvent { #[allow(deprecated)]
window_id: RootWindowId(id), runner.send_event(Event::WindowEvent {
event: WindowEvent::KeyboardInput { window_id: RootWindowId(id),
device_id: RootDeviceId(unsafe { DeviceId::dummy() }), event: WindowEvent::KeyboardInput {
input: KeyboardInput { device_id: RootDeviceId(unsafe { DeviceId::dummy() }),
scancode, input: KeyboardInput {
state: ElementState::Released, scancode,
virtual_keycode, state: ElementState::Released,
modifiers, virtual_keycode,
modifiers,
},
is_synthetic: false,
}, },
is_synthetic: false, });
}, },
}); prevent_default,
}); );
let runner = self.runner.clone(); let runner = self.runner.clone();
canvas.on_received_character(move |char_code| { canvas.on_received_character(
runner.send_event(Event::WindowEvent { move |char_code| {
window_id: RootWindowId(id), runner.send_event(Event::WindowEvent {
event: WindowEvent::ReceivedCharacter(char_code), window_id: RootWindowId(id),
}); event: WindowEvent::ReceivedCharacter(char_code),
}); });
},
prevent_default,
);
let runner = self.runner.clone(); let runner = self.runner.clone();
canvas.on_cursor_leave(move |pointer_id| { canvas.on_cursor_leave(move |pointer_id| {
@ -197,17 +211,20 @@ impl<T> EventLoopWindowTarget<T> {
}); });
let runner = self.runner.clone(); let runner = self.runner.clone();
canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| { canvas.on_mouse_wheel(
runner.send_event(Event::WindowEvent { move |pointer_id, delta, modifiers| {
window_id: RootWindowId(id), runner.send_event(Event::WindowEvent {
event: WindowEvent::MouseWheel { window_id: RootWindowId(id),
device_id: RootDeviceId(DeviceId(pointer_id)), event: WindowEvent::MouseWheel {
delta, device_id: RootDeviceId(DeviceId(pointer_id)),
phase: TouchPhase::Moved, delta,
modifiers, phase: TouchPhase::Moved,
}, modifiers,
}); },
}); });
},
prevent_default,
);
let runner = self.runner.clone(); let runner = self.runner.clone();
let raw = canvas.raw().clone(); let raw = canvas.raw().clone();

View file

@ -62,9 +62,11 @@ impl Canvas {
// sequential keyboard navigation, but its order is defined by the // sequential keyboard navigation, but its order is defined by the
// document's source order. // document's source order.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
canvas if attr.focusable {
.set_attribute("tabindex", "0") canvas
.map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; .set_attribute("tabindex", "0")
.map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?;
}
let mouse_state = if has_pointer_event() { let mouse_state = if has_pointer_event() {
MouseState::HasPointerEvent(pointer_handler::PointerHandler::new()) MouseState::HasPointerEvent(pointer_handler::PointerHandler::new())
@ -148,14 +150,17 @@ impl Canvas {
})); }));
} }
pub fn on_keyboard_release<F>(&mut self, mut handler: F) pub fn on_keyboard_release<F>(&mut self, mut handler: F, prevent_default: bool)
where where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState), F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{ {
self.on_keyboard_release = Some(self.common.add_user_event( self.on_keyboard_release = Some(self.common.add_user_event(
"keyup", "keyup",
move |event: KeyboardEvent| { move |event: KeyboardEvent| {
event.prevent_default(); if prevent_default {
event.prevent_default();
}
handler( handler(
event::scan_code(&event), event::scan_code(&event),
event::virtual_key_code(&event), event::virtual_key_code(&event),
@ -165,7 +170,7 @@ impl Canvas {
)); ));
} }
pub fn on_keyboard_press<F>(&mut self, mut handler: F) pub fn on_keyboard_press<F>(&mut self, mut handler: F, prevent_default: bool)
where where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState), F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{ {
@ -173,16 +178,19 @@ impl Canvas {
"keydown", "keydown",
move |event: KeyboardEvent| { move |event: KeyboardEvent| {
// event.prevent_default() would suppress subsequent on_received_character() calls. That // 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 // scroll, etc. We should not do it for key sequences that result in meaningful character
// input though. // input though.
let event_key = &event.key(); if prevent_default {
let is_key_string = event_key.len() == 1 || !event_key.is_ascii(); let event_key = &event.key();
let is_shortcut_modifiers = let is_key_string = event_key.len() == 1 || !event_key.is_ascii();
(event.ctrl_key() || event.alt_key()) && !event.get_modifier_state("AltGr"); let is_shortcut_modifiers =
if !is_key_string || is_shortcut_modifiers { (event.ctrl_key() || event.alt_key()) && !event.get_modifier_state("AltGr");
event.prevent_default(); if !is_key_string || is_shortcut_modifiers {
event.prevent_default();
}
} }
handler( handler(
event::scan_code(&event), event::scan_code(&event),
event::virtual_key_code(&event), event::virtual_key_code(&event),
@ -192,7 +200,7 @@ impl Canvas {
)); ));
} }
pub fn on_received_character<F>(&mut self, mut handler: F) pub fn on_received_character<F>(&mut self, mut handler: F, prevent_default: bool)
where where
F: 'static + FnMut(char), F: 'static + FnMut(char),
{ {
@ -204,8 +212,11 @@ impl Canvas {
self.on_received_character = Some(self.common.add_user_event( self.on_received_character = Some(self.common.add_user_event(
"keypress", "keypress",
move |event: KeyboardEvent| { move |event: KeyboardEvent| {
// Supress further handling to stop keys like the space key from scrolling the page. // Suppress further handling to stop keys like the space key from scrolling the page.
event.prevent_default(); if prevent_default {
event.prevent_default();
}
handler(event::codepoint(&event)); handler(event::codepoint(&event));
}, },
)); ));
@ -261,12 +272,15 @@ impl Canvas {
} }
} }
pub fn on_mouse_wheel<F>(&mut self, mut handler: F) pub fn on_mouse_wheel<F>(&mut self, mut handler: F, prevent_default: bool)
where where
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
{ {
self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { 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) { if let Some(delta) = event::mouse_scroll_delta(&event) {
handler(0, delta, event::mouse_modifiers(&event)); handler(0, delta, event::mouse_modifiers(&event));
} }

View file

@ -35,12 +35,14 @@ impl Window {
let id = target.generate_id(); let id = target.generate_id();
let prevent_default = platform_attr.prevent_default;
let canvas = backend::Canvas::create(platform_attr)?; let canvas = backend::Canvas::create(platform_attr)?;
let canvas = Rc::new(RefCell::new(canvas)); let canvas = Rc::new(RefCell::new(canvas));
let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); 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 runner = target.runner.clone();
let resize_notify_fn = Box::new(move |new_size| { let resize_notify_fn = Box::new(move |new_size| {
@ -392,7 +394,19 @@ impl From<u64> for WindowId {
} }
} }
#[derive(Default, Clone)] #[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes { pub struct PlatformSpecificWindowBuilderAttributes {
pub(crate) canvas: Option<backend::RawCanvasType>, pub(crate) canvas: Option<backend::RawCanvasType>,
pub(crate) prevent_default: bool,
pub(crate) focusable: bool,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
Self {
canvas: None,
prevent_default: true,
focusable: true,
}
}
} }