Impl. mouse capturing on web target (#1672)

* Impl. mouse capturing for web-sys with PointerEvent

* Impl. mouse capturing for web-sys with MouseEvent by manual tracking

* Reorganize web-sys backend mouse and pointer handling code

* Impl. mouse capturing for stdweb with PointerEvent

* Add mouse capturing for web target to changelog
This commit is contained in:
alvinhochun 2020-08-29 21:34:33 +08:00 committed by GitHub
parent bea60930b6
commit 02a34a167a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 446 additions and 129 deletions

View file

@ -9,36 +9,33 @@ use std::rc::Rc;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{
Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, MouseEvent,
PointerEvent, WheelEvent,
AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
MediaQueryListEvent, MouseEvent, WheelEvent,
};
mod mouse_handler;
mod pointer_handler;
pub struct Canvas {
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
raw: HtmlCanvasElement,
common: Common,
on_focus: Option<Closure<dyn FnMut(FocusEvent)>>,
on_blur: Option<Closure<dyn FnMut(FocusEvent)>>,
on_keyboard_release: Option<Closure<dyn FnMut(KeyboardEvent)>>,
on_keyboard_press: Option<Closure<dyn FnMut(KeyboardEvent)>>,
on_received_character: Option<Closure<dyn FnMut(KeyboardEvent)>>,
on_cursor_leave: Option<Closure<dyn FnMut(PointerEvent)>>,
on_cursor_enter: Option<Closure<dyn FnMut(PointerEvent)>>,
on_cursor_move: Option<Closure<dyn FnMut(PointerEvent)>>,
on_pointer_press: Option<Closure<dyn FnMut(PointerEvent)>>,
on_pointer_release: Option<Closure<dyn FnMut(PointerEvent)>>,
// Fallback events when pointer event support is missing
on_mouse_leave: Option<Closure<dyn FnMut(MouseEvent)>>,
on_mouse_enter: Option<Closure<dyn FnMut(MouseEvent)>>,
on_mouse_move: Option<Closure<dyn FnMut(MouseEvent)>>,
on_mouse_press: Option<Closure<dyn FnMut(MouseEvent)>>,
on_mouse_release: Option<Closure<dyn FnMut(MouseEvent)>>,
on_mouse_wheel: Option<Closure<dyn FnMut(WheelEvent)>>,
on_fullscreen_change: Option<Closure<dyn FnMut(Event)>>,
wants_fullscreen: Rc<RefCell<bool>>,
on_dark_mode: Option<Closure<dyn FnMut(MediaQueryListEvent)>>,
mouse_state: MouseState,
}
impl Drop for Canvas {
struct Common {
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
raw: HtmlCanvasElement,
wants_fullscreen: Rc<RefCell<bool>>,
}
impl Drop for Common {
fn drop(&mut self) {
self.raw.remove();
}
@ -72,38 +69,38 @@ impl 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())
} else {
MouseState::NoPointerEvent(mouse_handler::MouseHandler::new())
};
Ok(Canvas {
raw: canvas,
common: Common {
raw: canvas,
wants_fullscreen: Rc::new(RefCell::new(false)),
},
on_blur: None,
on_focus: None,
on_keyboard_release: None,
on_keyboard_press: None,
on_received_character: None,
on_cursor_leave: None,
on_cursor_enter: None,
on_cursor_move: None,
on_pointer_release: None,
on_pointer_press: None,
on_mouse_leave: None,
on_mouse_enter: None,
on_mouse_move: None,
on_mouse_press: None,
on_mouse_release: None,
on_mouse_wheel: None,
on_fullscreen_change: None,
wants_fullscreen: Rc::new(RefCell::new(false)),
on_dark_mode: None,
mouse_state,
})
}
pub fn set_attribute(&self, attribute: &str, value: &str) {
self.raw
self.common
.raw
.set_attribute(attribute, value)
.expect(&format!("Set attribute: {}", attribute));
}
pub fn position(&self) -> LogicalPosition<f64> {
let bounds = self.raw.get_bounding_client_rect();
let bounds = self.common.raw.get_bounding_client_rect();
LogicalPosition {
x: bounds.x(),
@ -113,20 +110,20 @@ impl Canvas {
pub fn size(&self) -> PhysicalSize<u32> {
PhysicalSize {
width: self.raw.width(),
height: self.raw.height(),
width: self.common.raw.width(),
height: self.common.raw.height(),
}
}
pub fn raw(&self) -> &HtmlCanvasElement {
&self.raw
&self.common.raw
}
pub fn on_blur<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(),
{
self.on_blur = Some(self.add_event("blur", move |_: FocusEvent| {
self.on_blur = Some(self.common.add_event("blur", move |_: FocusEvent| {
handler();
}));
}
@ -135,7 +132,7 @@ impl Canvas {
where
F: 'static + FnMut(),
{
self.on_focus = Some(self.add_event("focus", move |_: FocusEvent| {
self.on_focus = Some(self.common.add_event("focus", move |_: FocusEvent| {
handler();
}));
}
@ -144,30 +141,34 @@ impl Canvas {
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_release =
Some(self.add_user_event("keyup", move |event: KeyboardEvent| {
self.on_keyboard_release = Some(self.common.add_user_event(
"keyup",
move |event: KeyboardEvent| {
event.prevent_default();
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
}));
},
));
}
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
where
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
{
self.on_keyboard_press =
Some(self.add_user_event("keydown", move |event: KeyboardEvent| {
self.on_keyboard_press = Some(self.common.add_user_event(
"keydown",
move |event: KeyboardEvent| {
event.prevent_default();
handler(
event::scan_code(&event),
event::virtual_key_code(&event),
event::keyboard_modifiers(&event),
);
}));
},
));
}
pub fn on_received_character<F>(&mut self, mut handler: F)
@ -179,7 +180,7 @@ impl Canvas {
// The `keypress` event is deprecated, but there does not seem to be a
// viable/compatible alternative as of now. `beforeinput` is still widely
// unsupported.
self.on_received_character = Some(self.add_user_event(
self.on_received_character = Some(self.common.add_user_event(
"keypress",
move |event: KeyboardEvent| {
handler(event::codepoint(&event));
@ -187,115 +188,53 @@ impl Canvas {
));
}
pub fn on_cursor_leave<F>(&mut self, mut handler: F)
pub fn on_cursor_leave<F>(&mut self, handler: F)
where
F: 'static + FnMut(i32),
{
if has_pointer_event() {
self.on_cursor_leave =
Some(self.add_event("pointerout", move |event: PointerEvent| {
handler(event.pointer_id());
}));
} else {
self.on_mouse_leave = Some(self.add_event("mouseout", move |_: MouseEvent| {
handler(0);
}));
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => h.on_cursor_leave(&self.common, handler),
MouseState::NoPointerEvent(h) => h.on_cursor_leave(&self.common, handler),
}
}
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
pub fn on_cursor_enter<F>(&mut self, handler: F)
where
F: 'static + FnMut(i32),
{
if has_pointer_event() {
self.on_cursor_enter =
Some(self.add_event("pointerover", move |event: PointerEvent| {
handler(event.pointer_id());
}));
} else {
self.on_mouse_enter = Some(self.add_event("mouseover", move |_: MouseEvent| {
handler(0);
}));
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => h.on_cursor_enter(&self.common, handler),
MouseState::NoPointerEvent(h) => h.on_cursor_enter(&self.common, handler),
}
}
pub fn on_mouse_release<F>(&mut self, mut handler: F)
pub fn on_mouse_release<F>(&mut self, handler: F)
where
F: 'static + FnMut(i32, MouseButton, ModifiersState),
{
if has_pointer_event() {
self.on_pointer_release = Some(self.add_user_event(
"pointerup",
move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
},
));
} else {
self.on_mouse_release =
Some(self.add_user_event("mouseup", move |event: MouseEvent| {
handler(
0,
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
}));
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => h.on_mouse_release(&self.common, handler),
MouseState::NoPointerEvent(h) => h.on_mouse_release(&self.common, handler),
}
}
pub fn on_mouse_press<F>(&mut self, mut handler: F)
pub fn on_mouse_press<F>(&mut self, handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton, ModifiersState),
{
if has_pointer_event() {
self.on_pointer_press = Some(self.add_user_event(
"pointerdown",
move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event).to_physical(super::scale_factor()),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
},
));
} else {
self.on_mouse_press =
Some(self.add_user_event("mousedown", move |event: MouseEvent| {
handler(
0,
event::mouse_position(&event).to_physical(super::scale_factor()),
event::mouse_button(&event),
event::mouse_modifiers(&event),
);
}));
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => h.on_mouse_press(&self.common, handler),
MouseState::NoPointerEvent(h) => h.on_mouse_press(&self.common, handler),
}
}
pub fn on_cursor_move<F>(&mut self, mut handler: F)
pub fn on_cursor_move<F>(&mut self, handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
{
if has_pointer_event() {
self.on_cursor_move =
Some(self.add_event("pointermove", move |event: PointerEvent| {
handler(
event.pointer_id(),
event::mouse_position(&event).to_physical(super::scale_factor()),
event::mouse_modifiers(&event),
);
}));
} else {
self.on_mouse_move = Some(self.add_event("mousemove", move |event: MouseEvent| {
handler(
0,
event::mouse_position(&event).to_physical(super::scale_factor()),
event::mouse_modifiers(&event),
);
}));
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => h.on_cursor_move(&self.common, handler),
MouseState::NoPointerEvent(h) => h.on_cursor_move(&self.common, handler),
}
}
@ -303,7 +242,7 @@ impl Canvas {
where
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
{
self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| {
self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| {
event.prevent_default();
if let Some(delta) = event::mouse_scroll_delta(&event) {
handler(0, delta, event::mouse_modifiers(&event));
@ -315,8 +254,10 @@ impl Canvas {
where
F: 'static + FnMut(),
{
self.on_fullscreen_change =
Some(self.add_event("fullscreenchange", move |_: Event| handler()));
self.on_fullscreen_change = Some(
self.common
.add_event("fullscreenchange", move |_: Event| handler()),
);
}
pub fn on_dark_mode<F>(&mut self, mut handler: F)
@ -341,6 +282,16 @@ impl Canvas {
});
}
pub fn request_fullscreen(&self) {
self.common.request_fullscreen()
}
pub fn is_fullscreen(&self) -> bool {
self.common.is_fullscreen()
}
}
impl Common {
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
where
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
@ -386,6 +337,44 @@ impl Canvas {
})
}
// This function is used exclusively for mouse events (not pointer events).
// Due to the need for mouse capturing, the mouse event handlers are added
// to the window instead of the canvas element, which requires special
// handling to control event propagation.
fn add_window_mouse_event<F>(
&self,
event_name: &str,
mut handler: F,
) -> Closure<dyn FnMut(MouseEvent)>
where
F: 'static + FnMut(MouseEvent),
{
let wants_fullscreen = self.wants_fullscreen.clone();
let canvas = self.raw.clone();
let window = web_sys::window().expect("Failed to obtain window");
let closure = Closure::wrap(Box::new(move |event: MouseEvent| {
handler(event);
if *wants_fullscreen.borrow() {
canvas
.request_fullscreen()
.expect("Failed to enter fullscreen");
*wants_fullscreen.borrow_mut() = false;
}
}) as Box<dyn FnMut(_)>);
window
.add_event_listener_with_callback_and_add_event_listener_options(
event_name,
&closure.as_ref().unchecked_ref(),
AddEventListenerOptions::new().capture(true),
)
.expect("Failed to add event listener with callback and options");
closure
}
pub fn request_fullscreen(&self) {
*self.wants_fullscreen.borrow_mut() = true;
}
@ -395,6 +384,11 @@ impl Canvas {
}
}
enum MouseState {
HasPointerEvent(pointer_handler::PointerHandler),
NoPointerEvent(mouse_handler::MouseHandler),
}
/// Returns whether pointer events are supported.
/// Used to decide whether to use pointer events
/// or plain mouse events. Note that Safari