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

On Web, implement DeviceEvents #2871

Merged
merged 6 commits into from
Jun 14, 2023
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Web, use `Window.requestIdleCallback()` for `ControlFlow::Poll` when available.
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to
the canvas size will be reported through `WindowEvent::Resized`.
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and
`DeviceEvent::Key` support.

# 0.28.6

Expand Down
4 changes: 2 additions & 2 deletions src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,11 @@ impl<T> EventLoopWindowTarget<T> {
///
/// ## Platform-specific
///
/// - **Wayland / macOS / iOS / Android / Web / Orbital:** Unsupported.
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
#[cfg(any(x11_platform, wayland_platform, windows))]
#[cfg(any(x11_platform, wasm_platform, wayland_platform, windows))]
self.p.listen_device_events(_allowed);
}
}
Expand Down
231 changes: 228 additions & 3 deletions src/platform_impl/web/event_loop/runner.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
use super::super::DeviceId;
use super::{backend, state::State};
use crate::dpi::PhysicalSize;
use crate::event::{Event, StartCause};
use crate::event_loop::ControlFlow;
use crate::event::{
DeviceEvent, DeviceId as RootDeviceId, ElementState, Event, RawKeyEvent, StartCause,
};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::platform_impl::platform::backend::EventListenerHandle;
use crate::window::WindowId;

use std::sync::atomic::Ordering;
use std::{
cell::RefCell,
cell::{Cell, RefCell},
clone::Clone,
collections::{HashSet, VecDeque},
iter,
ops::Deref,
rc::{Rc, Weak},
};
use wasm_bindgen::prelude::Closure;
use web_sys::{KeyboardEvent, PointerEvent, WheelEvent};
use web_time::{Duration, Instant};

pub struct Shared<T: 'static>(Rc<Execution<T>>);
Expand All @@ -24,6 +31,8 @@ impl<T> Clone for Shared<T> {
}
}

type OnEventHandle<T> = RefCell<Option<EventListenerHandle<dyn FnMut(T)>>>;

pub struct Execution<T: 'static> {
runner: RefCell<RunnerEnum<T>>,
events: RefCell<VecDeque<EventWrapper<T>>>,
Expand All @@ -33,6 +42,13 @@ pub struct Execution<T: 'static> {
redraw_pending: RefCell<HashSet<WindowId>>,
destroy_pending: RefCell<VecDeque<WindowId>>,
unload_event_handle: RefCell<Option<backend::UnloadEventHandle>>,
device_events: Cell<DeviceEvents>,
on_mouse_move: OnEventHandle<PointerEvent>,
on_wheel: OnEventHandle<WheelEvent>,
on_mouse_press: OnEventHandle<PointerEvent>,
on_mouse_release: OnEventHandle<PointerEvent>,
on_key_press: OnEventHandle<KeyboardEvent>,
on_key_release: OnEventHandle<KeyboardEvent>,
}

enum RunnerEnum<T: 'static> {
Expand Down Expand Up @@ -131,6 +147,13 @@ impl<T: 'static> Shared<T> {
redraw_pending: RefCell::new(HashSet::new()),
destroy_pending: RefCell::new(VecDeque::new()),
unload_event_handle: RefCell::new(None),
device_events: Cell::default(),
on_mouse_move: RefCell::new(None),
on_wheel: RefCell::new(None),
on_mouse_press: RefCell::new(None),
on_mouse_release: RefCell::new(None),
on_key_press: RefCell::new(None),
on_key_release: RefCell::new(None),
}))
}

Expand Down Expand Up @@ -165,6 +188,184 @@ impl<T: 'static> Shared<T> {
Some(backend::on_unload(self.window(), move || {
close_instance.handle_unload()
}));

