Remove MouseEvent fallback support

This commit is contained in:
dAxpeDDa 2023-06-04 01:44:53 +02:00 committed by daxpedda
parent fbba203c4a
commit a134a59917
8 changed files with 64 additions and 416 deletions

View file

@ -65,6 +65,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Web, fix pen treated as mouse input.
- On Web, send mouse position on button release as well.
- On Web, fix touch input not gaining or loosing focus.
- **Breaking:** On Web, dropped support for Safari versions below 13.
# 0.28.6

View file

@ -132,7 +132,6 @@ package = "web-sys"
version = "0.3.22"
features = [
'console',
"AddEventListenerOptions",
'CssStyleDeclaration',
'BeforeUnloadEvent',
'Document',
@ -147,7 +146,6 @@ features = [
'KeyboardEvent',
'MediaQueryList',
'MediaQueryListEvent',
'MouseEvent',
'Node',
'PointerEvent',
'Window',

View file

@ -352,11 +352,10 @@ impl<T> EventLoopWindowTarget<T> {
let has_focus = has_focus.clone();
move |pointer_id, position, button, active_modifiers| {
let focus_changed =
(!has_focus.replace(true)).then_some(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Focused(true),
});
let focus_changed = (!has_focus.replace(true)).then_some(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Focused(true),
});
let modifiers_changed = (modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
@ -393,11 +392,10 @@ impl<T> EventLoopWindowTarget<T> {
let has_focus = has_focus.clone();
move |device_id, location, force| {
let focus_changed =
(!has_focus.replace(true)).then_some(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Focused(true),
});
let focus_changed = (!has_focus.replace(true)).then_some(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Focused(true),
});
runner.send_events(focus_changed.into_iter().chain(iter::once(
Event::WindowEvent {

View file

@ -1,5 +1,6 @@
use super::event_handle::EventListenerHandle;
use super::media_query_handle::MediaQueryListHandle;
use super::pointer::PointerHandler;
use super::{event, ButtonsState};
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::error::OsError as RootOE;
@ -16,13 +17,9 @@ use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{
AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent,
MediaQueryListEvent, MouseEvent, WheelEvent,
Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, WheelEvent,
};
mod mouse_handler;
mod pointer_handler;
#[allow(dead_code)]
pub struct Canvas {
common: Common,
@ -35,12 +32,12 @@ pub struct Canvas {
on_mouse_wheel: Option<EventListenerHandle<dyn FnMut(WheelEvent)>>,
on_fullscreen_change: Option<EventListenerHandle<dyn FnMut(Event)>>,
on_dark_mode: Option<MediaQueryListHandle>,
mouse_state: MouseState,
pointer_handler: PointerHandler,
}
struct Common {
pub struct Common {
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
raw: HtmlCanvasElement,
pub raw: HtmlCanvasElement,
wants_fullscreen: Rc<RefCell<bool>>,
}
@ -74,12 +71,6 @@ impl Canvas {
.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 {
common: Common {
raw: canvas,
@ -94,7 +85,7 @@ impl Canvas {
on_mouse_wheel: None,
on_fullscreen_change: None,
on_dark_mode: None,
mouse_state,
pointer_handler: PointerHandler::new(),
})
}
@ -225,20 +216,14 @@ impl Canvas {
where
F: 'static + FnMut(i32, ModifiersState),
{
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),
}
self.pointer_handler.on_cursor_leave(&self.common, handler)
}
pub fn on_cursor_enter<F>(&mut self, handler: F)
where
F: 'static + FnMut(i32, ModifiersState),
{
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),
}
self.pointer_handler.on_cursor_enter(&self.common, handler)
}
pub fn on_mouse_release<M, T>(&mut self, mouse_handler: M, touch_handler: T)
@ -246,12 +231,8 @@ impl Canvas {
M: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton, ModifiersState),
T: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
{
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => {
h.on_mouse_release(&self.common, mouse_handler, touch_handler)
}
MouseState::NoPointerEvent(h) => h.on_mouse_release(&self.common, mouse_handler),
}
self.pointer_handler
.on_mouse_release(&self.common, mouse_handler, touch_handler)
}
pub fn on_mouse_press<M, T>(&mut self, mouse_handler: M, touch_handler: T)
@ -259,12 +240,8 @@ impl Canvas {
M: 'static + FnMut(i32, PhysicalPosition<f64>, MouseButton, ModifiersState),
T: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
{
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => {
h.on_mouse_press(&self.common, mouse_handler, touch_handler)
}
MouseState::NoPointerEvent(h) => h.on_mouse_press(&self.common, mouse_handler),
}
self.pointer_handler
.on_mouse_press(&self.common, mouse_handler, touch_handler)
}
pub fn on_cursor_move<MOD, M, T, B>(
@ -280,28 +257,21 @@ impl Canvas {
T: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
B: 'static + FnMut(i32, PhysicalPosition<f64>, ButtonsState, MouseButton),
{
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => h.on_cursor_move(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
button_handler,
prevent_default,
),
MouseState::NoPointerEvent(h) => {
h.on_cursor_move(&self.common, modifier_handler, mouse_handler)
}
}
self.pointer_handler.on_cursor_move(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
button_handler,
prevent_default,
)
}
pub fn on_touch_cancel<F>(&mut self, handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
{
if let MouseState::HasPointerEvent(h) = &mut self.mouse_state {
h.on_touch_cancel(&self.common, handler)
}
self.pointer_handler.on_touch_cancel(&self.common, handler)
}
pub fn on_mouse_wheel<F>(&mut self, mut handler: F, prevent_default: bool)
@ -358,15 +328,12 @@ impl Canvas {
self.on_mouse_wheel = None;
self.on_fullscreen_change = None;
self.on_dark_mode = None;
match &mut self.mouse_state {
MouseState::HasPointerEvent(h) => h.remove_listeners(),
MouseState::NoPointerEvent(h) => h.remove_listeners(),
}
self.pointer_handler.remove_listeners()
}
}
impl Common {
fn add_event<E, F>(
pub fn add_event<E, F>(
&self,
event_name: &'static str,
mut handler: F,
@ -391,7 +358,7 @@ impl Common {
// The difference between add_event and add_user_event is that the latter has a special meaning
// for browser security. A user event is a deliberate action by the user (like a mouse or key
// press) and is the only time things like a fullscreen request may be successfully completed.)
fn add_user_event<E, F>(
pub fn add_user_event<E, F>(
&self,
event_name: &'static str,
mut handler: F,
@ -415,43 +382,6 @@ impl Common {
})
}
// 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: &'static str,
mut handler: F,
) -> EventListenerHandle<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(_)>);
let listener = EventListenerHandle::with_options(
&window,
event_name,
closure,
AddEventListenerOptions::new().capture(true),
);
listener
}
pub fn request_fullscreen(&self) {
#[wasm_bindgen]
extern "C" {
@ -483,21 +413,3 @@ impl Common {
super::is_fullscreen(&self.raw)
}
}
/// Pointer events are supported or not.
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
/// doesn't support pointer events now.
fn has_pointer_event() -> bool {
if let Some(window) = web_sys::window() {
window.get("PointerEvent").is_some()
} else {
false
}
}

View file

@ -1,234 +0,0 @@
use super::event;
use super::EventListenerHandle;
use crate::dpi::PhysicalPosition;
use crate::event::MouseButton;
use crate::keyboard::ModifiersState;
use std::cell::RefCell;
use std::rc::Rc;
use web_sys::{EventTarget, MouseEvent};
type MouseLeaveHandler = Rc<RefCell<Option<Box<dyn FnMut(i32, ModifiersState)>>>>;
#[allow(dead_code)]
pub(super) struct MouseHandler {
on_mouse_leave: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
on_mouse_enter: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
on_mouse_move: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
on_mouse_press: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
on_mouse_release: Option<EventListenerHandle<dyn FnMut(MouseEvent)>>,
on_mouse_leave_handler: MouseLeaveHandler,
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, ModifiersState),
{
*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 |event: 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() {
let modifiers = event::mouse_modifiers(&event);
handler(0, modifiers);
}
}
},
));
}
pub fn on_cursor_enter<F>(&mut self, canvas_common: &super::Common, mut handler: F)
where
F: 'static + FnMut(i32, ModifiersState),
{
let mouse_capture_state = self.mouse_capture_state.clone();
self.on_mouse_enter = Some(canvas_common.add_event(
"mouseover",
move |event: 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 {
let modifiers = event::mouse_modifiers(&event);
handler(0, modifiers);
}
},
));
}
pub fn on_mouse_release<F>(&mut self, canvas_common: &super::Common, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, 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_position(&event).to_physical(super::super::scale_factor()),
event::mouse_button(&event).expect("no mouse button released"),
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() {
let modifiers = event::mouse_modifiers(&event);
handler(0, modifiers);
}
}
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).expect("no mouse button pressed"),
event::mouse_modifiers(&event),
);
},
));
}
pub fn on_cursor_move<MOD, M>(
&mut self,
canvas_common: &super::Common,
mut modifier_handler: MOD,
mut mouse_handler: M,
) where
MOD: 'static + FnMut(ModifiersState),
M: 'static + FnMut(i32, PhysicalPosition<f64>, PhysicalPosition<f64>),
{
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| {
modifier_handler(event::mouse_modifiers(&event));
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 | MouseCaptureState::OtherElement
if !is_over_canvas => {}
// 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)
};
let mouse_delta = event::mouse_delta(&event);
mouse_handler(
0,
mouse_pos.to_physical(super::super::scale_factor()),
mouse_delta.to_physical(super::super::scale_factor()),
);
}
}
},
));
}
pub fn remove_listeners(&mut self) {
self.on_mouse_leave = None;
self.on_mouse_enter = None;
self.on_mouse_move = None;
self.on_mouse_press = None;
self.on_mouse_release = None;
*self.on_mouse_leave_handler.borrow_mut() = None;
}
}

