mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-24 06:11:30 +11:00
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:
parent
bea60930b6
commit
02a34a167a
|
@ -19,6 +19,7 @@
|
||||||
- On Android, fix `ControlFlow::Poll` not polling the Android event queue.
|
- On Android, fix `ControlFlow::Poll` not polling the Android event queue.
|
||||||
- On macOS, add `NSWindow.hasShadow` support.
|
- On macOS, add `NSWindow.hasShadow` support.
|
||||||
- On Web, fix vertical mouse wheel scrolling being inverted.
|
- On Web, fix vertical mouse wheel scrolling being inverted.
|
||||||
|
- On Web, implement mouse capturing for click-dragging out of the canvas.
|
||||||
- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error.
|
- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error.
|
||||||
- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`.
|
- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`.
|
||||||
- On NetBSD, fixed crash due to incorrect detection of the main thread.
|
- On NetBSD, fixed crash due to incorrect detection of the main thread.
|
||||||
|
|
|
@ -97,6 +97,7 @@ version = "0.3.22"
|
||||||
optional = true
|
optional = true
|
||||||
features = [
|
features = [
|
||||||
'console',
|
'console',
|
||||||
|
"AddEventListenerOptions",
|
||||||
'CssStyleDeclaration',
|
'CssStyleDeclaration',
|
||||||
'BeforeUnloadEvent',
|
'BeforeUnloadEvent',
|
||||||
'Document',
|
'Document',
|
||||||
|
|
|
@ -202,6 +202,7 @@ impl Canvas {
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton, ModifiersState),
|
F: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton, ModifiersState),
|
||||||
{
|
{
|
||||||
|
let canvas = self.raw.clone();
|
||||||
self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| {
|
self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| {
|
||||||
handler(
|
handler(
|
||||||
event.pointer_id(),
|
event.pointer_id(),
|
||||||
|
@ -209,6 +210,9 @@ impl Canvas {
|
||||||
event::mouse_button(&event),
|
event::mouse_button(&event),
|
||||||
event::mouse_modifiers(&event),
|
event::mouse_modifiers(&event),
|
||||||
);
|
);
|
||||||
|
canvas
|
||||||
|
.set_pointer_capture(event.pointer_id())
|
||||||
|
.expect("Failed to set pointer capture");
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,36 +9,33 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
use web_sys::{
|
use web_sys::{
|
||||||
Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, MouseEvent,
|
AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
|
||||||
PointerEvent, WheelEvent,
|
MediaQueryListEvent, MouseEvent, WheelEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod mouse_handler;
|
||||||
|
mod pointer_handler;
|
||||||
|
|
||||||
pub struct Canvas {
|
pub struct Canvas {
|
||||||
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
|
common: Common,
|
||||||
raw: HtmlCanvasElement,
|
|
||||||
on_focus: Option<Closure<dyn FnMut(FocusEvent)>>,
|
on_focus: Option<Closure<dyn FnMut(FocusEvent)>>,
|
||||||
on_blur: Option<Closure<dyn FnMut(FocusEvent)>>,
|
on_blur: Option<Closure<dyn FnMut(FocusEvent)>>,
|
||||||
on_keyboard_release: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
on_keyboard_release: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
||||||
on_keyboard_press: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
on_keyboard_press: Option<Closure<dyn FnMut(KeyboardEvent)>>,
|
||||||
on_received_character: 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_mouse_wheel: Option<Closure<dyn FnMut(WheelEvent)>>,
|
||||||
on_fullscreen_change: Option<Closure<dyn FnMut(Event)>>,
|
on_fullscreen_change: Option<Closure<dyn FnMut(Event)>>,
|
||||||
wants_fullscreen: Rc<RefCell<bool>>,
|
|
||||||
on_dark_mode: Option<Closure<dyn FnMut(MediaQueryListEvent)>>,
|
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) {
|
fn drop(&mut self) {
|
||||||
self.raw.remove();
|
self.raw.remove();
|
||||||
}
|
}
|
||||||
|
@ -72,38 +69,38 @@ impl Canvas {
|
||||||
.set_attribute("tabindex", "0")
|
.set_attribute("tabindex", "0")
|
||||||
.map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?;
|
.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 {
|
Ok(Canvas {
|
||||||
raw: canvas,
|
common: Common {
|
||||||
|
raw: canvas,
|
||||||
|
wants_fullscreen: Rc::new(RefCell::new(false)),
|
||||||
|
},
|
||||||
on_blur: None,
|
on_blur: None,
|
||||||
on_focus: None,
|
on_focus: None,
|
||||||
on_keyboard_release: None,
|
on_keyboard_release: None,
|
||||||
on_keyboard_press: None,
|
on_keyboard_press: None,
|
||||||
on_received_character: 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_mouse_wheel: None,
|
||||||
on_fullscreen_change: None,
|
on_fullscreen_change: None,
|
||||||
wants_fullscreen: Rc::new(RefCell::new(false)),
|
|
||||||
on_dark_mode: None,
|
on_dark_mode: None,
|
||||||
|
mouse_state,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_attribute(&self, attribute: &str, value: &str) {
|
pub fn set_attribute(&self, attribute: &str, value: &str) {
|
||||||
self.raw
|
self.common
|
||||||
|
.raw
|
||||||
.set_attribute(attribute, value)
|
.set_attribute(attribute, value)
|
||||||
.expect(&format!("Set attribute: {}", attribute));
|
.expect(&format!("Set attribute: {}", attribute));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn position(&self) -> LogicalPosition<f64> {
|
pub fn position(&self) -> LogicalPosition<f64> {
|
||||||
let bounds = self.raw.get_bounding_client_rect();
|
let bounds = self.common.raw.get_bounding_client_rect();
|
||||||
|
|
||||||
LogicalPosition {
|
LogicalPosition {
|
||||||
x: bounds.x(),
|
x: bounds.x(),
|
||||||
|
@ -113,20 +110,20 @@ impl Canvas {
|
||||||
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
pub fn size(&self) -> PhysicalSize<u32> {
|
||||||
PhysicalSize {
|
PhysicalSize {
|
||||||
width: self.raw.width(),
|
width: self.common.raw.width(),
|
||||||
height: self.raw.height(),
|
height: self.common.raw.height(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn raw(&self) -> &HtmlCanvasElement {
|
pub fn raw(&self) -> &HtmlCanvasElement {
|
||||||
&self.raw
|
&self.common.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_blur<F>(&mut self, mut handler: F)
|
pub fn on_blur<F>(&mut self, mut handler: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(),
|
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();
|
handler();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -135,7 +132,7 @@ impl Canvas {
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(),
|
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();
|
handler();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -144,30 +141,34 @@ impl Canvas {
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
|
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
|
||||||
{
|
{
|
||||||
self.on_keyboard_release =
|
self.on_keyboard_release = Some(self.common.add_user_event(
|
||||||
Some(self.add_user_event("keyup", move |event: KeyboardEvent| {
|
"keyup",
|
||||||
|
move |event: KeyboardEvent| {
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
handler(
|
handler(
|
||||||
event::scan_code(&event),
|
event::scan_code(&event),
|
||||||
event::virtual_key_code(&event),
|
event::virtual_key_code(&event),
|
||||||
event::keyboard_modifiers(&event),
|
event::keyboard_modifiers(&event),
|
||||||
);
|
);
|
||||||
}));
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
|
pub fn on_keyboard_press<F>(&mut self, mut handler: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
|
F: 'static + FnMut(ScanCode, Option<VirtualKeyCode>, ModifiersState),
|
||||||
{
|
{
|
||||||
self.on_keyboard_press =
|
self.on_keyboard_press = Some(self.common.add_user_event(
|
||||||
Some(self.add_user_event("keydown", move |event: KeyboardEvent| {
|
"keydown",
|
||||||
|
move |event: KeyboardEvent| {
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
handler(
|
handler(
|
||||||
event::scan_code(&event),
|
event::scan_code(&event),
|
||||||
event::virtual_key_code(&event),
|
event::virtual_key_code(&event),
|
||||||
event::keyboard_modifiers(&event),
|
event::keyboard_modifiers(&event),
|
||||||
);
|
);
|
||||||
}));
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_received_character<F>(&mut self, mut handler: F)
|
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
|
// The `keypress` event is deprecated, but there does not seem to be a
|
||||||
// viable/compatible alternative as of now. `beforeinput` is still widely
|
// viable/compatible alternative as of now. `beforeinput` is still widely
|
||||||
// unsupported.
|
// unsupported.
|
||||||
self.on_received_character = Some(self.add_user_event(
|
self.on_received_character = Some(self.common.add_user_event(
|
||||||
"keypress",
|
"keypress",
|
||||||
move |event: KeyboardEvent| {
|
move |event: KeyboardEvent| {
|
||||||
handler(event::codepoint(&event));
|
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
|
where
|
||||||
F: 'static + FnMut(i32),
|
F: 'static + FnMut(i32),
|
||||||
{
|
{
|
||||||
if has_pointer_event() {
|
match &mut self.mouse_state {
|
||||||
self.on_cursor_leave =
|
MouseState::HasPointerEvent(h) => h.on_cursor_leave(&self.common, handler),
|
||||||
Some(self.add_event("pointerout", move |event: PointerEvent| {
|
MouseState::NoPointerEvent(h) => h.on_cursor_leave(&self.common, handler),
|
||||||
handler(event.pointer_id());
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
self.on_mouse_leave = Some(self.add_event("mouseout", move |_: MouseEvent| {
|
|
||||||
handler(0);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_cursor_enter<F>(&mut self, mut handler: F)
|
pub fn on_cursor_enter<F>(&mut self, handler: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(i32),
|
F: 'static + FnMut(i32),
|
||||||
{
|
{
|
||||||
if has_pointer_event() {
|
match &mut self.mouse_state {
|
||||||
self.on_cursor_enter =
|
MouseState::HasPointerEvent(h) => h.on_cursor_enter(&self.common, handler),
|
||||||
Some(self.add_event("pointerover", move |event: PointerEvent| {
|
MouseState::NoPointerEvent(h) => h.on_cursor_enter(&self.common, handler),
|
||||||
handler(event.pointer_id());
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
self.on_mouse_enter = Some(self.add_event("mouseover", move |_: MouseEvent| {
|
|
||||||
handler(0);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_mouse_release<F>(&mut self, mut handler: F)
|
pub fn on_mouse_release<F>(&mut self, handler: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||||
{
|
{
|
||||||
if has_pointer_event() {
|
match &mut self.mouse_state {
|
||||||
self.on_pointer_release = Some(self.add_user_event(
|
MouseState::HasPointerEvent(h) => h.on_mouse_release(&self.common, handler),
|
||||||
"pointerup",
|
MouseState::NoPointerEvent(h) => h.on_mouse_release(&self.common, handler),
|
||||||
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),
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_mouse_press<F>(&mut self, mut handler: F)
|
pub fn on_mouse_press<F>(&mut self, handler: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton, ModifiersState),
|
F: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton, ModifiersState),
|
||||||
{
|
{
|
||||||
if has_pointer_event() {
|
match &mut self.mouse_state {
|
||||||
self.on_pointer_press = Some(self.add_user_event(
|
MouseState::HasPointerEvent(h) => h.on_mouse_press(&self.common, handler),
|
||||||
"pointerdown",
|
MouseState::NoPointerEvent(h) => h.on_mouse_press(&self.common, handler),
|
||||||
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),
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_cursor_move<F>(&mut self, mut handler: F)
|
pub fn on_cursor_move<F>(&mut self, handler: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||||
{
|
{
|
||||||
if has_pointer_event() {
|
match &mut self.mouse_state {
|
||||||
self.on_cursor_move =
|
MouseState::HasPointerEvent(h) => h.on_cursor_move(&self.common, handler),
|
||||||
Some(self.add_event("pointermove", move |event: PointerEvent| {
|
MouseState::NoPointerEvent(h) => h.on_cursor_move(&self.common, handler),
|
||||||
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),
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +242,7 @@ impl Canvas {
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState),
|
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();
|
event.prevent_default();
|
||||||
if let Some(delta) = event::mouse_scroll_delta(&event) {
|
if let Some(delta) = event::mouse_scroll_delta(&event) {
|
||||||
handler(0, delta, event::mouse_modifiers(&event));
|
handler(0, delta, event::mouse_modifiers(&event));
|
||||||
|
@ -315,8 +254,10 @@ impl Canvas {
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(),
|
F: 'static + FnMut(),
|
||||||
{
|
{
|
||||||
self.on_fullscreen_change =
|
self.on_fullscreen_change = Some(
|
||||||
Some(self.add_event("fullscreenchange", move |_: Event| handler()));
|
self.common
|
||||||
|
.add_event("fullscreenchange", move |_: Event| handler()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_dark_mode<F>(&mut self, mut handler: F)
|
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)>
|
fn add_event<E, F>(&self, event_name: &str, mut handler: F) -> Closure<dyn FnMut(E)>
|
||||||
where
|
where
|
||||||
E: 'static + AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi,
|
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) {
|
pub fn request_fullscreen(&self) {
|
||||||
*self.wants_fullscreen.borrow_mut() = true;
|
*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.
|
/// Returns whether pointer events are supported.
|
||||||
/// Used to decide whether to use pointer events
|
/// Used to decide whether to use pointer events
|
||||||
/// or plain mouse events. Note that Safari
|
/// or plain mouse events. Note that Safari
|
||||||
|
|
203
src/platform_impl/web/web_sys/canvas/mouse_handler.rs
Normal file
203
src/platform_impl/web/web_sys/canvas/mouse_handler.rs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
use super::event;
|
||||||
|
use crate::dpi::PhysicalPosition;
|
||||||
|
use crate::event::{ModifiersState, MouseButton};
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use wasm_bindgen::closure::Closure;
|
||||||
|
use web_sys::{EventTarget, MouseEvent};
|
||||||
|
|
||||||
|
pub(super) struct MouseHandler {
|
||||||
|
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_leave_handler: Rc<RefCell<Option<Box<dyn FnMut(i32)>>>>,
|
||||||
|
mouse_capture_state: Rc<RefCell<MouseCaptureState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub(super) enum MouseCaptureState {
|
||||||
|
NotCaptured,
|
||||||
|
Captured,
|
||||||
|
OtherElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MouseHandler {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
on_mouse_leave: None,
|
||||||
|
on_mouse_enter: None,
|
||||||
|
on_mouse_move: None,
|
||||||
|
on_mouse_press: None,
|
||||||
|
on_mouse_release: None,
|
||||||
|
on_mouse_leave_handler: Rc::new(RefCell::new(None)),
|
||||||
|
mouse_capture_state: Rc::new(RefCell::new(MouseCaptureState::NotCaptured)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn on_cursor_leave<F>(&mut self, canvas_common: &super::Common, handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32),
|
||||||
|
{
|
||||||
|
*self.on_mouse_leave_handler.borrow_mut() = Some(Box::new(handler));
|
||||||
|
let on_mouse_leave_handler = self.on_mouse_leave_handler.clone();
|
||||||
|
let mouse_capture_state = self.mouse_capture_state.clone();
|
||||||
|
self.on_mouse_leave = Some(canvas_common.add_event("mouseout", move |_: MouseEvent| {
|
||||||
|
// If the mouse is being captured, it is always considered
|
||||||
|
// to be "within" the the canvas, until the capture has been
|
||||||
|
// released, therefore we don't send cursor leave events.
|
||||||
|
if *mouse_capture_state.borrow() != MouseCaptureState::Captured {
|
||||||
|
if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() {
|
||||||
|
handler(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_enter<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32),
|
||||||
|
{
|
||||||
|
let mouse_capture_state = self.mouse_capture_state.clone();
|
||||||
|
self.on_mouse_enter = Some(canvas_common.add_event("mouseover", move |_: MouseEvent| {
|
||||||
|
// We don't send cursor leave events when the mouse is being
|
||||||
|
// captured, therefore we do the same with cursor enter events.
|
||||||
|
if *mouse_capture_state.borrow() != MouseCaptureState::Captured {
|
||||||
|
handler(0);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_release<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||||
|
{
|
||||||
|
let on_mouse_leave_handler = self.on_mouse_leave_handler.clone();
|
||||||
|
let mouse_capture_state = self.mouse_capture_state.clone();
|
||||||
|
let canvas = canvas_common.raw.clone();
|
||||||
|
self.on_mouse_release = Some(canvas_common.add_window_mouse_event(
|
||||||
|
"mouseup",
|
||||||
|
move |event: MouseEvent| {
|
||||||
|
let canvas = canvas.clone();
|
||||||
|
let mut mouse_capture_state = mouse_capture_state.borrow_mut();
|
||||||
|
match &*mouse_capture_state {
|
||||||
|
// Shouldn't happen but we'll just ignore it.
|
||||||
|
MouseCaptureState::NotCaptured => return,
|
||||||
|
MouseCaptureState::OtherElement => {
|
||||||
|
if event.buttons() == 0 {
|
||||||
|
// No buttons are pressed anymore so reset
|
||||||
|
// the capturing state.
|
||||||
|
*mouse_capture_state = MouseCaptureState::NotCaptured;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MouseCaptureState::Captured => {}
|
||||||
|
}
|
||||||
|
event.stop_propagation();
|
||||||
|
handler(
|
||||||
|
0,
|
||||||
|
event::mouse_button(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
if event
|
||||||
|
.target()
|
||||||
|
.map_or(false, |target| target != EventTarget::from(canvas))
|
||||||
|
{
|
||||||
|
// Since we do not send cursor leave events while the
|
||||||
|
// cursor is being captured, we instead send it after
|
||||||
|
// the capture has been released.
|
||||||
|
if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() {
|
||||||
|
handler(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if event.buttons() == 0 {
|
||||||
|
// No buttons are pressed anymore so reset
|
||||||
|
// the capturing state.
|
||||||
|
*mouse_capture_state = MouseCaptureState::NotCaptured;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_press<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton, ModifiersState),
|
||||||
|
{
|
||||||
|
let mouse_capture_state = self.mouse_capture_state.clone();
|
||||||
|
let canvas = canvas_common.raw.clone();
|
||||||
|
self.on_mouse_press = Some(canvas_common.add_window_mouse_event(
|
||||||
|
"mousedown",
|
||||||
|
move |event: MouseEvent| {
|
||||||
|
let canvas = canvas.clone();
|
||||||
|
let mut mouse_capture_state = mouse_capture_state.borrow_mut();
|
||||||
|
match &*mouse_capture_state {
|
||||||
|
MouseCaptureState::NotCaptured
|
||||||
|
if event
|
||||||
|
.target()
|
||||||
|
.map_or(false, |target| target != EventTarget::from(canvas)) =>
|
||||||
|
{
|
||||||
|
// The target isn't our canvas which means the
|
||||||
|
// mouse is pressed outside of it.
|
||||||
|
*mouse_capture_state = MouseCaptureState::OtherElement;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MouseCaptureState::OtherElement => return,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
*mouse_capture_state = MouseCaptureState::Captured;
|
||||||
|
event.stop_propagation();
|
||||||
|
handler(
|
||||||
|
0,
|
||||||
|
event::mouse_position(&event).to_physical(super::super::scale_factor()),
|
||||||
|
event::mouse_button(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_move<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||||
|
{
|
||||||
|
let mouse_capture_state = self.mouse_capture_state.clone();
|
||||||
|
let canvas = canvas_common.raw.clone();
|
||||||
|
self.on_mouse_move = Some(canvas_common.add_window_mouse_event(
|
||||||
|
"mousemove",
|
||||||
|
move |event: MouseEvent| {
|
||||||
|
let canvas = canvas.clone();
|
||||||
|
let mouse_capture_state = mouse_capture_state.borrow();
|
||||||
|
let is_over_canvas = event
|
||||||
|
.target()
|
||||||
|
.map_or(false, |target| target == EventTarget::from(canvas.clone()));
|
||||||
|
match &*mouse_capture_state {
|
||||||
|
// Don't handle hover events outside of canvas.
|
||||||
|
MouseCaptureState::NotCaptured if !is_over_canvas => return,
|
||||||
|
MouseCaptureState::OtherElement if !is_over_canvas => return,
|
||||||
|
// If hovering over the canvas, just send the cursor move event.
|
||||||
|
MouseCaptureState::NotCaptured
|
||||||
|
| MouseCaptureState::OtherElement
|
||||||
|
| MouseCaptureState::Captured => {
|
||||||
|
if *mouse_capture_state == MouseCaptureState::Captured {
|
||||||
|
event.stop_propagation();
|
||||||
|
}
|
||||||
|
let mouse_pos = if is_over_canvas {
|
||||||
|
event::mouse_position(&event)
|
||||||
|
} else {
|
||||||
|
// Since the mouse is not on the canvas, we cannot
|
||||||
|
// use `offsetX`/`offsetY`.
|
||||||
|
event::mouse_position_by_client(&event, &canvas)
|
||||||
|
};
|
||||||
|
handler(
|
||||||
|
0,
|
||||||
|
mouse_pos.to_physical(super::super::scale_factor()),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
103
src/platform_impl/web/web_sys/canvas/pointer_handler.rs
Normal file
103
src/platform_impl/web/web_sys/canvas/pointer_handler.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use super::event;
|
||||||
|
use crate::dpi::PhysicalPosition;
|
||||||
|
use crate::event::{ModifiersState, MouseButton};
|
||||||
|
|
||||||
|
use wasm_bindgen::closure::Closure;
|
||||||
|
use web_sys::PointerEvent;
|
||||||
|
|
||||||
|
pub(super) struct PointerHandler {
|
||||||
|
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)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerHandler {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
on_cursor_leave: None,
|
||||||
|
on_cursor_enter: None,
|
||||||
|
on_cursor_move: None,
|
||||||
|
on_pointer_press: None,
|
||||||
|
on_pointer_release: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_leave<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32),
|
||||||
|
{
|
||||||
|
self.on_cursor_leave = Some(canvas_common.add_event(
|
||||||
|
"pointerout",
|
||||||
|
move |event: PointerEvent| {
|
||||||
|
handler(event.pointer_id());
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_enter<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32),
|
||||||
|
{
|
||||||
|
self.on_cursor_enter = Some(canvas_common.add_event(
|
||||||
|
"pointerover",
|
||||||
|
move |event: PointerEvent| {
|
||||||
|
handler(event.pointer_id());
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_release<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, MouseButton, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_pointer_release = Some(canvas_common.add_user_event(
|
||||||
|
"pointerup",
|
||||||
|
move |event: PointerEvent| {
|
||||||
|
handler(
|
||||||
|
event.pointer_id(),
|
||||||
|
event::mouse_button(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_press<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton, ModifiersState),
|
||||||
|
{
|
||||||
|
let canvas = canvas_common.raw.clone();
|
||||||
|
self.on_pointer_press = Some(canvas_common.add_user_event(
|
||||||
|
"pointerdown",
|
||||||
|
move |event: PointerEvent| {
|
||||||
|
handler(
|
||||||
|
event.pointer_id(),
|
||||||
|
event::mouse_position(&event).to_physical(super::super::scale_factor()),
|
||||||
|
event::mouse_button(&event),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
canvas
|
||||||
|
.set_pointer_capture(event.pointer_id())
|
||||||
|
.expect("Failed to set pointer capture");
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_cursor_move<F>(&mut self, canvas_common: &super::Common, mut handler: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(i32, PhysicalPosition<f64>, ModifiersState),
|
||||||
|
{
|
||||||
|
self.on_cursor_move = Some(canvas_common.add_event(
|
||||||
|
"pointermove",
|
||||||
|
move |event: PointerEvent| {
|
||||||
|
handler(
|
||||||
|
event.pointer_id(),
|
||||||
|
event::mouse_position(&event).to_physical(super::super::scale_factor()),
|
||||||
|
event::mouse_modifiers(&event),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ use crate::dpi::LogicalPosition;
|
||||||
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode};
|
||||||
|
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use web_sys::{KeyboardEvent, MouseEvent, WheelEvent};
|
use web_sys::{HtmlCanvasElement, KeyboardEvent, MouseEvent, WheelEvent};
|
||||||
|
|
||||||
pub fn mouse_button(event: &MouseEvent) -> MouseButton {
|
pub fn mouse_button(event: &MouseEvent) -> MouseButton {
|
||||||
match event.button() {
|
match event.button() {
|
||||||
|
@ -29,6 +29,17 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mouse_position_by_client(
|
||||||
|
event: &MouseEvent,
|
||||||
|
canvas: &HtmlCanvasElement,
|
||||||
|
) -> LogicalPosition<f64> {
|
||||||
|
let bounding_client_rect = canvas.get_bounding_client_rect();
|
||||||
|
LogicalPosition {
|
||||||
|
x: event.client_x() as f64 - bounding_client_rect.x(),
|
||||||
|
y: event.client_y() as f64 - bounding_client_rect.y(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mouse_scroll_delta(event: &WheelEvent) -> Option<MouseScrollDelta> {
|
pub fn mouse_scroll_delta(event: &WheelEvent) -> Option<MouseScrollDelta> {
|
||||||
let x = event.delta_x();
|
let x = event.delta_x();
|
||||||
let y = -event.delta_y();
|
let y = -event.delta_y();
|
||||||
|
|
Loading…
Reference in a new issue