let runner = self.clone();
let window = self.window().clone();
*self.0.on_mouse_move.borrow_mut() = Some(EventListenerHandle::new(
self.window(),
"pointermove",
Closure::new(move |event: PointerEvent| {
if !runner.device_events() {
return;
}

let pointer_type = event.pointer_type();

if pointer_type != "mouse" {
return;
}

// chorded button event
let device_id = RootDeviceId(DeviceId(event.pointer_id()));

if let Some(button) = backend::event::mouse_button(&event) {
debug_assert_eq!(
pointer_type, "mouse",
"expect pointer type of a chorded button event to be a mouse"
);

let state = if backend::event::mouse_buttons(&event).contains(button.into()) {
ElementState::Pressed
} else {
ElementState::Released
};

runner.send_event(Event::DeviceEvent {
device_id,
event: DeviceEvent::Button {
button: button.to_id(),
state,
},
});

return;
}

// pointer move event
let mut delta = backend::event::MouseDelta::init(&window, &event);
runner.send_events(backend::event::pointer_move_event(event).flat_map(|event| {
let delta = delta
.delta(&event)
.to_physical(backend::scale_factor(&window));

let x_motion = (delta.x != 0.0).then_some(Event::DeviceEvent {
device_id,
event: DeviceEvent::Motion {
axis: 0,
value: delta.x,
},
});

let y_motion = (delta.y != 0.0).then_some(Event::DeviceEvent {
device_id,
event: DeviceEvent::Motion {
axis: 1,
value: delta.y,
},
});

x_motion
.into_iter()
.chain(y_motion)
.chain(iter::once(Event::DeviceEvent {
device_id,
event: DeviceEvent::MouseMotion {
delta: (delta.x, delta.y),
},
}))
}));
}),
));
let runner = self.clone();
let window = self.window().clone();
*self.0.on_wheel.borrow_mut() = Some(EventListenerHandle::new(
self.window(),
"wheel",
Closure::new(move |event: WheelEvent| {
if !runner.device_events() {
return;
}

if let Some(delta) = backend::event::mouse_scroll_delta(&window, &event) {
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId(0)),
event: DeviceEvent::MouseWheel { delta },
});
}
}),
));
let runner = self.clone();
*self.0.on_mouse_press.borrow_mut() = Some(EventListenerHandle::new(
self.window(),
"pointerdown",
Closure::new(move |event: PointerEvent| {
if !runner.device_events() {
return;
}

if event.pointer_type() != "mouse" {
return;
}

let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId(event.pointer_id())),
event: DeviceEvent::Button {
button: button.to_id(),
state: ElementState::Pressed,
},
});
}),
));
let runner = self.clone();
*self.0.on_mouse_release.borrow_mut() = Some(EventListenerHandle::new(
self.window(),
"pointerup",
Closure::new(move |event: PointerEvent| {
if !runner.device_events() {
return;
}

if event.pointer_type() != "mouse" {
return;
}

let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId(event.pointer_id())),
event: DeviceEvent::Button {
button: button.to_id(),
state: ElementState::Released,
},
});
}),
));
let runner = self.clone();
*self.0.on_key_press.borrow_mut() = Some(EventListenerHandle::new(
self.window(),
"keydown",
Closure::new(move |event: KeyboardEvent| {
if !runner.device_events() {
return;
}

runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(unsafe { DeviceId::dummy() }),
event: DeviceEvent::Key(RawKeyEvent {
physical_key: backend::event::key_code(&event),
state: ElementState::Pressed,
}),
});
}),
));
let runner = self.clone();
*self.0.on_key_release.borrow_mut() = Some(EventListenerHandle::new(
self.window(),
"keyup",
Closure::new(move |event: KeyboardEvent| {
if !runner.device_events() {
return;
}

runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(unsafe { DeviceId::dummy() }),
event: DeviceEvent::Key(RawKeyEvent {
physical_key: backend::event::key_code(&event),
state: ElementState::Released,
}),
});
}),
));
}

// Generate a strictly increasing ID
Expand Down Expand Up @@ -405,6 +606,12 @@ impl<T: 'static> Shared<T> {
self.handle_event(Event::LoopDestroyed, control);
let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut());
*self.0.unload_event_handle.borrow_mut() = None;
*self.0.on_mouse_move.borrow_mut() = None;
*self.0.on_wheel.borrow_mut() = None;
*self.0.on_mouse_press.borrow_mut() = None;
*self.0.on_mouse_release.borrow_mut() = None;
*self.0.on_key_press.borrow_mut() = None;
*self.0.on_key_release.borrow_mut() = None;
// Dropping the `Runner` drops the event handler closure, which will in
// turn drop all `Window`s moved into the closure.
*self.0.runner.borrow_mut() = RunnerEnum::Destroyed;
Expand Down Expand Up @@ -450,6 +657,24 @@ impl<T: 'static> Shared<T> {
RunnerEnum::Destroyed => ControlFlow::Exit,
}
}

pub fn listen_device_events(&self, allowed: DeviceEvents) {
self.0.device_events.set(allowed)
}

pub fn device_events(&self) -> bool {
match self.0.device_events.get() {
DeviceEvents::Always => true,
DeviceEvents::WhenFocused => self.0.all_canvases.borrow().iter().any(|(_, canvas)| {
if let Some(canvas) = canvas.upgrade() {
canvas.borrow().has_focus.load(Ordering::Relaxed)
} else {
false
}
}),
DeviceEvents::Never => false,
}
}
}

pub(crate) enum EventWrapper<T: 'static> {
Expand Down
Loading