View file

@ -1,7 +1,7 @@
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::{AddEventListenerOptions, EventListenerOptions, EventTarget};
use web_sys::{EventListenerOptions, EventTarget};
pub(super) struct EventListenerHandle<T: ?Sized> {
pub struct EventListenerHandle<T: ?Sized> {
target: EventTarget,
event_type: &'static str,
listener: Closure<T>,
@ -24,31 +24,6 @@ impl<T: ?Sized> EventListenerHandle<T> {
options: EventListenerOptions::new(),
}
}
pub fn with_options<U>(
target: &U,
event_type: &'static str,
listener: Closure<T>,
options: &AddEventListenerOptions,
) -> Self
where
U: Clone + Into<EventTarget>,
{
let target = target.clone().into();
target
.add_event_listener_with_callback_and_add_event_listener_options(
event_type,
listener.as_ref().unchecked_ref(),
options,
)
.expect("Failed to add event listener");
EventListenerHandle {
target,
event_type,
listener,
options: options.clone().unchecked_into(),
}
}
}
impl<T: ?Sized> Drop for EventListenerHandle<T> {

View file

@ -2,6 +2,7 @@ mod canvas;
mod event;
mod event_handle;
mod media_query_handle;
mod pointer;
mod scaling;
mod timeout;

View file

@ -1,5 +1,6 @@
use super::canvas::Common;
use super::event;
use super::EventListenerHandle;
use super::event_handle::EventListenerHandle;
use crate::dpi::PhysicalPosition;
use crate::event::{Force, MouseButton};
use crate::keyboard::ModifiersState;
@ -31,7 +32,7 @@ impl PointerHandler {
}
}
pub fn on_cursor_leave<F>(&mut self, canvas_common: &super::Common, mut handler: F)
pub fn on_cursor_leave<F>(&mut self, canvas_common: &Common, mut handler: F)
where
F: 'static + FnMut(i32, ModifiersState),
{
@ -50,7 +51,7 @@ impl PointerHandler {
));
}
pub fn on_cursor_enter<F>(&mut self, canvas_common: &super::Common, mut handler: F)
pub fn on_cursor_enter<F>(&mut self, canvas_common: &Common, mut handler: F)
where
F: 'static + FnMut(i32, ModifiersState),
{
@ -71,7 +72,7 @@ impl PointerHandler {
pub fn on_mouse_release<M, T>(
&mut self,
canvas_common: &super::Common,
canvas_common: &Common,
mut mouse_handler: M,
mut touch_handler: T,
) where
@ -81,29 +82,26 @@ impl PointerHandler {
let canvas = canvas_common.raw.clone();
self.on_pointer_release = Some(canvas_common.add_user_event(
"pointerup",
move |event: PointerEvent| {
match event.pointer_type().as_str() {
"touch" => touch_handler(
event.pointer_id(),
event::touch_position(&event, &canvas)
.to_physical(super::super::scale_factor()),
Force::Normalized(event.pressure() as f64),
),
"mouse" => mouse_handler(
event.pointer_id(),
event::mouse_position(&event).to_physical(super::super::scale_factor()),
event::mouse_button(&event).expect("no mouse button released"),
event::mouse_modifiers(&event),
),
_ => (),
}
move |event: PointerEvent| match event.pointer_type().as_str() {
"touch" => touch_handler(
event.pointer_id(),
event::touch_position(&event, &canvas).to_physical(super::scale_factor()),
Force::Normalized(event.pressure() as f64),
),
"mouse" => mouse_handler(
event.pointer_id(),
event::mouse_position(&event).to_physical(super::scale_factor()),
event::mouse_button(&event).expect("no mouse button released"),
event::mouse_modifiers(&event),
),
_ => (),
},
));
}
pub fn on_mouse_press<M, T>(
&mut self,
canvas_common: &super::Common,
canvas_common: &Common,
mut mouse_handler: M,
mut touch_handler: T,
) where
@ -119,14 +117,14 @@ impl PointerHandler {
touch_handler(
event.pointer_id(),
event::touch_position(&event, &canvas)
.to_physical(super::super::scale_factor()),
.to_physical(super::scale_factor()),
Force::Normalized(event.pressure() as f64),
);
}
"mouse" => {
mouse_handler(
event.pointer_id(),
event::mouse_position(&event).to_physical(super::super::scale_factor()),
event::mouse_position(&event).to_physical(super::scale_factor()),
event::mouse_button(&event).expect("no mouse button pressed"),
event::mouse_modifiers(&event),
);
@ -144,7 +142,7 @@ impl PointerHandler {
pub fn on_cursor_move<MOD, M, T, B>(
&mut self,
canvas_common: &super::Common,
canvas_common: &Common,
mut modifier_handler: MOD,
mut mouse_handler: M,
mut touch_handler: T,
@ -196,7 +194,7 @@ impl PointerHandler {
button_handler(
id,
event::mouse_position(&event).to_physical(super::super::scale_factor()),
event::mouse_position(&event).to_physical(super::scale_factor()),
event::mouse_buttons(&event),
button,
);
@ -227,13 +225,13 @@ impl PointerHandler {
match pointer_type.as_str() {
"mouse" => mouse_handler(
id,
event::mouse_position(&event).to_physical(super::super::scale_factor()),
event::mouse_delta(&event).to_physical(super::super::scale_factor()),
event::mouse_position(&event).to_physical(super::scale_factor()),
event::mouse_delta(&event).to_physical(super::scale_factor()),
),
"touch" => touch_handler(
id,
event::touch_position(&event, &canvas)
.to_physical(super::super::scale_factor()),
.to_physical(super::scale_factor()),
Force::Normalized(event.pressure() as f64),
),
_ => unreachable!("didn't return early before"),
@ -243,7 +241,7 @@ impl PointerHandler {
));
}
pub fn on_touch_cancel<F>(&mut self, canvas_common: &super::Common, mut handler: F)
pub fn on_touch_cancel<F>(&mut self, canvas_common: &Common, mut handler: F)
where
F: 'static + FnMut(i32, PhysicalPosition<f64>, Force),
{
@ -254,8 +252,7 @@ impl PointerHandler {
if event.pointer_type() == "touch" {
handler(
event.pointer_id(),
event::touch_position(&event, &canvas)
.to_physical(super::super::scale_factor()),
event::touch_position(&event, &canvas).to_physical(super::scale_factor()),
Force::Normalized(event.pressure() as f64),
);
}