Skip to content

Commit

Permalink
web: add with_prevent_default, with_focusable (#2365)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
grovesNL authored Jul 14, 2022
1 parent 472d7b9 commit 990e34a
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 68 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `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)

Expand Down
23 changes: 23 additions & 0 deletions src/platform/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ pub trait WindowExtWebSys {

pub trait WindowBuilderExtWebSys {
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 {
Expand All @@ -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.
Expand Down
113 changes: 65 additions & 48 deletions src/platform_impl/web/event_loop/window_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ impl<T> EventLoopWindowTarget<T> {
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);
let mut canvas = canvas.borrow_mut();
canvas.set_attribute("data-raw-handle", &id.0.to_string());
Expand All @@ -72,48 +77,57 @@ impl<T> EventLoopWindowTarget<T> {
});

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| {
Expand Down Expand Up @@ -197,17 +211,20 @@ impl<T> EventLoopWindowTarget<T> {
});

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();
Expand Down
50 changes: 32 additions & 18 deletions src/platform_impl/web/web_sys/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, 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),
Expand All @@ -165,24 +170,27 @@ 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
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_press = Some(self.common.add_user_event(
"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),
Expand All @@ -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
F: 'static + FnMut(char),
{
Expand All @@ -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));
},
));
Expand Down Expand Up @@ -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
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));
}
Expand Down
18 changes: 16 additions & 2 deletions src/platform_impl/web/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down Expand Up @@ -392,7 +394,19 @@ impl From<u64> for WindowId {
}
}

#[derive(Default, Clone)]
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
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,
}
}
}

0 comments on commit 990e34a

Please sign in to comment.