diff --git a/clippy.toml b/clippy.toml index 84bf0d5e01..fdb6c7b57e 100644 --- a/clippy.toml +++ b/clippy.toml @@ -10,6 +10,9 @@ disallowed-methods = [ { path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" }, + { path = "web_sys::PointerEvent::pointer_type", reason = "Use `WebPointerType` to emit warnings" }, + { path = "web_sys::MouseEvent::button", reason = "Use `backend::event::cursor_button()` to avoid wrong conversions" }, + { path = "web_sys::MouseEvent::buttons", reason = "Use `backend::event::cursor_buttons()` to avoid wrong conversions" }, { path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." }, { path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" }, ] diff --git a/docs/res/ATTRIBUTION.md b/docs/res/ATTRIBUTION.md index 268316f946..e369d4d673 100644 --- a/docs/res/ATTRIBUTION.md +++ b/docs/res/ATTRIBUTION.md @@ -9,3 +9,10 @@ by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en) License. Minor modifications have been made by [John Nunley](https://github.com/notgull), which have been released under the same license as a derivative work. + +## tool_*.webp + +These files are converted versions of +[W3C Pointer Events spec images](https://github.com/w3c/pointerevents/tree/93938ae7fe0172e2ae7587ad7d7c4fc8562d7153/images) +by [patrickhlauke](https://github.com/patrickhlauke). It is licensed under the +[W3C Software and Document License](https://www.w3.org/copyright/software-license). diff --git a/docs/res/tool_altitude.webp b/docs/res/tool_altitude.webp new file mode 100644 index 0000000000..cf70af6361 Binary files /dev/null and b/docs/res/tool_altitude.webp differ diff --git a/docs/res/tool_azimuth.webp b/docs/res/tool_azimuth.webp new file mode 100644 index 0000000000..66b5a358e1 Binary files /dev/null and b/docs/res/tool_azimuth.webp differ diff --git a/docs/res/tool_tilt_x.webp b/docs/res/tool_tilt_x.webp new file mode 100644 index 0000000000..d4eb72acc7 Binary files /dev/null and b/docs/res/tool_tilt_x.webp differ diff --git a/docs/res/tool_tilt_y.webp b/docs/res/tool_tilt_y.webp new file mode 100644 index 0000000000..6488addc15 Binary files /dev/null and b/docs/res/tool_tilt_y.webp differ diff --git a/examples/window.rs b/examples/window.rs index daf1a610f9..7206b914d2 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -16,7 +16,9 @@ use rwh_06::{DisplayHandle, HasDisplayHandle}; use softbuffer::{Context, Surface}; use winit::application::ApplicationHandler; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; -use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent}; +use winit::event::{ + CursorButton, DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent, +}; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::keyboard::{Key, ModifiersState}; #[cfg(macos_platform)] @@ -373,12 +375,16 @@ impl ApplicationHandler for Application { } } }, - WindowEvent::MouseInput { button, state, .. } => { + WindowEvent::CursorInput { button, state, .. } => { let mods = window.modifiers; - if let Some(action) = - state.is_pressed().then(|| Self::process_mouse_binding(button, &mods)).flatten() - { - self.handle_action(event_loop, window_id, action); + if let CursorButton::Mouse(button) = button { + if let Some(action) = state + .is_pressed() + .then(|| Self::process_mouse_binding(button, &mods)) + .flatten() + { + self.handle_action(event_loop, window_id, action); + } } }, WindowEvent::CursorLeft { .. } => { diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 359737be66..32d9d27131 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -44,6 +44,8 @@ changelog entry. - Add `ActiveEventLoop::create_proxy()`. - On Web, implement `Error` for `platform::web::CustomCursorError`. +- Add `WindowEvent::CursorMoved::type` with a new type `CursorType` introducing pen/stylus support. + Currently only implemented on Web. ### Changed @@ -70,6 +72,9 @@ changelog entry. - Change signature of `EventLoop::run_app`, `EventLoopExtPumpEvents::pump_app_events` and `EventLoopExtRunOnDemand::run_app_on_demand` to accept a `impl ApplicationHandler` directly, instead of requiring a `&mut` reference to it. +- `Force::normalized()` now takes a `Option` to calculate the perpendicular force. +- Rename `WindowEvent::MouseButton` to `WindowEvent::CursorButton` and add `CursorButton` and + `ToolButton`. This is part of the new pen/stylus feature. ### Removed @@ -82,7 +87,9 @@ changelog entry. This feature was incomplete, and the equivalent functionality can be trivially achieved outside of `winit` using `objc2-ui-kit` and calling `UIDevice::currentDevice().userInterfaceIdiom()`. - On Web, remove unused `platform::web::CustomCursorError::Animation`. +- Remove `Force::Calibrated::altitude_angle` in favor of `ToolAngle::altitude`. ### Fixed - On MacOS, fix building with `feature = "rwh_04"`. +- On Web, device events are emitted regardless of cursor type. diff --git a/src/event.rs b/src/event.rs index 5cb8037993..355f93fc25 100644 --- a/src/event.rs +++ b/src/event.rs @@ -34,6 +34,8 @@ //! //! [`EventLoop::run_app(...)`]: crate::event_loop::EventLoop::run_app //! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil +use std::cmp::Ordering; +use std::f64; use std::path::PathBuf; use std::sync::{Mutex, Weak}; #[cfg(not(web_platform))] @@ -50,6 +52,7 @@ use crate::error::ExternalError; use crate::event_loop::AsyncRequestSerial; use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState}; use crate::platform_impl; +use crate::utils::LazyCell; #[cfg(doc)] use crate::window::Window; use crate::window::{ActivationToken, Theme, WindowId}; @@ -238,6 +241,8 @@ pub enum WindowEvent { /// the OS to implement effects such as cursor acceleration, it should not be used /// to implement non-cursor-like interactions such as 3D camera control. position: PhysicalPosition, + + r#type: CursorType, }, /// The cursor has entered the window. @@ -262,12 +267,12 @@ pub enum WindowEvent { /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform CursorLeft { device_id: DeviceId }, + /// A cursor button press has been received. + CursorInput { device_id: DeviceId, state: ElementState, button: CursorButton }, + /// A mouse wheel movement or touchpad scroll occurred. MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase }, - /// An mouse button press has been received. - MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton }, - /// Two-finger pinch gesture, often used for magnification. /// /// ## Platform-specific @@ -850,6 +855,7 @@ pub struct Touch { /// Describes the force of a touch event #[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Force { /// On iOS, the force is calibrated so that the same number corresponds to /// roughly the same amount of pressure on the screen regardless of the @@ -860,19 +866,13 @@ pub enum Force { /// /// The force reported by Apple Pencil is measured along the axis of the /// pencil. If you want a force perpendicular to the device, you need to - /// calculate this value using the `altitude_angle` value. + /// calculate this value using the [`ToolAngle::altitude`] value. force: f64, /// The maximum possible force for a touch. /// /// The value of this field is sufficiently high to provide a wide /// dynamic range for values of the `force` field. max_possible_force: f64, - /// The altitude (in radians) of the stylus. - /// - /// A value of 0 radians indicates that the stylus is parallel to the - /// surface. The value of this property is Pi/2 when the stylus is - /// perpendicular to the surface. - altitude_angle: Option, }, /// If the platform reports the force as normalized, we have no way of /// knowing how much pressure 1.0 corresponds to – we know it's the maximum @@ -887,11 +887,13 @@ impl Force { /// Instead of normalizing the force, you should prefer to handle /// [`Force::Calibrated`] so that the amount of force the user has to apply is /// consistent across devices. - pub fn normalized(&self) -> f64 { + /// + /// Passing in a [`ToolAngle`], returns the perpendicular force. + pub fn normalized(&self, angle: Option) -> f64 { match self { - Force::Calibrated { force, max_possible_force, altitude_angle } => { - let force = match altitude_angle { - Some(altitude_angle) => force / altitude_angle.sin(), + Force::Calibrated { force, max_possible_force } => { + let force = match angle { + Some(ToolAngle { altitude, .. }) => force / altitude.sin(), None => *force, }; force / max_possible_force @@ -907,6 +909,266 @@ pub type AxisId = u32; /// Identifier for a specific button on some device. pub type ButtonId = u32; +/// Cursor type. +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub enum CursorType { + Mouse, + Pen(ToolState), + Eraser(ToolState), +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ToolState { + /// The force applied to the tool against the surface. + pub force: Force, + /// Represents normalized tangential pressure, also known as barrel pressure. In the range of + /// -1 to 1. 0 means no tangential pressure is applied. [`None`] means backend or device has no + /// support. + /// + /// ## Platform-specific + /// + /// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with a value + /// of 0. + pub tangential_force: Option, + /// The clockwise rotation in degrees of a tool around its own major axis. E.g. twisting a pen + /// around its length. In the range of 0 to 359. [`None`] means backend or device has no + /// support. + /// + /// ## Platform-specific + /// + /// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with a value + /// of 0. + pub twist: Option, + /// The plane angle in degrees. [`None`] means backend or device has no support. + /// + /// ## Platform-specific + /// + /// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with default + /// values. + pub tilt: Option, + /// The angular position in radians. [`None`] means backend or device has no support. + /// + /// ## Platform-specific + /// + /// **Web:** Has no mechanism to detect device support, so this will always be [`Some`] with + /// default values unless browser support is lacking. + pub angle: Option, +} + +impl ToolState { + /// Returns [`ToolTilt`] if present or calculates it from [`ToolAngle`]. + pub fn tilt(self) -> Option { + if let Some(tilt) = self.tilt { + Some(tilt) + } else { + self.angle.map(ToolAngle::tilt) + } + } + + /// Returns [`ToolAngle`] if present or calculates it from [`ToolTilt`]. + pub fn angle(self) -> Option { + if let Some(angle) = self.angle { + Some(angle) + } else { + self.tilt.map(ToolTilt::angle) + } + } +} + +/// The plane angle in degrees of a tool. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ToolTilt { + /// The plane angle in degrees between the surface Y-Z plane and the plane containing the tool + /// and the surface Y axis. Positive values are to the right. In the range of -90 to 90. 0 + /// means the tool is perpendicular to the surface and is the default. + /// + /// ![Tilt X](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/tool_tilt_x.webp) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + pub x: i8, + /// The plane angle in degrees between the surface X-Z plane and the plane containing the tool + /// and the surface X axis. Positive values are towards the user. In the range of -90 to + /// 90. 0 means the tool is perpendicular to the surface and is the default. + /// + /// ![Tilt Y](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/tool_tilt_y.webp) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + pub y: i8, +} + +impl ToolTilt { + pub fn angle(self) -> ToolAngle { + // See . + + use std::f64::consts::*; + + const PI_0_5: f64 = FRAC_PI_2; + const PI_1_5: f64 = 3. * FRAC_PI_2; + const PI_2: f64 = 2. * PI; + + let x = LazyCell::new(|| f64::from(self.x).to_radians()); + let y = LazyCell::new(|| f64::from(self.y).to_radians()); + + let mut azimuth = 0.; + + if self.x == 0 { + match self.y.cmp(&0) { + Ordering::Greater => azimuth = PI_0_5, + Ordering::Less => azimuth = PI_1_5, + Ordering::Equal => (), + } + } else if self.y == 0 { + if self.x < 0 { + azimuth = PI; + } + } else if self.x.abs() == 90 || self.y.abs() == 90 { + // not enough information to calculate azimuth + azimuth = 0.; + } else { + // Non-boundary case: neither tiltX nor tiltY is equal to 0 or +-90 + azimuth = f64::atan2(y.tan(), x.tan()); + + if azimuth < 0. { + azimuth += PI_2; + } + } + + let altitude; + + if self.x.abs() == 90 || self.y.abs() == 90 { + altitude = 0.; + } else if self.x == 0 { + altitude = PI_0_5 - y.abs(); + } else if self.y == 0 { + altitude = PI_0_5 - x.abs(); + } else { + // Non-boundary case: neither tiltX nor tiltY is equal to 0 or +-90 + altitude = f64::atan(1. / f64::sqrt(x.tan().powi(2) + y.tan().powi(2))); + } + + ToolAngle { altitude, azimuth } + } +} + +/// The angular position in radians of a tool. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ToolAngle { + /// The altitude angle in radians between the tools perpendicular position to the surface and + /// the surface X-Y plane. In the range of 0, parallel to the surface, to π/2, perpendicular to + /// the surface. π/2 means the tool is perpendicular to the surface and is the default. + /// + /// ![Altitude angle](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/tool_altitude.webp) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + pub altitude: f64, + /// The azimuth angle in radiants representing the rotation between the major axis of the tool + /// and the surface X-Y plane. In the range of 0, 3 o'clock, progressively increasing clockwise + /// to 2π. 0 means the tool is at 3 o'clock or is perpendicular to the surface (`altitude` of + /// π/2) and is the default. + /// + /// ![Azimuth angle](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/tool_azimuth.webp) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + pub azimuth: f64, +} + +impl Default for ToolAngle { + fn default() -> Self { + Self { altitude: f64::consts::FRAC_2_PI, azimuth: 0. } + } +} + +impl ToolAngle { + pub fn tilt(self) -> ToolTilt { + // See . + + use std::f64::consts::*; + + const PI_0_5: f64 = FRAC_PI_2; + const PI_1_5: f64 = 3. * FRAC_PI_2; + const PI_2: f64 = 2. * PI; + + let mut x = 0.; + let mut y = 0.; + + if self.altitude == 0. { + if self.azimuth == 0. || self.azimuth == PI_2 { + x = FRAC_PI_2; + } else if self.azimuth == PI_0_5 { + y = FRAC_PI_2; + } else if self.azimuth == PI { + x = -FRAC_PI_2; + } else if self.azimuth == PI_1_5 { + y = -FRAC_PI_2; + } else if self.azimuth > 0. && self.azimuth < PI_0_5 { + x = FRAC_PI_2; + y = FRAC_PI_2; + } else if self.azimuth > PI_0_5 && self.azimuth < PI { + x = -FRAC_PI_2; + y = FRAC_PI_2; + } else if self.azimuth > PI && self.azimuth < PI_1_5 { + x = -FRAC_PI_2; + y = -FRAC_PI_2; + } else if self.azimuth > PI_1_5 && self.azimuth < PI_2 { + x = FRAC_PI_2; + y = -FRAC_PI_2; + } + } + + if self.altitude != 0. { + let altitude = self.altitude.tan(); + + x = f64::atan(f64::cos(self.azimuth) / altitude); + y = f64::atan(f64::sin(self.azimuth) / altitude); + } + + ToolTilt { x: x.to_degrees().round() as i8, y: y.to_degrees().round() as i8 } + } +} + +/// Input type for vaiour cursor types. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub enum CursorButton { + Mouse(MouseButton), + Pen(ToolButton), + Eraser(ToolButton), +} + +impl From for CursorButton { + fn from(mouse: MouseButton) -> Self { + Self::Mouse(mouse) + } +} + /// Describes the input state of a key. #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -939,6 +1201,28 @@ pub enum MouseButton { Other(u16), } +/// Describes a button of a tool, e.g. a pen. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub enum ToolButton { + Contact, + Barrel, + Other(u16), +} + +impl From for MouseButton { + fn from(tool: ToolButton) -> Self { + match tool { + ToolButton::Contact => MouseButton::Left, + ToolButton::Barrel => MouseButton::Right, + ToolButton::Other(1) => MouseButton::Middle, + ToolButton::Other(3) => MouseButton::Back, + ToolButton::Other(4) => MouseButton::Forward, + ToolButton::Other(other) => MouseButton::Other(other), + } + } +} + /// Describes a difference in the mouse scroll wheel state. #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -1039,7 +1323,11 @@ mod tests { with_window_event(HoveredFile("x.txt".into())); with_window_event(HoveredFileCancelled); with_window_event(Ime(Enabled)); - with_window_event(CursorMoved { device_id: did, position: (0, 0).into() }); + with_window_event(CursorMoved { + device_id: did, + position: (0, 0).into(), + r#type: event::CursorType::Mouse, + }); with_window_event(ModifiersChanged(event::Modifiers::default())); with_window_event(CursorEntered { device_id: did }); with_window_event(CursorLeft { device_id: did }); @@ -1048,10 +1336,10 @@ mod tests { delta: event::MouseScrollDelta::LineDelta(0.0, 0.0), phase: event::TouchPhase::Started, }); - with_window_event(MouseInput { + with_window_event(CursorInput { device_id: did, state: event::ElementState::Pressed, - button: event::MouseButton::Other(0), + button: event::MouseButton::Other(0).into(), }); with_window_event(PinchGesture { device_id: did, @@ -1110,21 +1398,67 @@ mod tests { }) } + #[test] + fn test_tilt_angle_conversions() { + use std::f64::consts::*; + + use event::{ToolAngle, ToolTilt}; + + // See . + const TILT_TO_ANGLE: &[(ToolTilt, ToolAngle)] = &[ + (ToolTilt { x: 0, y: 0 }, ToolAngle { altitude: FRAC_PI_2, azimuth: 0. }), + (ToolTilt { x: 0, y: 90 }, ToolAngle { altitude: 0., azimuth: FRAC_PI_2 }), + (ToolTilt { x: 0, y: -90 }, ToolAngle { altitude: 0., azimuth: 3. * FRAC_PI_2 }), + (ToolTilt { x: 90, y: 0 }, ToolAngle { altitude: 0., azimuth: 0. }), + (ToolTilt { x: 90, y: 90 }, ToolAngle { altitude: 0., azimuth: 0. }), + (ToolTilt { x: 90, y: -90 }, ToolAngle { altitude: 0., azimuth: 0. }), + (ToolTilt { x: -90, y: 0 }, ToolAngle { altitude: 0., azimuth: PI }), + (ToolTilt { x: -90, y: 90 }, ToolAngle { altitude: 0., azimuth: 0. }), + (ToolTilt { x: -90, y: -90 }, ToolAngle { altitude: 0., azimuth: 0. }), + (ToolTilt { x: 0, y: 45 }, ToolAngle { altitude: FRAC_PI_4, azimuth: FRAC_PI_2 }), + (ToolTilt { x: 0, y: -45 }, ToolAngle { altitude: FRAC_PI_4, azimuth: 3. * FRAC_PI_2 }), + (ToolTilt { x: 45, y: 0 }, ToolAngle { altitude: FRAC_PI_4, azimuth: 0. }), + (ToolTilt { x: -45, y: 0 }, ToolAngle { altitude: FRAC_PI_4, azimuth: PI }), + ]; + + for (tilt, angle) in TILT_TO_ANGLE { + assert_eq!(tilt.angle(), *angle, "{tilt:?}"); + } + + // See . + const ANGLE_TO_TILT: &[(ToolAngle, ToolTilt)] = &[ + (ToolAngle { altitude: 0., azimuth: 0. }, ToolTilt { x: 90, y: 0 }), + (ToolAngle { altitude: FRAC_PI_4, azimuth: 0. }, ToolTilt { x: 45, y: 0 }), + (ToolAngle { altitude: FRAC_PI_2, azimuth: 0. }, ToolTilt { x: 0, y: 0 }), + (ToolAngle { altitude: 0., azimuth: FRAC_PI_2 }, ToolTilt { x: 0, y: 90 }), + (ToolAngle { altitude: FRAC_PI_4, azimuth: FRAC_PI_2 }, ToolTilt { x: 0, y: 45 }), + (ToolAngle { altitude: 0., azimuth: PI }, ToolTilt { x: -90, y: 0 }), + (ToolAngle { altitude: FRAC_PI_4, azimuth: PI }, ToolTilt { x: -45, y: 0 }), + (ToolAngle { altitude: 0., azimuth: 3. * FRAC_PI_2 }, ToolTilt { x: 0, y: -90 }), + (ToolAngle { altitude: FRAC_PI_4, azimuth: 3. * FRAC_PI_2 }, ToolTilt { x: 0, y: -45 }), + ]; + + for (angle, tilt) in ANGLE_TO_TILT { + assert_eq!(angle.tilt(), *tilt, "{angle:?}"); + } + } + #[test] fn test_force_normalize() { let force = event::Force::Normalized(0.0); - assert_eq!(force.normalized(), 0.0); - - let force2 = - event::Force::Calibrated { force: 5.0, max_possible_force: 2.5, altitude_angle: None }; - assert_eq!(force2.normalized(), 2.0); - - let force3 = event::Force::Calibrated { - force: 5.0, - max_possible_force: 2.5, - altitude_angle: Some(std::f64::consts::PI / 2.0), - }; - assert_eq!(force3.normalized(), 2.0); + assert_eq!(force.normalized(None), 0.0); + + let force2 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 }; + assert_eq!(force2.normalized(None), 2.0); + + let force3 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 }; + assert_eq!( + force3.normalized(Some(event::ToolAngle { + altitude: std::f64::consts::PI / 2.0, + azimuth: 0. + })), + 2.0 + ); } #[allow(clippy::clone_on_copy)] @@ -1155,8 +1489,6 @@ mod tests { force: Some(event::Force::Normalized(0.0)), } .clone(); - let _ = - event::Force::Calibrated { force: 0.0, max_possible_force: 0.0, altitude_angle: None } - .clone(); + let _ = event::Force::Calibrated { force: 0.0, max_possible_force: 0.0 }.clone(); } } diff --git a/src/platform_impl/apple/appkit/view.rs b/src/platform_impl/apple/appkit/view.rs index a5b048ea67..d4be33b9d3 100644 --- a/src/platform_impl/apple/appkit/view.rs +++ b/src/platform_impl/apple/appkit/view.rs @@ -26,8 +26,8 @@ use super::window::WinitWindow; use super::DEVICE_ID; use crate::dpi::{LogicalPosition, LogicalSize}; use crate::event::{ - DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase, - WindowEvent, + CursorType, DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, + TouchPhase, WindowEvent, }; use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey}; use crate::platform::macos::OptionAsAlt; @@ -1033,11 +1033,11 @@ impl WinitView { } fn mouse_click(&self, event: &NSEvent, button_state: ElementState) { - let button = mouse_button(event); + let button = mouse_button(event).into(); self.update_modifiers(event, false); - self.queue_event(WindowEvent::MouseInput { + self.queue_event(WindowEvent::CursorInput { device_id: DEVICE_ID, state: button_state, button, @@ -1068,6 +1068,7 @@ impl WinitView { self.queue_event(WindowEvent::CursorMoved { device_id: DEVICE_ID, position: view_point.to_physical(self.scale_factor()), + r#type: CursorType::Mouse, }); } } diff --git a/src/platform_impl/apple/uikit/view.rs b/src/platform_impl/apple/uikit/view.rs index 0c7fd39c0a..f0a8301662 100644 --- a/src/platform_impl/apple/uikit/view.rs +++ b/src/platform_impl/apple/uikit/view.rs @@ -452,6 +452,10 @@ impl WinitView { let mut touch_events = Vec::new(); let os_supports_force = app_state::os_capabilities().force_touch; for touch in touches { + if let UITouchType::Pencil = touch.r#type() { + continue; + } + let logical_location = touch.locationInView(None); let touch_type = touch.r#type(); let force = if os_supports_force { @@ -463,16 +467,9 @@ impl WinitView { { let force = touch.force(); let max_possible_force = touch.maximumPossibleForce(); - let altitude_angle: Option = if touch_type == UITouchType::Pencil { - let angle = touch.altitudeAngle(); - Some(angle as _) - } else { - None - }; Some(Force::Calibrated { force: force as _, max_possible_force: max_possible_force as _, - altitude_angle, }) } else { None diff --git a/src/platform_impl/linux/common/xkb/mod.rs b/src/platform_impl/linux/common/xkb/mod.rs index d516883f5a..a6c04b34ca 100644 --- a/src/platform_impl/linux/common/xkb/mod.rs +++ b/src/platform_impl/linux/common/xkb/mod.rs @@ -17,7 +17,7 @@ use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle use crate::event::{ElementState, KeyEvent}; use crate::keyboard::{Key, KeyLocation}; use crate::platform_impl::KeyEventExtra; -use crate::utils::Lazy; +use crate::utils::LazyLock; mod compose; mod keymap; @@ -33,10 +33,10 @@ pub use state::XkbState; // TODO: Wire this up without using a static `AtomicBool`. static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false); -static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle); -static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle); +static XKBH: LazyLock<&'static XkbCommon> = LazyLock::new(xkbcommon_handle); +static XKBCH: LazyLock<&'static XkbCommonCompose> = LazyLock::new(xkbcommon_compose_handle); #[cfg(feature = "x11")] -static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle); +static XKBXH: LazyLock<&'static xkb::x11::XkbCommonX11> = LazyLock::new(xkbcommon_x11_handle); #[inline(always)] pub fn reset_dead_keys() { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index dd4d4c20d6..927358745d 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -29,7 +29,7 @@ use crate::platform::pump_events::PumpStatus; use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook}; pub(crate) use crate::platform_impl::Fullscreen; #[cfg(x11_platform)] -use crate::utils::Lazy; +use crate::utils::LazyLock; use crate::window::{ ActivationToken, Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, @@ -108,8 +108,8 @@ impl Default for PlatformSpecificWindowAttributes { } #[cfg(x11_platform)] -pub(crate) static X11_BACKEND: Lazy, XNotSupported>>> = - Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))); +pub(crate) static X11_BACKEND: LazyLock, XNotSupported>>> = + LazyLock::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))); #[derive(Debug, Clone)] pub enum OsError { diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index fcca59343b..599174b594 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -27,7 +27,7 @@ use sctk::seat::pointer::{ use sctk::seat::SeatState; use crate::dpi::{LogicalPosition, PhysicalPosition}; -use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}; +use crate::event::{CursorType, ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::{self, DeviceId, WindowId}; @@ -133,7 +133,7 @@ impl PointerHandler for WinitState { pointer.winit_data().inner.lock().unwrap().surface = Some(window_id); self.events_sink.push_window_event( - WindowEvent::CursorMoved { device_id, position }, + WindowEvent::CursorMoved { device_id, position, r#type: CursorType::Mouse }, window_id, ); }, @@ -148,7 +148,7 @@ impl PointerHandler for WinitState { }, PointerEventKind::Motion { .. } => { self.events_sink.push_window_event( - WindowEvent::CursorMoved { device_id, position }, + WindowEvent::CursorMoved { device_id, position, r#type: CursorType::Mouse }, window_id, ); }, @@ -164,7 +164,7 @@ impl PointerHandler for WinitState { ElementState::Released }; self.events_sink.push_window_event( - WindowEvent::MouseInput { device_id, state, button }, + WindowEvent::CursorInput { device_id, state, button: button.into() }, window_id, ); }, diff --git a/src/platform_impl/linux/x11/atoms.rs b/src/platform_impl/linux/x11/atoms.rs index 59f271d531..e5ac78f4dd 100644 --- a/src/platform_impl/linux/x11/atoms.rs +++ b/src/platform_impl/linux/x11/atoms.rs @@ -103,7 +103,14 @@ atom_manager! { _NET_SUPPORTED, _NET_SUPPORTING_WM_CHECK, _XEMBED, - _XSETTINGS_SETTINGS + _XSETTINGS_SETTINGS, + + // Stylus Atoms + ABS_X: b"Abs X", + ABS_Y: b"Abs Y", + ABS_PRESSURE: b"Abs Pressure", + ABS_TILT_X: b"Abs Tilt X", + ABS_TILT_Y: b"Abs Tilt Y" } impl Index for Atoms { diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index b0aadc3596..526cddbb9f 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -22,8 +22,8 @@ use xkbcommon_dl::xkb_mod_mask_t; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::event::{ - DeviceEvent, ElementState, Event, Ime, InnerSizeWriter, MouseButton, MouseScrollDelta, - RawKeyEvent, Touch, TouchPhase, WindowEvent, + CursorType, DeviceEvent, ElementState, Event, Ime, InnerSizeWriter, MouseButton, + MouseScrollDelta, RawKeyEvent, Touch, TouchPhase, WindowEvent, }; use crate::event_loop::ActiveEventLoop as RootAEL; use crate::keyboard::ModifiersState; @@ -35,8 +35,8 @@ use crate::platform_impl::platform::ActiveEventLoop as PlatformActiveEventLoop; use crate::platform_impl::x11::atoms::*; use crate::platform_impl::x11::util::cookie::GenericEventCookie; use crate::platform_impl::x11::{ - mkdid, mkwid, util, CookieResultExt, Device, DeviceId, DeviceInfo, Dnd, DndState, ImeReceiver, - ScrollOrientation, UnownedWindow, WindowId, + mkdid, mkwid, util, CookieResultExt, Device, DeviceId, DeviceInfo, DeviceType, Dnd, DndState, + ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, }; /// The maximum amount of X modifiers to replay. @@ -337,8 +337,11 @@ impl EventProcessor { let window_target = Self::window_target(&self.target); let mut devices = self.devices.borrow_mut(); if let Some(info) = DeviceInfo::get(&window_target.xconn, device as _) { + let wt = Self::window_target(&self.target); + let atoms = wt.x_connection().atoms(); + for info in info.iter() { - devices.insert(DeviceId(info.deviceid as _), Device::new(info)); + devices.insert(DeviceId(info.deviceid as _), Device::new(info, atoms)); } } } @@ -1055,6 +1058,15 @@ impl EventProcessor { // Set the timestamp. wt.xconn.set_timestamp(event.time as xproto::Timestamp); + let Some(DeviceType::Mouse) = self + .devices + .borrow() + .get(&DeviceId(event.sourceid as xinput::DeviceId)) + .map(|device| device.r#type) + else { + return; + }; + // Deliver multi-touch events instead of emulated mouse events. if (event.flags & xinput2::XIPointerEmulated) != 0 { return; @@ -1062,14 +1074,14 @@ impl EventProcessor { let event = match event.detail as u32 { xlib::Button1 => { - WindowEvent::MouseInput { device_id, state, button: MouseButton::Left } + WindowEvent::CursorInput { device_id, state, button: MouseButton::Left.into() } }, xlib::Button2 => { - WindowEvent::MouseInput { device_id, state, button: MouseButton::Middle } + WindowEvent::CursorInput { device_id, state, button: MouseButton::Middle.into() } }, xlib::Button3 => { - WindowEvent::MouseInput { device_id, state, button: MouseButton::Right } + WindowEvent::CursorInput { device_id, state, button: MouseButton::Right.into() } }, // Suppress emulated scroll wheel clicks, since we handle the real motion events for @@ -1087,10 +1099,14 @@ impl EventProcessor { }, phase: TouchPhase::Moved, }, - 8 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Back }, + 8 => WindowEvent::CursorInput { device_id, state, button: MouseButton::Back.into() }, - 9 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Forward }, - x => WindowEvent::MouseInput { device_id, state, button: MouseButton::Other(x as u16) }, + 9 => WindowEvent::CursorInput { device_id, state, button: MouseButton::Forward.into() }, + x => WindowEvent::CursorInput { + device_id, + state, + button: MouseButton::Other(x as u16).into(), + }, }; let event = Event::WindowEvent { window_id, event }; @@ -1106,6 +1122,15 @@ impl EventProcessor { // Set the timestamp. wt.xconn.set_timestamp(event.time as xproto::Timestamp); + let Some(DeviceType::Mouse) = self + .devices + .borrow() + .get(&DeviceId(event.sourceid as xinput::DeviceId)) + .map(|device| device.r#type) + else { + return; + }; + let device_id = mkdid(event.deviceid as xinput::DeviceId); let window = event.event as xproto::Window; let window_id = mkwid(window); @@ -1121,7 +1146,7 @@ impl EventProcessor { let event = Event::WindowEvent { window_id, - event: WindowEvent::CursorMoved { device_id, position }, + event: WindowEvent::CursorMoved { device_id, position, r#type: CursorType::Mouse }, }; callback(&self.target, event); } else if cursor_moved.is_none() { @@ -1214,7 +1239,7 @@ impl EventProcessor { let event = Event::WindowEvent { window_id, - event: WindowEvent::CursorMoved { device_id, position }, + event: WindowEvent::CursorMoved { device_id, position, r#type: CursorType::Mouse }, }; callback(&self.target, event); } @@ -1297,7 +1322,11 @@ impl EventProcessor { let event = Event::WindowEvent { window_id, - event: WindowEvent::CursorMoved { device_id: mkdid(pointer_id as _), position }, + event: WindowEvent::CursorMoved { + device_id: mkdid(pointer_id as _), + position, + r#type: CursorType::Mouse, + }, }; callback(&self.target, event); } @@ -1377,6 +1406,7 @@ impl EventProcessor { event: WindowEvent::CursorMoved { device_id: mkdid(util::VIRTUAL_CORE_POINTER), position: location.cast(), + r#type: CursorType::Mouse, }, }; callback(&self.target, event); @@ -1424,7 +1454,6 @@ impl EventProcessor { wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let did = mkdid(xev.deviceid as xinput::DeviceId); - let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; let mut value = xev.raw_values; @@ -1455,6 +1484,15 @@ impl EventProcessor { value = unsafe { value.offset(1) }; } + let Some(DeviceType::Mouse) = self + .devices + .borrow() + .get(&DeviceId(xev.sourceid as xinput::DeviceId)) + .map(|device| device.r#type) + else { + return; + }; + if let Some(mouse_delta) = mouse_delta.consume() { let event = Event::DeviceEvent { device_id: did, diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 6b95e45cc5..bba06dfdc1 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -987,6 +987,15 @@ pub struct Device { // For master devices, this is the paired device (pointer <-> keyboard). // For slave devices, this is the master. attachment: c_int, + r#type: DeviceType, +} + +#[derive(Clone, Copy, Debug)] +enum DeviceType { + Mouse, + Touch, + Pen, + Eraser, } #[derive(Debug, Copy, Clone)] @@ -1003,9 +1012,10 @@ enum ScrollOrientation { } impl Device { - fn new(info: &ffi::XIDeviceInfo) -> Self { + fn new(info: &ffi::XIDeviceInfo, atoms: &Atoms) -> Self { let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; let mut scroll_axes = Vec::new(); + let mut r#type = None; if Device::physical_device(info) { // Identify scroll axes @@ -1022,12 +1032,34 @@ impl Device { }, position: 0.0, })); + } else if ty == ffi::XITouchClass { + r#type = Some(DeviceType::Touch); + } else if r#type.is_none() && ty == ffi::XIValuatorClass { + let info = unsafe { &*(class_ptr as *const ffi::XIValuatorClassInfo) }; + let atom = info.label as xproto::Atom; + + if atom == atoms[ABS_X] + || atom == atoms[ABS_Y] + || atom == atoms[ABS_PRESSURE] + || atom == atoms[ABS_TILT_X] + || atom == atoms[ABS_TILT_Y] + { + if name.contains("eraser") { + r#type = Some(DeviceType::Eraser); + } else { + r#type = Some(DeviceType::Pen); + } + } } } } - let mut device = - Device { _name: name.into_owned(), scroll_axes, attachment: info.attachment }; + let mut device = Device { + _name: name.into_owned(), + scroll_axes, + attachment: info.attachment, + r#type: r#type.unwrap_or(DeviceType::Mouse), + }; device.reset_scroll_position(info); device } diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index b322190501..8fb79448bd 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -18,7 +18,7 @@ use super::{ }; use crate::application::ApplicationHandler; use crate::error::EventLoopError; -use crate::event::{self, Ime, Modifiers, StartCause}; +use crate::event::{self, CursorType, Ime, Modifiers, StartCause}; use crate::event_loop::{self, ControlFlow, DeviceEvents}; use crate::keyboard::{ Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode, @@ -419,6 +419,7 @@ impl EventLoop { event::WindowEvent::CursorMoved { device_id: event::DeviceId(DeviceId), position: (x, y).into(), + r#type: CursorType::Mouse, }, ); }, @@ -434,10 +435,10 @@ impl EventLoop { app.window_event( window_target, RootWindowId(window_id), - event::WindowEvent::MouseInput { + event::WindowEvent::CursorInput { device_id: event::DeviceId(DeviceId), state, - button, + button: button.into(), }, ); } diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 317ff077fa..43bc32a1cf 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -11,8 +11,9 @@ use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelE use web_time::{Duration, Instant}; use super::super::main_thread::MainThreadMarker; +use super::super::web_sys::pointer::{PointerEventExt, WebPointerType}; use super::super::DeviceId; -use super::backend; +use super::backend::{self, ButtonsState, EventListenerHandle}; use super::state::State; use crate::dpi::PhysicalSize; use crate::event::{ @@ -21,7 +22,6 @@ use crate::event::{ }; use crate::event_loop::{ControlFlow, DeviceEvents}; use crate::platform::web::{PollStrategy, WaitUntilStrategy}; -use crate::platform_impl::platform::backend::EventListenerHandle; use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner}; use crate::platform_impl::platform::window::Inner; use crate::window::WindowId; @@ -58,7 +58,7 @@ pub struct Execution { destroy_pending: RefCell>, page_transition_event_handle: RefCell>, device_events: Cell, - on_mouse_move: OnEventHandle, + on_mouse_move: OnEventHandle, on_wheel: OnEventHandle, on_mouse_press: OnEventHandle, on_mouse_release: OnEventHandle, @@ -239,27 +239,18 @@ impl Shared { *self.0.on_mouse_move.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "pointermove", - Closure::new(move |event: PointerEvent| { + Closure::new(move |event: PointerEventExt| { if !runner.device_events() { return; } - let pointer_type = event.pointer_type(); - - if pointer_type != "mouse" { - return; - } - // chorded button event let device_id = RootDeviceId(DeviceId(event.pointer_id())); - if let Some(button) = backend::event::mouse_button(&event) { - debug_assert_eq!( - pointer_type, "mouse", - "expect pointer type of a chorded button event to be a mouse" - ); - - let state = if backend::event::mouse_buttons(&event).contains(button.into()) { + if let Some(button) = backend::event::raw_button(&event) { + let state = if backend::event::cursor_buttons(&event) + .contains(ButtonsState::from_bits_retain(button)) + { ElementState::Pressed } else { ElementState::Released @@ -267,7 +258,7 @@ impl Shared { runner.send_event(Event::DeviceEvent { device_id, - event: DeviceEvent::Button { button: button.to_id(), state }, + event: DeviceEvent::Button { button: button.into(), state }, }); return; @@ -288,10 +279,13 @@ impl Shared { event: DeviceEvent::Motion { axis: 1, value: delta.y }, }); - x_motion.into_iter().chain(y_motion).chain(iter::once(Event::DeviceEvent { - device_id, - event: DeviceEvent::MouseMotion { delta: (delta.x, delta.y) }, - })) + x_motion.into_iter().chain(y_motion).chain( + matches!(WebPointerType::from_event(&event), Some(WebPointerType::Mouse)) + .then_some(Event::DeviceEvent { + device_id, + event: DeviceEvent::MouseMotion { delta: (delta.x, delta.y) }, + }), + ) })); }), )); @@ -322,15 +316,11 @@ impl Shared { return; } - if event.pointer_type() != "mouse" { - return; - } - - let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); + let button = backend::event::raw_button(&event).expect("no pointer button pressed"); runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(event.pointer_id())), event: DeviceEvent::Button { - button: button.to_id(), + button: button.into(), state: ElementState::Pressed, }, }); @@ -345,15 +335,11 @@ impl Shared { return; } - if event.pointer_type() != "mouse" { - return; - } - - let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); + let button = backend::event::raw_button(&event).expect("no pointer button pressed"); runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(event.pointer_id())), event: DeviceEvent::Button { - button: button.to_id(), + button: button.into(), state: ElementState::Released, }, }); diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index f697142a08..41cdee4539 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -296,14 +296,16 @@ impl ActiveEventLoop { } }); - runner.send_events(modifiers.into_iter().chain(events.flat_map(|position| { - let device_id = RootDeviceId(DeviceId(pointer_id)); + runner.send_events(modifiers.into_iter().chain(events.flat_map( + |(position, r#type)| { + let device_id = RootDeviceId(DeviceId(pointer_id)); - iter::once(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { device_id, position }, - }) - }))); + iter::once(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorMoved { device_id, position, r#type }, + }) + }, + ))); } }, { @@ -343,6 +345,7 @@ impl ActiveEventLoop { move |active_modifiers, pointer_id, position: crate::dpi::PhysicalPosition, + r#type, buttons, button| { let modifiers = @@ -368,11 +371,11 @@ impl ActiveEventLoop { runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { device_id, position }, + event: WindowEvent::CursorMoved { device_id, position, r#type }, }, Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::MouseInput { device_id, state, button }, + event: WindowEvent::CursorInput { device_id, state, button }, }, ])); } @@ -398,7 +401,7 @@ impl ActiveEventLoop { let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); - move |active_modifiers, pointer_id, position, button| { + move |active_modifiers, pointer_id, position, r#type, button| { let modifiers = (modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { @@ -415,11 +418,11 @@ impl ActiveEventLoop { runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { device_id, position }, + event: WindowEvent::CursorMoved { device_id, position, r#type }, }, Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::MouseInput { + event: WindowEvent::CursorInput { device_id, state: ElementState::Pressed, button, @@ -478,7 +481,7 @@ impl ActiveEventLoop { let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); - move |active_modifiers, pointer_id, position, button| { + move |active_modifiers, pointer_id, position, r#type, button| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); @@ -496,11 +499,11 @@ impl ActiveEventLoop { runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { device_id, position }, + event: WindowEvent::CursorMoved { device_id, position, r#type }, }, Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::MouseInput { + event: WindowEvent::CursorInput { device_id, state: ElementState::Released, button, diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index f515bbf5ce..036ae6db11 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -22,7 +22,7 @@ use super::pointer::PointerHandler; use super::{event, fullscreen, ButtonsState, ResizeScaleHandle}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; -use crate::event::{Force, InnerSizeWriter, MouseButton, MouseScrollDelta}; +use crate::event::{CursorButton, CursorType, Force, InnerSizeWriter, MouseScrollDelta}; use crate::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey}; use crate::platform_impl::OsError; use crate::window::{WindowAttributes, WindowId as RootWindowId}; @@ -328,60 +328,73 @@ impl Canvas { self.pointer_handler.on_cursor_enter(&self.common, handler) } - pub fn on_mouse_release( + pub fn on_mouse_release( &mut self, modifier_handler: MOD, - mouse_handler: M, + cursor_handler: C, touch_handler: T, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), + C: 'static + FnMut(ModifiersState, i32, PhysicalPosition, CursorType, CursorButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { self.pointer_handler.on_mouse_release( &self.common, modifier_handler, - mouse_handler, + cursor_handler, touch_handler, ) } - pub fn on_mouse_press( + pub fn on_mouse_press( &mut self, modifier_handler: MOD, - mouse_handler: M, + cursor_handler: C, touch_handler: T, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), + C: 'static + FnMut(ModifiersState, i32, PhysicalPosition, CursorType, CursorButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { self.pointer_handler.on_mouse_press( &self.common, modifier_handler, - mouse_handler, + cursor_handler, touch_handler, Rc::clone(&self.prevent_default), ) } - pub fn on_cursor_move( + pub fn on_cursor_move( &mut self, modifier_handler: MOD, - mouse_handler: M, + cursor_handler: C, touch_handler: T, button_handler: B, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), + C: 'static + + FnMut( + ModifiersState, + i32, + &mut dyn Iterator, CursorType)>, + ), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), - B: 'static + FnMut(ModifiersState, i32, PhysicalPosition, ButtonsState, MouseButton), + B: 'static + + FnMut( + ModifiersState, + i32, + PhysicalPosition, + CursorType, + ButtonsState, + CursorButton, + ), { self.pointer_handler.on_cursor_move( &self.common, modifier_handler, - mouse_handler, + cursor_handler, touch_handler, button_handler, Rc::clone(&self.prevent_default), diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 7d38fabdd1..ae92e48af4 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -1,4 +1,5 @@ use std::cell::OnceCell; +use std::f64; use dpi::{LogicalPosition, PhysicalPosition, Position}; use smol_str::SmolStr; @@ -6,8 +7,12 @@ use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent}; +use super::pointer::{PointerEventExt, WebPointerType}; use super::Engine; -use crate::event::{MouseButton, MouseScrollDelta}; +use crate::event::{ + CursorButton, CursorType, Force, MouseButton, MouseScrollDelta, ToolAngle, ToolButton, + ToolState, ToolTilt, +}; use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey}; bitflags::bitflags! { @@ -35,6 +40,15 @@ impl From for MouseButton { } } +impl From for ButtonsState { + fn from(cursor: CursorButton) -> Self { + match cursor { + CursorButton::Mouse(mouse) => mouse.into(), + CursorButton::Pen(tool) | CursorButton::Eraser(tool) => tool.into(), + } + } +} + impl From for ButtonsState { fn from(value: MouseButton) -> Self { match value { @@ -48,22 +62,98 @@ impl From for ButtonsState { } } -pub fn mouse_buttons(event: &MouseEvent) -> ButtonsState { +impl From for ButtonsState { + fn from(tool: ToolButton) -> Self { + match tool { + ToolButton::Contact => ButtonsState::LEFT, + ToolButton::Barrel => ButtonsState::RIGHT, + ToolButton::Other(value) => Self::from_bits_retain(value), + } + } +} + +pub fn cursor_buttons(event: &MouseEvent) -> ButtonsState { + #[allow(clippy::disallowed_methods)] ButtonsState::from_bits_retain(event.buttons()) } -pub fn mouse_button(event: &MouseEvent) -> Option { +pub fn raw_button(event: &MouseEvent) -> Option { // https://www.w3.org/TR/pointerevents3/#the-button-property - match event.button() { - -1 => None, - 0 => Some(MouseButton::Left), - 1 => Some(MouseButton::Middle), - 2 => Some(MouseButton::Right), - 3 => Some(MouseButton::Back), - 4 => Some(MouseButton::Forward), - i => { - Some(MouseButton::Other(i.try_into().expect("unexpected negative mouse button value"))) + #[allow(clippy::disallowed_methods)] + let button = event.button(); + + if button == -1 { + None + } else { + Some(button.try_into().expect("unexpected negative mouse button value")) + } +} + +pub fn cursor_button(event: &PointerEventExt, r#type: WebPointerType) -> Option { + // https://www.w3.org/TR/pointerevents3/#the-button-property + + let button = raw_button(event)?; + + let button = match r#type { + WebPointerType::Mouse => { + let button = match button { + 0 => MouseButton::Left, + 1 => MouseButton::Middle, + 2 => MouseButton::Right, + 3 => MouseButton::Back, + 4 => MouseButton::Forward, + button => MouseButton::Other(button), + }; + + CursorButton::Mouse(button) + }, + WebPointerType::Pen => { + if button == 5 { + return Some(CursorButton::Eraser(ToolButton::Contact)); + }; + + let button = match button { + 0 => ToolButton::Contact, + 2 => ToolButton::Barrel, + button => ToolButton::Other(button), + }; + + CursorButton::Pen(button) + }, + WebPointerType::Touch => unreachable!(), + }; + + Some(button) +} + +pub fn cursor_type( + event: &PointerEventExt, + r#type: WebPointerType, + button: Option<&CursorButton>, +) -> CursorType { + match r#type { + WebPointerType::Mouse => CursorType::Mouse, + WebPointerType::Pen => { + let force = Force::Normalized(event.pressure().into()); + let tangential_force = Some(event.tangential_pressure()); + let twist = Some(event.twist().try_into().expect("found invalid `twist`")); + let tilt = Some(ToolTilt { + x: event.tilt_x().try_into().expect("found invalid `tiltX`"), + y: event.tilt_y().try_into().expect("found invalid `tiltY`"), + }); + let angle = event + .altitude_angle() + .map(|altitude| ToolAngle { altitude, azimuth: event.azimuth_angle() }); + + let state = ToolState { force, tangential_force, twist, tilt, angle }; + + match button { + Some(CursorButton::Eraser(_)) => CursorType::Eraser(state), + Some(CursorButton::Pen(_)) | None => CursorType::Pen(state), + _ => unreachable!(), + } }, + WebPointerType::Touch => unreachable!(), } } @@ -80,7 +170,7 @@ impl MouseButton { } } -pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { +pub fn cursor_position(event: &MouseEvent) -> LogicalPosition { #[wasm_bindgen] extern "C" { type MouseEventExt; @@ -113,7 +203,7 @@ impl MouseDelta { 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_position: cursor_position(event), old_delta: LogicalPosition::new( event.movement_x() as f64, event.movement_y() as f64, @@ -129,7 +219,7 @@ impl MouseDelta { PhysicalPosition::new(event.movement_x(), event.movement_y()).into() }, MouseDelta::Gecko { old_position, old_delta } => { - let new_position = mouse_position(event); + let new_position = cursor_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; @@ -233,11 +323,11 @@ pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState { state } -pub fn pointer_move_event(event: PointerEvent) -> impl Iterator { +pub fn pointer_move_event(event: PointerEventExt) -> impl Iterator { // make a single iterator depending on the availability of coalesced events if has_coalesced_events_support(&event) { None.into_iter().chain( - Some(event.get_coalesced_events().into_iter().map(PointerEvent::unchecked_from_js)) + Some(event.get_coalesced_events().into_iter().map(PointerEventExt::unchecked_from_js)) .into_iter() .flatten(), ) diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 97019666b6..77f56a219c 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -5,7 +5,7 @@ mod event_handle; mod fullscreen; mod intersection_handle; mod media_query_handle; -mod pointer; +pub mod pointer; mod resize_scaling; mod schedule; diff --git a/src/platform_impl/web/web_sys/pointer.rs b/src/platform_impl/web/web_sys/pointer.rs index f5e9365d5c..4c127e8d39 100644 --- a/src/platform_impl/web/web_sys/pointer.rs +++ b/src/platform_impl/web/web_sys/pointer.rs @@ -2,22 +2,24 @@ use std::cell::Cell; use std::rc::Rc; use event::ButtonsState; -use web_sys::PointerEvent; +use tracing::warn; +use wasm_bindgen::prelude::wasm_bindgen; +use web_sys::{Event, MouseEvent, PointerEvent}; use super::canvas::Common; use super::event; use super::event_handle::EventListenerHandle; use crate::dpi::PhysicalPosition; -use crate::event::{Force, MouseButton}; +use crate::event::{CursorButton, CursorType, Force}; use crate::keyboard::ModifiersState; #[allow(dead_code)] pub(super) struct PointerHandler { on_cursor_leave: Option>, on_cursor_enter: Option>, - on_cursor_move: Option>, - on_pointer_press: Option>, - on_pointer_release: Option>, + on_cursor_move: Option>, + on_pointer_press: Option>, + on_pointer_release: Option>, on_touch_cancel: Option>, } @@ -44,7 +46,9 @@ impl PointerHandler { // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. - let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id()); + let pointer_id = + matches!(WebPointerType::from_event(&event), Some(WebPointerType::Mouse)) + .then(|| event.pointer_id()); handler(modifiers, pointer_id); })); @@ -61,62 +65,73 @@ impl PointerHandler { // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. - let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id()); + let pointer_id = + matches!(WebPointerType::from_event(&event), Some(WebPointerType::Mouse)) + .then(|| event.pointer_id()); handler(modifiers, pointer_id); })); } - pub fn on_mouse_release( + pub fn on_mouse_release( &mut self, canvas_common: &Common, mut modifier_handler: MOD, - mut mouse_handler: M, + mut cursor_handler: C, mut touch_handler: T, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), + C: 'static + FnMut(ModifiersState, i32, PhysicalPosition, CursorType, CursorButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { let window = canvas_common.window.clone(); self.on_pointer_release = - Some(canvas_common.add_event("pointerup", move |event: PointerEvent| { + Some(canvas_common.add_event("pointerup", move |event: PointerEventExt| { let modifiers = event::mouse_modifiers(&event); + let Some(r#type) = WebPointerType::from_event(&event) else { + modifier_handler(modifiers); + return; + }; - match event.pointer_type().as_str() { - "touch" => touch_handler( + match r#type { + WebPointerType::Mouse | WebPointerType::Pen => { + let button = event::cursor_button(&event, r#type); + let r#type = event::cursor_type(&event, r#type, button.as_ref()); + cursor_handler( + modifiers, + event.pointer_id(), + event::cursor_position(&event) + .to_physical(super::scale_factor(&window)), + r#type, + button.expect("no cursor button released"), + ) + }, + WebPointerType::Touch => touch_handler( modifiers, event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), + event::cursor_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ), - "mouse" => mouse_handler( - modifiers, - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), - event::mouse_button(&event).expect("no mouse button released"), - ), - _ => modifier_handler(modifiers), } })); } - pub fn on_mouse_press( + pub fn on_mouse_press( &mut self, canvas_common: &Common, mut modifier_handler: MOD, - mut mouse_handler: M, + mut cursor_handler: C, mut touch_handler: T, prevent_default: Rc>, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), + C: 'static + FnMut(ModifiersState, i32, PhysicalPosition, CursorType, CursorButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { let window = canvas_common.window.clone(); let canvas = canvas_common.raw().clone(); self.on_pointer_press = - Some(canvas_common.add_event("pointerdown", move |event: PointerEvent| { + Some(canvas_common.add_event("pointerdown", move |event: PointerEventExt| { if prevent_default.get() { // prevent text selection event.prevent_default(); @@ -125,112 +140,138 @@ impl PointerHandler { } let modifiers = event::mouse_modifiers(&event); + let Some(r#type) = WebPointerType::from_event(&event) else { + modifier_handler(modifiers); + return; + }; - match event.pointer_type().as_str() { - "touch" => { - touch_handler( - modifiers, - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), - Force::Normalized(event.pressure() as f64), - ); - }, - "mouse" => { - mouse_handler( - modifiers, - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), - event::mouse_button(&event).expect("no mouse button pressed"), - ); - + match r#type { + WebPointerType::Mouse | WebPointerType::Pen => { // Error is swallowed here since the error would occur every time the mouse // is clicked when the cursor is grabbed, and there // is probably not a situation where this could // fail, that we care if it fails. let _e = canvas.set_pointer_capture(event.pointer_id()); + + let button = event::cursor_button(&event, r#type); + let r#type = event::cursor_type(&event, r#type, button.as_ref()); + cursor_handler( + modifiers, + event.pointer_id(), + event::cursor_position(&event) + .to_physical(super::scale_factor(&window)), + r#type, + button.expect("no cursor button released"), + ) }, - _ => modifier_handler(modifiers), + WebPointerType::Touch => touch_handler( + modifiers, + event.pointer_id(), + event::cursor_position(&event).to_physical(super::scale_factor(&window)), + Force::Normalized(event.pressure() as f64), + ), } })); } - pub fn on_cursor_move( + pub fn on_cursor_move( &mut self, canvas_common: &Common, mut modifier_handler: MOD, - mut mouse_handler: M, + mut cursor_handler: C, mut touch_handler: T, mut button_handler: B, prevent_default: Rc>, ) where MOD: 'static + FnMut(ModifiersState), - M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), + C: 'static + + FnMut( + ModifiersState, + i32, + &mut dyn Iterator, CursorType)>, + ), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), - B: 'static + FnMut(ModifiersState, i32, PhysicalPosition, ButtonsState, MouseButton), + B: 'static + + FnMut( + ModifiersState, + i32, + PhysicalPosition, + CursorType, + ButtonsState, + CursorButton, + ), { let window = canvas_common.window.clone(); let canvas = canvas_common.raw().clone(); self.on_cursor_move = - Some(canvas_common.add_event("pointermove", move |event: PointerEvent| { + Some(canvas_common.add_event("pointermove", move |event: PointerEventExt| { let modifiers = event::mouse_modifiers(&event); - - let pointer_type = event.pointer_type(); - - if let "touch" | "mouse" = pointer_type.as_str() { - } else { + let Some(r#type) = WebPointerType::from_event(&event) else { modifier_handler(modifiers); return; - } + }; let id = event.pointer_id(); - // chorded button event - if let Some(button) = event::mouse_button(&event) { - debug_assert_eq!( - pointer_type, "mouse", - "expect pointer type of a chorded button event to be a mouse" - ); - - if prevent_default.get() { - // prevent text selection - event.prevent_default(); - // but still focus element - let _ = canvas.focus(); - } + match r#type { + WebPointerType::Mouse | WebPointerType::Pen => { + let button = event::cursor_button(&event, r#type); - button_handler( - modifiers, - id, - event::mouse_position(&event).to_physical(super::scale_factor(&window)), - event::mouse_buttons(&event), - button, - ); + // chorded button event + if let Some(button) = button { + if prevent_default.get() { + // prevent text selection + event.prevent_default(); + // but still focus element + let _ = canvas.focus(); + } - return; - } + let r#type = event::cursor_type(&event, r#type, Some(&button)); + button_handler( + modifiers, + id, + event::cursor_position(&event) + .to_physical(super::scale_factor(&window)), + r#type, + event::cursor_buttons(&event), + button, + ); + } else { + cursor_handler( + modifiers, + id, + &mut event::pointer_move_event(event).map(|event| { + let position = event::cursor_position(&event) + .to_physical(super::scale_factor(&window)); + let r#type = + event::cursor_type(&event, r#type, button.as_ref()); - // pointer move event - let scale = super::scale_factor(&window); - match pointer_type.as_str() { - "mouse" => mouse_handler( - modifiers, - id, - &mut event::pointer_move_event(event) - .map(|event| event::mouse_position(&event).to_physical(scale)), - ), - "touch" => touch_handler( - modifiers, - id, - &mut event::pointer_move_event(event).map(|event| { - ( - event::mouse_position(&event).to_physical(scale), - Force::Normalized(event.pressure() as f64), + (position, r#type) + }), ) - }), - ), - _ => unreachable!("didn't return early before"), - }; + } + }, + WebPointerType::Touch => { + debug_assert_eq!( + event::raw_button(&event), + None, + "expect pointer type of a chorded button event to be mouse or pen" + ); + + touch_handler( + modifiers, + id, + &mut event::pointer_move_event(event).map(|event| { + let position = event::cursor_position(&event) + .to_physical(super::scale_factor(&window)); + let pressure = event.pressure().into(); + + (position, Force::Normalized(pressure)) + }), + ) + }, + } })); } @@ -241,10 +282,10 @@ impl PointerHandler { let window = canvas_common.window.clone(); self.on_touch_cancel = Some(canvas_common.add_event("pointercancel", move |event: PointerEvent| { - if event.pointer_type() == "touch" { + if matches!(WebPointerType::from_event(&event), Some(WebPointerType::Touch)) { handler( event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor(&window)), + event::cursor_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ); } @@ -260,3 +301,39 @@ impl PointerHandler { self.on_touch_cancel = None; } } + +#[derive(Clone, Copy)] +pub enum WebPointerType { + Mouse, + Touch, + Pen, +} + +impl WebPointerType { + pub fn from_event(event: &PointerEvent) -> Option { + #[allow(clippy::disallowed_methods)] + let r#type = event.pointer_type(); + + match r#type.as_ref() { + "mouse" => Some(Self::Mouse), + "touch" => Some(Self::Touch), + "pen" => Some(Self::Pen), + r#type => { + warn!("found unknown pointer typ: {type}"); + None + }, + } + } +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = PointerEvent, extends = MouseEvent, extends = Event)] + pub type PointerEventExt; + + #[wasm_bindgen(method, getter, js_name = altitudeAngle)] + pub fn altitude_angle(this: &PointerEventExt) -> Option; + + #[wasm_bindgen(method, getter, js_name = azimuthAngle)] + pub fn azimuth_angle(this: &PointerEventExt) -> f64; +} diff --git a/src/platform_impl/windows/dark_mode.rs b/src/platform_impl/windows/dark_mode.rs index 4e3a826296..7942030337 100644 --- a/src/platform_impl/windows/dark_mode.rs +++ b/src/platform_impl/windows/dark_mode.rs @@ -11,10 +11,10 @@ use windows_sys::Win32::UI::Controls::SetWindowTheme; use windows_sys::Win32::UI::WindowsAndMessaging::{SystemParametersInfoA, SPI_GETHIGHCONTRAST}; use super::util; -use crate::utils::Lazy; +use crate::utils::LazyLock; use crate::window::Theme; -static WIN10_BUILD_VERSION: Lazy> = Lazy::new(|| { +static WIN10_BUILD_VERSION: LazyLock> = LazyLock::new(|| { type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS; let handle = get_function!("ntdll.dll", RtlGetVersion); @@ -42,7 +42,7 @@ static WIN10_BUILD_VERSION: Lazy> = Lazy::new(|| { } }); -static DARK_MODE_SUPPORTED: Lazy = Lazy::new(|| { +static DARK_MODE_SUPPORTED: LazyLock = LazyLock::new(|| { // We won't try to do anything for windows versions < 17763 // (Windows 10 October 2018 update) match *WIN10_BUILD_VERSION { @@ -51,8 +51,9 @@ static DARK_MODE_SUPPORTED: Lazy = Lazy::new(|| { } }); -static DARK_THEME_NAME: Lazy> = Lazy::new(|| util::encode_wide("DarkMode_Explorer")); -static LIGHT_THEME_NAME: Lazy> = Lazy::new(|| util::encode_wide("")); +static DARK_THEME_NAME: LazyLock> = + LazyLock::new(|| util::encode_wide("DarkMode_Explorer")); +static LIGHT_THEME_NAME: LazyLock> = LazyLock::new(|| util::encode_wide("")); /// Attempt to set a theme on a window, if necessary. /// Returns the theme that was picked @@ -99,8 +100,8 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool { cbData: usize, } - static SET_WINDOW_COMPOSITION_ATTRIBUTE: Lazy> = - Lazy::new(|| get_function!("user32.dll", SetWindowCompositionAttribute)); + static SET_WINDOW_COMPOSITION_ATTRIBUTE: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SetWindowCompositionAttribute)); if let Some(set_window_composition_attribute) = *SET_WINDOW_COMPOSITION_ATTRIBUTE { unsafe { @@ -128,19 +129,20 @@ fn should_use_dark_mode() -> bool { fn should_apps_use_dark_mode() -> bool { type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool; - static SHOULD_APPS_USE_DARK_MODE: Lazy> = Lazy::new(|| unsafe { - const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: PCSTR = 132 as PCSTR; + static SHOULD_APPS_USE_DARK_MODE: LazyLock> = + LazyLock::new(|| unsafe { + const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: PCSTR = 132 as PCSTR; - let module = LoadLibraryA("uxtheme.dll\0".as_ptr()); + let module = LoadLibraryA("uxtheme.dll\0".as_ptr()); - if module == 0 { - return None; - } + if module == 0 { + return None; + } - let handle = GetProcAddress(module, UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL); + let handle = GetProcAddress(module, UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL); - handle.map(|handle| std::mem::transmute(handle)) - }); + handle.map(|handle| std::mem::transmute(handle)) + }); SHOULD_APPS_USE_DARK_MODE .map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() }) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index c17c620779..5d4ca5260d 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -61,7 +61,8 @@ use crate::application::ApplicationHandler; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::error::EventLoopError; use crate::event::{ - DeviceEvent, Event, Force, Ime, InnerSizeWriter, RawKeyEvent, Touch, TouchPhase, WindowEvent, + CursorType, DeviceEvent, Event, Force, Ime, InnerSizeWriter, RawKeyEvent, Touch, TouchPhase, + WindowEvent, }; use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents}; use crate::keyboard::ModifiersState; @@ -81,7 +82,7 @@ use crate::platform_impl::platform::window_state::{ use crate::platform_impl::platform::{ raw_input, util, wrap_device_id, Fullscreen, WindowId, DEVICE_ID, }; -use crate::utils::Lazy; +use crate::utils::LazyLock; use crate::window::{ CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId, }; @@ -777,8 +778,8 @@ pub(crate) static DESTROY_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::Des // documentation in the `window_state` module for more information. pub(crate) static SET_RETAIN_STATE_ON_SIZE_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::SetRetainMaximized\0"); -static THREAD_EVENT_TARGET_WINDOW_CLASS: Lazy> = - Lazy::new(|| util::encode_wide("Winit Thread Event Target")); +static THREAD_EVENT_TARGET_WINDOW_CLASS: LazyLock> = + LazyLock::new(|| util::encode_wide("Winit Thread Event Target")); /// When the taskbar is created, it registers a message with the "TaskbarCreated" string and then /// broadcasts this message to all top-level windows pub(crate) static TASKBAR_CREATED: LazyMessageId = LazyMessageId::new("TaskbarCreated\0"); @@ -1553,7 +1554,11 @@ unsafe fn public_window_callback_inner( userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: CursorMoved { device_id: DEVICE_ID, position }, + event: CursorMoved { + device_id: DEVICE_ID, + position, + r#type: CursorType::Mouse, + }, }); } @@ -1632,7 +1637,7 @@ unsafe fn public_window_callback_inner( WM_LBUTTONDOWN => { use crate::event::ElementState::Pressed; use crate::event::MouseButton::Left; - use crate::event::WindowEvent::MouseInput; + use crate::event::WindowEvent::CursorInput; unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; @@ -1640,7 +1645,7 @@ unsafe fn public_window_callback_inner( userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left }, + event: CursorInput { device_id: DEVICE_ID, state: Pressed, button: Left.into() }, }); result = ProcResult::Value(0); }, @@ -1648,7 +1653,7 @@ unsafe fn public_window_callback_inner( WM_LBUTTONUP => { use crate::event::ElementState::Released; use crate::event::MouseButton::Left; - use crate::event::WindowEvent::MouseInput; + use crate::event::WindowEvent::CursorInput; unsafe { release_mouse(userdata.window_state_lock()) }; @@ -1656,7 +1661,7 @@ unsafe fn public_window_callback_inner( userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Left }, + event: CursorInput { device_id: DEVICE_ID, state: Released, button: Left.into() }, }); result = ProcResult::Value(0); }, @@ -1664,7 +1669,7 @@ unsafe fn public_window_callback_inner( WM_RBUTTONDOWN => { use crate::event::ElementState::Pressed; use crate::event::MouseButton::Right; - use crate::event::WindowEvent::MouseInput; + use crate::event::WindowEvent::CursorInput; unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; @@ -1672,7 +1677,7 @@ unsafe fn public_window_callback_inner( userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right }, + event: CursorInput { device_id: DEVICE_ID, state: Pressed, button: Right.into() }, }); result = ProcResult::Value(0); }, @@ -1680,7 +1685,7 @@ unsafe fn public_window_callback_inner( WM_RBUTTONUP => { use crate::event::ElementState::Released; use crate::event::MouseButton::Right; - use crate::event::WindowEvent::MouseInput; + use crate::event::WindowEvent::CursorInput; unsafe { release_mouse(userdata.window_state_lock()) }; @@ -1688,7 +1693,7 @@ unsafe fn public_window_callback_inner( userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Right }, + event: CursorInput { device_id: DEVICE_ID, state: Released, button: Right.into() }, }); result = ProcResult::Value(0); }, @@ -1696,7 +1701,7 @@ unsafe fn public_window_callback_inner( WM_MBUTTONDOWN => { use crate::event::ElementState::Pressed; use crate::event::MouseButton::Middle; - use crate::event::WindowEvent::MouseInput; + use crate::event::WindowEvent::CursorInput; unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; @@ -1704,7 +1709,7 @@ unsafe fn public_window_callback_inner( userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle }, + event: CursorInput { device_id: DEVICE_ID, state: Pressed, button: Middle.into() }, }); result = ProcResult::Value(0); }, @@ -1712,7 +1717,7 @@ unsafe fn public_window_callback_inner( WM_MBUTTONUP => { use crate::event::ElementState::Released; use crate::event::MouseButton::Middle; - use crate::event::WindowEvent::MouseInput; + use crate::event::WindowEvent::CursorInput; unsafe { release_mouse(userdata.window_state_lock()) }; @@ -1720,7 +1725,7 @@ unsafe fn public_window_callback_inner( userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Middle }, + event: CursorInput { device_id: DEVICE_ID, state: Released, button: Middle.into() }, }); result = ProcResult::Value(0); }, @@ -1728,7 +1733,7 @@ unsafe fn public_window_callback_inner( WM_XBUTTONDOWN => { use crate::event::ElementState::Pressed; use crate::event::MouseButton::{Back, Forward, Other}; - use crate::event::WindowEvent::MouseInput; + use crate::event::WindowEvent::CursorInput; let xbutton = super::get_xbutton_wparam(wparam as u32); unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; @@ -1737,14 +1742,15 @@ unsafe fn public_window_callback_inner( userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: MouseInput { + event: CursorInput { device_id: DEVICE_ID, state: Pressed, button: match xbutton { 1 => Back, 2 => Forward, _ => Other(xbutton), - }, + } + .into(), }, }); result = ProcResult::Value(0); @@ -1753,7 +1759,7 @@ unsafe fn public_window_callback_inner( WM_XBUTTONUP => { use crate::event::ElementState::Released; use crate::event::MouseButton::{Back, Forward, Other}; - use crate::event::WindowEvent::MouseInput; + use crate::event::WindowEvent::CursorInput; let xbutton = super::get_xbutton_wparam(wparam as u32); unsafe { release_mouse(userdata.window_state_lock()) }; @@ -1762,14 +1768,15 @@ unsafe fn public_window_callback_inner( userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: MouseInput { + event: CursorInput { device_id: DEVICE_ID, state: Released, button: match xbutton { 1 => Back, 2 => Forward, _ => Other(xbutton), - }, + } + .into(), }, }); result = ProcResult::Value(0); diff --git a/src/platform_impl/windows/keyboard_layout.rs b/src/platform_impl/windows/keyboard_layout.rs index a518886030..f616ce2ac2 100644 --- a/src/platform_impl/windows/keyboard_layout.rs +++ b/src/platform_impl/windows/keyboard_layout.rs @@ -43,10 +43,10 @@ use windows_sys::Win32::UI::TextServices::HKL; use crate::keyboard::{Key, KeyCode, ModifiersState, NamedKey, NativeKey, PhysicalKey}; use crate::platform_impl::{loword, primarylangid, scancode_to_physicalkey}; -use crate::utils::Lazy; +use crate::utils::LazyLock; -pub(crate) static LAYOUT_CACHE: Lazy> = - Lazy::new(|| Mutex::new(LayoutCache::default())); +pub(crate) static LAYOUT_CACHE: LazyLock> = + LazyLock::new(|| Mutex::new(LayoutCache::default())); fn key_pressed(vkey: VIRTUAL_KEY) -> bool { unsafe { (GetKeyState(vkey as i32) & (1 << 15)) == (1 << 15) } @@ -71,7 +71,7 @@ const NUMPAD_VKEYS: [VIRTUAL_KEY; 16] = [ VK_DIVIDE, ]; -static NUMPAD_KEYCODES: Lazy> = Lazy::new(|| { +static NUMPAD_KEYCODES: LazyLock> = LazyLock::new(|| { let mut keycodes = HashSet::new(); keycodes.insert(KeyCode::Numpad0); keycodes.insert(KeyCode::Numpad1); diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index c7a337e791..8240c37403 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -23,7 +23,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ WINDOWPLACEMENT, }; -use crate::utils::Lazy; +use crate::utils::LazyLock; use crate::window::CursorIcon; pub fn encode_wide(string: impl AsRef) -> Vec { @@ -247,27 +247,28 @@ pub type GetPointerTouchInfo = pub type GetPointerPenInfo = unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL; -pub(crate) static GET_DPI_FOR_WINDOW: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetDpiForWindow)); -pub(crate) static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy> = - Lazy::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi)); -pub(crate) static GET_DPI_FOR_MONITOR: Lazy> = - Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor)); -pub(crate) static ENABLE_NON_CLIENT_DPI_SCALING: Lazy> = - Lazy::new(|| get_function!("user32.dll", EnableNonClientDpiScaling)); -pub(crate) static SET_PROCESS_DPI_AWARENESS_CONTEXT: Lazy> = - Lazy::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext)); -pub(crate) static SET_PROCESS_DPI_AWARENESS: Lazy> = - Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness)); -pub(crate) static SET_PROCESS_DPI_AWARE: Lazy> = - Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware)); -pub(crate) static GET_POINTER_FRAME_INFO_HISTORY: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); -pub(crate) static SKIP_POINTER_FRAME_MESSAGES: Lazy> = - Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); -pub(crate) static GET_POINTER_DEVICE_RECTS: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects)); -pub(crate) static GET_POINTER_TOUCH_INFO: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo)); -pub(crate) static GET_POINTER_PEN_INFO: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo)); +pub(crate) static GET_DPI_FOR_WINDOW: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetDpiForWindow)); +pub(crate) static ADJUST_WINDOW_RECT_EX_FOR_DPI: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi)); +pub(crate) static GET_DPI_FOR_MONITOR: LazyLock> = + LazyLock::new(|| get_function!("shcore.dll", GetDpiForMonitor)); +pub(crate) static ENABLE_NON_CLIENT_DPI_SCALING: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", EnableNonClientDpiScaling)); +pub(crate) static SET_PROCESS_DPI_AWARENESS_CONTEXT: LazyLock< + Option, +> = LazyLock::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext)); +pub(crate) static SET_PROCESS_DPI_AWARENESS: LazyLock> = + LazyLock::new(|| get_function!("shcore.dll", SetProcessDpiAwareness)); +pub(crate) static SET_PROCESS_DPI_AWARE: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SetProcessDPIAware)); +pub(crate) static GET_POINTER_FRAME_INFO_HISTORY: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); +pub(crate) static SKIP_POINTER_FRAME_MESSAGES: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); +pub(crate) static GET_POINTER_DEVICE_RECTS: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerDeviceRects)); +pub(crate) static GET_POINTER_TOUCH_INFO: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerTouchInfo)); +pub(crate) static GET_POINTER_PEN_INFO: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerPenInfo)); diff --git a/src/utils.rs b/src/utils.rs index d9631edbb1..4a7d3034f1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,21 +4,22 @@ // This isn't used on every platform, which can come up as dead code warnings. #![allow(dead_code)] +use std::cell::OnceCell; use std::ops::Deref; use std::sync::OnceLock; -pub(crate) struct Lazy { +pub(crate) struct LazyLock { cell: OnceLock, init: fn() -> T, } -impl Lazy { +impl LazyLock { pub const fn new(f: fn() -> T) -> Self { Self { cell: OnceLock::new(), init: f } } } -impl Deref for Lazy { +impl Deref for LazyLock { type Target = T; #[inline] @@ -26,3 +27,22 @@ impl Deref for Lazy { self.cell.get_or_init(self.init) } } + +pub(crate) struct LazyCell T> { + cell: OnceCell, + init: F, +} +impl T> LazyCell { + pub const fn new(f: F) -> Self { + Self { cell: OnceCell::new(), init: f } + } +} + +impl T> Deref for LazyCell { + type Target = T; + + #[inline] + fn deref(&self) -> &'_ T { + self.cell.get_or_init(&self.init) + } +}