Web: fix MouseMotion coordinate space (#3770)
This commit is contained in:
parent
850dd97177
commit
936da131c2
5 changed files with 126 additions and 28 deletions
|
|
@ -302,6 +302,7 @@ web_sys = { package = "web-sys", version = "0.3.64", features = [
|
|||
'MediaQueryList',
|
||||
'MessageChannel',
|
||||
'MessagePort',
|
||||
'Navigator',
|
||||
'Node',
|
||||
'PageTransitionEvent',
|
||||
'PointerEvent',
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ changelog entry.
|
|||
to send specific data to be processed on the main thread.
|
||||
- Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now
|
||||
only wakes up the loop.
|
||||
- On Web, slightly improve accuracy of `DeviceEvent::MouseMotion`.
|
||||
- `ApplicationHandler::create|destroy_surfaces()` was split off from
|
||||
`ApplicationHandler::resumed/suspended()`.
|
||||
|
||||
|
|
@ -80,3 +79,4 @@ changelog entry.
|
|||
### Fixed
|
||||
|
||||
- On Wayland, avoid crashing when compositor is misbehaving.
|
||||
- Account for different browser engine implementations of pointer movement coordinate space.
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ impl Shared {
|
|||
));
|
||||
|
||||
let runner = self.clone();
|
||||
let mut delta = backend::event::MouseDelta::new();
|
||||
let window = self.window().clone();
|
||||
*self.0.on_mouse_move.borrow_mut() = Some(EventListenerHandle::new(
|
||||
self.window().clone(),
|
||||
"pointermove",
|
||||
|
|
@ -273,26 +273,23 @@ impl Shared {
|
|||
}
|
||||
|
||||
// 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);
|
||||
let delta = delta.delta(&event).to_physical(backend::scale_factor(&window));
|
||||
|
||||
if delta.x == 0 && delta.y == 0 {
|
||||
return None.into_iter().chain(None).chain(None);
|
||||
}
|
||||
|
||||
let x_motion = (delta.x != 0).then_some(Event::DeviceEvent {
|
||||
let x_motion = (delta.x != 0.0).then_some(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: DeviceEvent::Motion { axis: 0, value: delta.x.into() },
|
||||
event: DeviceEvent::Motion { axis: 0, value: delta.x },
|
||||
});
|
||||
|
||||
let y_motion = (delta.y != 0).then_some(Event::DeviceEvent {
|
||||
let y_motion = (delta.y != 0.0).then_some(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: DeviceEvent::Motion { axis: 1, value: delta.y.into() },
|
||||
event: DeviceEvent::Motion { axis: 1, value: delta.y },
|
||||
});
|
||||
|
||||
x_motion.into_iter().chain(y_motion).chain(Some(Event::DeviceEvent {
|
||||
x_motion.into_iter().chain(y_motion).chain(iter::once(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: DeviceEvent::MouseMotion { delta: (delta.x.into(), delta.y.into()) },
|
||||
event: DeviceEvent::MouseMotion { delta: (delta.x, delta.y) },
|
||||
}))
|
||||
}));
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
use crate::event::{MouseButton, MouseScrollDelta};
|
||||
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
|
||||
|
||||
use dpi::{LogicalPosition, PhysicalPosition};
|
||||
use dpi::{LogicalPosition, PhysicalPosition, Position};
|
||||
use smol_str::SmolStr;
|
||||
use std::cell::OnceCell;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent};
|
||||
|
||||
use super::Engine;
|
||||
|
||||
bitflags::bitflags! {
|
||||
// https://www.w3.org/TR/pointerevents3/#the-buttons-property
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -95,23 +97,48 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
|
|||
LogicalPosition { x: event.offset_x(), y: event.offset_y() }
|
||||
}
|
||||
|
||||
pub struct MouseDelta(Option<PhysicalPosition<i32>>);
|
||||
// TODO: Remove this when Firefox supports correct movement values in coalesced events and browsers
|
||||
// have agreed on what coordinate space `movementX/Y` is using.
|
||||
// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1753724>.
|
||||
// See <https://github.com/w3c/pointerlock/issues/42>.
|
||||
pub enum MouseDelta {
|
||||
Chromium,
|
||||
Gecko { old_position: LogicalPosition<f64>, old_delta: LogicalPosition<f64> },
|
||||
Other,
|
||||
}
|
||||
|
||||
impl MouseDelta {
|
||||
pub fn new() -> Self {
|
||||
Self(None)
|
||||
pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self {
|
||||
match super::engine(window) {
|
||||
Some(Engine::Chromium) => Self::Chromium,
|
||||
// Firefox has wrong movement values in coalesced events.
|
||||
Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko {
|
||||
old_position: mouse_position(event),
|
||||
old_delta: LogicalPosition::new(
|
||||
event.movement_x() as f64,
|
||||
event.movement_y() as f64,
|
||||
),
|
||||
},
|
||||
_ => Self::Other,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delta(&mut self, event: &MouseEvent) -> PhysicalPosition<i32> {
|
||||
let new = PhysicalPosition::new(event.screen_x(), event.screen_y());
|
||||
|
||||
if let Some(old) = self.0 {
|
||||
let delta = PhysicalPosition::new(new.x - old.x, new.y - old.y);
|
||||
self.0 = Some(new);
|
||||
delta
|
||||
} else {
|
||||
self.0 = Some(new);
|
||||
PhysicalPosition::default()
|
||||
pub fn delta(&mut self, event: &MouseEvent) -> Position {
|
||||
match self {
|
||||
MouseDelta::Chromium => {
|
||||
PhysicalPosition::new(event.movement_x(), event.movement_y()).into()
|
||||
},
|
||||
MouseDelta::Gecko { old_position, old_delta } => {
|
||||
let new_position = mouse_position(event);
|
||||
let x = new_position.x - old_position.x + old_delta.x;
|
||||
let y = new_position.y - old_position.y + old_delta.y;
|
||||
*old_position = new_position;
|
||||
*old_delta = LogicalPosition::new(0., 0.);
|
||||
LogicalPosition::new(x, y).into()
|
||||
},
|
||||
MouseDelta::Other => {
|
||||
LogicalPosition::new(event.movement_x(), event.movement_y()).into()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ mod pointer;
|
|||
mod resize_scaling;
|
||||
mod schedule;
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub use self::canvas::{Canvas, Style};
|
||||
pub use self::event::ButtonsState;
|
||||
pub use self::event_handle::EventListenerHandle;
|
||||
|
|
@ -16,8 +18,13 @@ pub use self::resize_scaling::ResizeScaleHandle;
|
|||
pub use self::schedule::Schedule;
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use web_sys::{Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState};
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{
|
||||
Document, HtmlCanvasElement, Navigator, PageTransitionEvent, VisibilityState, Window,
|
||||
};
|
||||
|
||||
pub fn throw(msg: &str) {
|
||||
wasm_bindgen::throw_str(msg);
|
||||
|
|
@ -158,3 +165,69 @@ pub fn is_visible(document: &Document) -> bool {
|
|||
}
|
||||
|
||||
pub type RawCanvasType = HtmlCanvasElement;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Engine {
|
||||
Chromium,
|
||||
Gecko,
|
||||
WebKit,
|
||||
}
|
||||
|
||||
pub fn engine(window: &Window) -> Option<Engine> {
|
||||
static ENGINE: OnceLock<Option<Engine>> = OnceLock::new();
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(extends = Navigator)]
|
||||
type NavigatorExt;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = userAgentData)]
|
||||
fn user_agent_data(this: &NavigatorExt) -> Option<NavigatorUaData>;
|
||||
|
||||
type NavigatorUaData;
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn brands(this: &NavigatorUaData) -> Array;
|
||||
|
||||
type NavigatorUaBrandVersion;
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn brand(this: &NavigatorUaBrandVersion) -> String;
|
||||
}
|
||||
|
||||
*ENGINE.get_or_init(|| {
|
||||
let navigator: NavigatorExt = window.navigator().unchecked_into();
|
||||
|
||||
if let Some(data) = navigator.user_agent_data() {
|
||||
for brand in data
|
||||
.brands()
|
||||
.iter()
|
||||
.map(NavigatorUaBrandVersion::unchecked_from_js)
|
||||
.map(|brand| brand.brand())
|
||||
{
|
||||
match brand.as_str() {
|
||||
"Chromium" => return Some(Engine::Chromium),
|
||||
// TODO: verify when Firefox actually implements it.
|
||||
"Gecko" => return Some(Engine::Gecko),
|
||||
// TODO: verify when Safari actually implements it.
|
||||
"WebKit" => return Some(Engine::WebKit),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
} else {
|
||||
let data = navigator.user_agent().ok()?;
|
||||
|
||||
if data.contains("Chrome/") {
|
||||
Some(Engine::Chromium)
|
||||
} else if data.contains("Gecko/") {
|
||||
Some(Engine::Gecko)
|
||||
} else if data.contains("AppleWebKit/") {
|
||||
Some(Engine::WebKit)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue