Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

web: add with_prevent_default, with_focusable #2365

Merged
merged 2 commits into from
Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ And please only add new entries to the top of this list, right below the `# Unre
- Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`.
- **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior.
- On Wayland, add support for `Window::set_cursor_position`.
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
grovesNL marked this conversation as resolved.
Show resolved Hide resolved
- `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once.
- 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,
}
}
}