mirror of
https://github.com/rust-windowing/winit.git
synced 2026-08-31 22:00:04 -04:00
Web: fix MouseMotion coordinate space (#3770)
This commit is contained in:
committed by
Kirill Chibisov
parent
bf68ac0b14
commit
949cb0f203
@@ -309,6 +309,7 @@ features = [
|
|||||||
'MediaQueryList',
|
'MediaQueryList',
|
||||||
'MessageChannel',
|
'MessageChannel',
|
||||||
'MessagePort',
|
'MessagePort',
|
||||||
|
'Navigator',
|
||||||
'Node',
|
'Node',
|
||||||
'PageTransitionEvent',
|
'PageTransitionEvent',
|
||||||
'PointerEvent',
|
'PointerEvent',
|
||||||
|
|||||||
@@ -50,3 +50,4 @@ changelog entry.
|
|||||||
- On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending
|
- On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending
|
||||||
`WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested`
|
`WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested`
|
||||||
to not be de-duplicated.
|
to not be de-duplicated.
|
||||||
|
- Account for different browser engine implementations of pointer movement coordinate space.
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use crate::dpi::LogicalPosition;
|
|
||||||
use crate::event::{MouseButton, MouseScrollDelta};
|
use crate::event::{MouseButton, MouseScrollDelta};
|
||||||
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
|
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
|
||||||
|
|
||||||
|
use dpi::{LogicalPosition, PhysicalPosition, Position};
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
use wasm_bindgen::{JsCast, JsValue};
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent};
|
use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent};
|
||||||
|
|
||||||
|
use super::Engine;
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
// https://www.w3.org/TR/pointerevents3/#the-buttons-property
|
// https://www.w3.org/TR/pointerevents3/#the-buttons-property
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -95,42 +97,48 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
|
|||||||
LogicalPosition { x: event.offset_x(), y: event.offset_y() }
|
LogicalPosition { x: event.offset_x(), y: event.offset_y() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove this when Firefox supports correct movement values in coalesced events.
|
// 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://bugzilla.mozilla.org/show_bug.cgi?id=1753724>.
|
||||||
pub struct MouseDelta(Option<MouseDeltaInner>);
|
// See <https://github.com/w3c/pointerlock/issues/42>.
|
||||||
|
pub enum MouseDelta {
|
||||||
pub struct MouseDeltaInner {
|
Chromium,
|
||||||
old_position: LogicalPosition<f64>,
|
Gecko { old_position: LogicalPosition<f64>, old_delta: LogicalPosition<f64> },
|
||||||
old_delta: LogicalPosition<f64>,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MouseDelta {
|
impl MouseDelta {
|
||||||
pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self {
|
pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self {
|
||||||
// Firefox has wrong movement values in coalesced events, we will detect that by checking
|
match super::engine(window) {
|
||||||
// for `pointerrawupdate` support. Presumably an implementation of `pointerrawupdate`
|
Some(Engine::Chromium) => Self::Chromium,
|
||||||
// should require correct movement values, otherwise uncoalesced events might be broken as
|
// Firefox has wrong movement values in coalesced events.
|
||||||
// well.
|
Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko {
|
||||||
Self((!has_pointer_raw_support(window) && has_coalesced_events_support(event)).then(|| {
|
|
||||||
MouseDeltaInner {
|
|
||||||
old_position: mouse_position(event),
|
old_position: mouse_position(event),
|
||||||
old_delta: LogicalPosition {
|
old_delta: LogicalPosition::new(
|
||||||
x: event.movement_x() as f64,
|
event.movement_x() as f64,
|
||||||
y: event.movement_y() as f64,
|
event.movement_y() as f64,
|
||||||
},
|
),
|
||||||
}
|
},
|
||||||
}))
|
_ => Self::Other,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delta(&mut self, event: &MouseEvent) -> LogicalPosition<f64> {
|
pub fn delta(&mut self, event: &MouseEvent) -> Position {
|
||||||
if let Some(inner) = &mut self.0 {
|
match self {
|
||||||
let new_position = mouse_position(event);
|
MouseDelta::Chromium => {
|
||||||
let x = new_position.x - inner.old_position.x + inner.old_delta.x;
|
PhysicalPosition::new(event.movement_x(), event.movement_y()).into()
|
||||||
let y = new_position.y - inner.old_position.y + inner.old_delta.y;
|
},
|
||||||
inner.old_position = new_position;
|
MouseDelta::Gecko { old_position, old_delta } => {
|
||||||
inner.old_delta = LogicalPosition::new(0., 0.);
|
let new_position = mouse_position(event);
|
||||||
LogicalPosition::new(x, y)
|
let x = new_position.x - old_position.x + old_delta.x;
|
||||||
} else {
|
let y = new_position.y - old_position.y + old_delta.y;
|
||||||
LogicalPosition { x: event.movement_x() as f64, y: event.movement_y() as f64 }
|
*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()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,29 +246,6 @@ pub fn pointer_move_event(event: PointerEvent) -> impl Iterator<Item = PointerEv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove when all browsers implement it correctly.
|
|
||||||
// See <https://github.com/rust-windowing/winit/issues/2875>.
|
|
||||||
pub fn has_pointer_raw_support(window: &web_sys::Window) -> bool {
|
|
||||||
thread_local! {
|
|
||||||
static POINTER_RAW_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
|
|
||||||
}
|
|
||||||
|
|
||||||
POINTER_RAW_SUPPORT.with(|support| {
|
|
||||||
*support.get_or_init(|| {
|
|
||||||
#[wasm_bindgen]
|
|
||||||
extern "C" {
|
|
||||||
type PointerRawSupport;
|
|
||||||
|
|
||||||
#[wasm_bindgen(method, getter, js_name = onpointerrawupdate)]
|
|
||||||
fn has_on_pointerrawupdate(this: &PointerRawSupport) -> JsValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let support: &PointerRawSupport = window.unchecked_ref();
|
|
||||||
!support.has_on_pointerrawupdate().is_undefined()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove when Safari supports `getCoalescedEvents`.
|
// TODO: Remove when Safari supports `getCoalescedEvents`.
|
||||||
// See <https://bugs.webkit.org/show_bug.cgi?id=210454>.
|
// See <https://bugs.webkit.org/show_bug.cgi?id=210454>.
|
||||||
pub fn has_coalesced_events_support(event: &PointerEvent) -> bool {
|
pub fn has_coalesced_events_support(event: &PointerEvent) -> bool {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ mod pointer;
|
|||||||
mod resize_scaling;
|
mod resize_scaling;
|
||||||
mod schedule;
|
mod schedule;
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
pub use self::canvas::{Canvas, Style};
|
pub use self::canvas::{Canvas, Style};
|
||||||
pub use self::event::ButtonsState;
|
pub use self::event::ButtonsState;
|
||||||
pub use self::event_handle::EventListenerHandle;
|
pub use self::event_handle::EventListenerHandle;
|
||||||
@@ -16,8 +18,13 @@ pub use self::resize_scaling::ResizeScaleHandle;
|
|||||||
pub use self::schedule::Schedule;
|
pub use self::schedule::Schedule;
|
||||||
|
|
||||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||||
|
use js_sys::Array;
|
||||||
use wasm_bindgen::closure::Closure;
|
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) {
|
pub fn throw(msg: &str) {
|
||||||
wasm_bindgen::throw_str(msg);
|
wasm_bindgen::throw_str(msg);
|
||||||
@@ -158,3 +165,69 @@ pub fn is_visible(document: &Document) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub type RawCanvasType = HtmlCanvasElement;
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user