Skip to content

Commit

Permalink
On Web, implement DeviceEvents (#2871)
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda authored Jun 14, 2023
1 parent 9a9c9b1 commit 6300cf9
Show file tree
Hide file tree
Showing 8 changed files with 405 additions and 85 deletions.
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

0 comments on commit 6300cf9

Please sign in to comment.