winit-core:winit: add tablet input support
The API is integrated into the `WindowEvent::Pointer*` API and is present in form of `TabletTool` variant on corresponding data entries. For now implemented for Web, Windows, and with limitations for Wayland. Fixes #99. Co-authored-by: daxpedda <daxpedda@gmail.com>
This commit is contained in:
parent
ffcdf80192
commit
f046e778aa
23 changed files with 1206 additions and 204 deletions
|
|
@ -10,6 +10,9 @@ disallowed-methods = [
|
||||||
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
|
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
|
||||||
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
|
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
|
||||||
{ allow-invalid = true, path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
|
{ allow-invalid = true, path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::MouseEvent::buttons", reason = "Use `backend::event::cursor_buttons()` to avoid wrong conversions" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::MouseEvent::button", reason = "Use `backend::event::cursor_button()` to avoid wrong conversions" },
|
||||||
|
{ allow-invalid = true, path = "web_sys::PointerEvent::pointer_type", reason = "Use `WebPointerType` to emit warnings" },
|
||||||
{ allow-invalid = true, path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
|
{ allow-invalid = true, path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
|
||||||
{ allow-invalid = true, path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
|
{ allow-invalid = true, path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
|
||||||
{ allow-invalid = true, path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" },
|
{ allow-invalid = true, path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" },
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
//! The event enums and assorted supporting types.
|
//! The event enums and assorted supporting types.
|
||||||
|
use std::cell::LazyCell;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::f64;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Mutex, Weak};
|
use std::sync::{Mutex, Weak};
|
||||||
|
|
||||||
|
|
@ -435,6 +438,7 @@ pub enum PointerKind {
|
||||||
///
|
///
|
||||||
/// **macOS:** Unsupported.
|
/// **macOS:** Unsupported.
|
||||||
Touch(FingerId),
|
Touch(FingerId),
|
||||||
|
TabletTool(TabletToolKind),
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -485,6 +489,13 @@ pub enum PointerSource {
|
||||||
/// force will be 0.5 when a button is pressed or 0.0 otherwise.
|
/// force will be 0.5 when a button is pressed or 0.0 otherwise.
|
||||||
force: Option<Force>,
|
force: Option<Force>,
|
||||||
},
|
},
|
||||||
|
TabletTool {
|
||||||
|
/// Describes as which tool kind the interaction happened.
|
||||||
|
kind: TabletToolKind,
|
||||||
|
|
||||||
|
/// Describes how the tool was held and used.
|
||||||
|
data: TabletToolData,
|
||||||
|
},
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -493,6 +504,7 @@ impl From<PointerSource> for PointerKind {
|
||||||
match source {
|
match source {
|
||||||
PointerSource::Mouse => Self::Mouse,
|
PointerSource::Mouse => Self::Mouse,
|
||||||
PointerSource::Touch { finger_id, .. } => Self::Touch(finger_id),
|
PointerSource::Touch { finger_id, .. } => Self::Touch(finger_id),
|
||||||
|
PointerSource::TabletTool { kind, .. } => Self::TabletTool(kind),
|
||||||
PointerSource::Unknown => Self::Unknown,
|
PointerSource::Unknown => Self::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -502,7 +514,7 @@ impl From<PointerSource> for PointerKind {
|
||||||
///
|
///
|
||||||
/// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the
|
/// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the
|
||||||
/// system.
|
/// system.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum ButtonSource {
|
pub enum ButtonSource {
|
||||||
Mouse(MouseButton),
|
Mouse(MouseButton),
|
||||||
/// See [`PointerSource::Touch`] for more details.
|
/// See [`PointerSource::Touch`] for more details.
|
||||||
|
|
@ -514,6 +526,11 @@ pub enum ButtonSource {
|
||||||
finger_id: FingerId,
|
finger_id: FingerId,
|
||||||
force: Option<Force>,
|
force: Option<Force>,
|
||||||
},
|
},
|
||||||
|
TabletTool {
|
||||||
|
kind: TabletToolKind,
|
||||||
|
button: TabletToolButton,
|
||||||
|
data: TabletToolData,
|
||||||
|
},
|
||||||
Unknown(u16),
|
Unknown(u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -525,6 +542,7 @@ impl ButtonSource {
|
||||||
match self {
|
match self {
|
||||||
ButtonSource::Mouse(mouse) => mouse,
|
ButtonSource::Mouse(mouse) => mouse,
|
||||||
ButtonSource::Touch { .. } => MouseButton::Left,
|
ButtonSource::Touch { .. } => MouseButton::Left,
|
||||||
|
ButtonSource::TabletTool { button, .. } => button.into(),
|
||||||
ButtonSource::Unknown(button) => match button {
|
ButtonSource::Unknown(button) => match button {
|
||||||
0 => MouseButton::Left,
|
0 => MouseButton::Left,
|
||||||
1 => MouseButton::Middle,
|
1 => MouseButton::Middle,
|
||||||
|
|
@ -981,6 +999,7 @@ pub enum TouchPhase {
|
||||||
/// Describes the force of a touch event
|
/// Describes the force of a touch event
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[doc(alias = "Pressure")]
|
||||||
pub enum Force {
|
pub enum Force {
|
||||||
/// On iOS, the force is calibrated so that the same number corresponds to
|
/// 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
|
/// roughly the same amount of pressure on the screen regardless of the
|
||||||
|
|
@ -991,7 +1010,7 @@ pub enum Force {
|
||||||
///
|
///
|
||||||
/// The force reported by Apple Pencil is measured along the axis of the
|
/// 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
|
/// 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 [`TabletToolAngle::altitude`] value.
|
||||||
force: f64,
|
force: f64,
|
||||||
/// The maximum possible force for a touch.
|
/// The maximum possible force for a touch.
|
||||||
///
|
///
|
||||||
|
|
@ -1012,9 +1031,17 @@ impl Force {
|
||||||
/// Instead of normalizing the force, you should prefer to handle
|
/// Instead of normalizing the force, you should prefer to handle
|
||||||
/// [`Force::Calibrated`] so that the amount of force the user has to apply is
|
/// [`Force::Calibrated`] so that the amount of force the user has to apply is
|
||||||
/// consistent across devices.
|
/// consistent across devices.
|
||||||
pub fn normalized(&self) -> f64 {
|
///
|
||||||
|
/// Passing in a [`TabletToolAngle`], returns the perpendicular force.
|
||||||
|
pub fn normalized(&self, angle: Option<TabletToolAngle>) -> f64 {
|
||||||
match self {
|
match self {
|
||||||
Force::Calibrated { force, max_possible_force } => force / max_possible_force,
|
Force::Calibrated { force, max_possible_force } => {
|
||||||
|
let force = match angle {
|
||||||
|
Some(TabletToolAngle { altitude, .. }) => force / altitude.sin(),
|
||||||
|
None => *force,
|
||||||
|
};
|
||||||
|
force / max_possible_force
|
||||||
|
},
|
||||||
Force::Normalized(force) => *force,
|
Force::Normalized(force) => *force,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1026,6 +1053,264 @@ pub type AxisId = u32;
|
||||||
/// Identifier for a specific button on some device.
|
/// Identifier for a specific button on some device.
|
||||||
pub type ButtonId = u32;
|
pub type ButtonId = u32;
|
||||||
|
|
||||||
|
/// Tablet of the tablet tool.
|
||||||
|
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum TabletToolKind {
|
||||||
|
#[default]
|
||||||
|
Pen,
|
||||||
|
Eraser,
|
||||||
|
Brush,
|
||||||
|
Pencil,
|
||||||
|
Airbrush,
|
||||||
|
Finger,
|
||||||
|
Mouse,
|
||||||
|
Lens,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||||
|
pub struct TabletToolData {
|
||||||
|
/// The force applied to the tool against the surface.
|
||||||
|
///
|
||||||
|
/// When the force information is not available, [`None`] is returned.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// **Web:** Has no mechanism to detect support, so this will always be [`Some`].
|
||||||
|
pub force: Option<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<f32>,
|
||||||
|
/// 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<u16>,
|
||||||
|
/// 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<TabletToolTilt>,
|
||||||
|
/// 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<TabletToolAngle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabletToolData {
|
||||||
|
/// Returns [`TabletToolTilt`] if present or calculates it from [`TabletToolAngle`].
|
||||||
|
pub fn tilt(self) -> Option<TabletToolTilt> {
|
||||||
|
if let Some(tilt) = self.tilt {
|
||||||
|
Some(tilt)
|
||||||
|
} else {
|
||||||
|
self.angle.map(TabletToolAngle::tilt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns [`TabletToolAngle`] if present or calculates it from [`TabletToolTilt`].
|
||||||
|
pub fn angle(self) -> Option<TabletToolAngle> {
|
||||||
|
if let Some(angle) = self.angle {
|
||||||
|
Some(angle)
|
||||||
|
} else {
|
||||||
|
self.tilt.map(TabletToolTilt::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 TabletToolTilt {
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// 
|
||||||
|
///
|
||||||
|
/// <sub>
|
||||||
|
/// For image attribution, see the
|
||||||
|
/// <a href="https://github.com/rust-windowing/winit/blob/master/winit/docs/ATTRIBUTION.md">
|
||||||
|
/// ATTRIBUTION.md
|
||||||
|
/// </a>
|
||||||
|
/// file.
|
||||||
|
/// </sub>
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// 
|
||||||
|
///
|
||||||
|
/// <sub>
|
||||||
|
/// For image attribution, see the
|
||||||
|
/// <a href="https://github.com/rust-windowing/winit/blob/master/winit/docs/ATTRIBUTION.md">
|
||||||
|
/// ATTRIBUTION.md
|
||||||
|
/// </a>
|
||||||
|
/// file.
|
||||||
|
/// </sub>
|
||||||
|
pub y: i8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabletToolTilt {
|
||||||
|
pub fn angle(self) -> TabletToolAngle {
|
||||||
|
// See <https://www.w3.org/TR/2024/WD-pointerevents3-20240326/#converting-between-tiltx-tilty-and-altitudeangle-azimuthangle>.
|
||||||
|
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TabletToolAngle { altitude, azimuth }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The angular position in radians of a tool.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||||
|
pub struct TabletToolAngle {
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// 
|
||||||
|
///
|
||||||
|
/// <sub>
|
||||||
|
/// For image attribution, see the
|
||||||
|
/// <a href="https://github.com/rust-windowing/winit/blob/master/docs/res/ATTRIBUTION.md">
|
||||||
|
/// ATTRIBUTION.md
|
||||||
|
/// </a>
|
||||||
|
/// file.
|
||||||
|
/// </sub>
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// 
|
||||||
|
///
|
||||||
|
/// <sub>
|
||||||
|
/// For image attribution, see the
|
||||||
|
/// <a href="https://github.com/rust-windowing/winit/blob/master/docs/res/ATTRIBUTION.md">
|
||||||
|
/// ATTRIBUTION.md
|
||||||
|
/// </a>
|
||||||
|
/// file.
|
||||||
|
/// </sub>
|
||||||
|
pub azimuth: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TabletToolAngle {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { altitude: f64::consts::FRAC_2_PI, azimuth: 0. }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabletToolAngle {
|
||||||
|
pub fn tilt(self) -> TabletToolTilt {
|
||||||
|
// See <https://www.w3.org/TR/2024/WD-pointerevents3-20240326/#converting-between-tiltx-tilty-and-altitudeangle-azimuthangle>.
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
TabletToolTilt { x: x.to_degrees().round() as i8, y: y.to_degrees().round() as i8 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes the input state of a key.
|
/// Describes the input state of a key.
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
|
@ -1058,6 +1343,28 @@ pub enum MouseButton {
|
||||||
Other(u16),
|
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 TabletToolButton {
|
||||||
|
Contact,
|
||||||
|
Barrel,
|
||||||
|
Other(u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TabletToolButton> for MouseButton {
|
||||||
|
fn from(tool: TabletToolButton) -> Self {
|
||||||
|
match tool {
|
||||||
|
TabletToolButton::Contact => MouseButton::Left,
|
||||||
|
TabletToolButton::Barrel => MouseButton::Right,
|
||||||
|
TabletToolButton::Other(1) => MouseButton::Middle,
|
||||||
|
TabletToolButton::Other(3) => MouseButton::Back,
|
||||||
|
TabletToolButton::Other(4) => MouseButton::Forward,
|
||||||
|
TabletToolButton::Other(other) => MouseButton::Other(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes a difference in the mouse scroll wheel state.
|
/// Describes a difference in the mouse scroll wheel state.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
|
@ -1241,16 +1548,85 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tilt_angle_conversions() {
|
||||||
|
use std::f64::consts::*;
|
||||||
|
|
||||||
|
use event::{TabletToolAngle, TabletToolTilt};
|
||||||
|
|
||||||
|
// See <https://github.com/web-platform-tests/wpt/blob/5af3e9c2a2aba76ade00f0dbc3486e50a74a4506/pointerevents/pointerevent_tiltX_tiltY_to_azimuth_altitude.html#L11-L23>.
|
||||||
|
const TILT_TO_ANGLE: &[(TabletToolTilt, TabletToolAngle)] = &[
|
||||||
|
(TabletToolTilt { x: 0, y: 0 }, TabletToolAngle { altitude: FRAC_PI_2, azimuth: 0. }),
|
||||||
|
(TabletToolTilt { x: 0, y: 90 }, TabletToolAngle { altitude: 0., azimuth: FRAC_PI_2 }),
|
||||||
|
(TabletToolTilt { x: 0, y: -90 }, TabletToolAngle {
|
||||||
|
altitude: 0.,
|
||||||
|
azimuth: 3. * FRAC_PI_2,
|
||||||
|
}),
|
||||||
|
(TabletToolTilt { x: 90, y: 0 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
|
||||||
|
(TabletToolTilt { x: 90, y: 90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
|
||||||
|
(TabletToolTilt { x: 90, y: -90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
|
||||||
|
(TabletToolTilt { x: -90, y: 0 }, TabletToolAngle { altitude: 0., azimuth: PI }),
|
||||||
|
(TabletToolTilt { x: -90, y: 90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
|
||||||
|
(TabletToolTilt { x: -90, y: -90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
|
||||||
|
(TabletToolTilt { x: 0, y: 45 }, TabletToolAngle {
|
||||||
|
altitude: FRAC_PI_4,
|
||||||
|
azimuth: FRAC_PI_2,
|
||||||
|
}),
|
||||||
|
(TabletToolTilt { x: 0, y: -45 }, TabletToolAngle {
|
||||||
|
altitude: FRAC_PI_4,
|
||||||
|
azimuth: 3. * FRAC_PI_2,
|
||||||
|
}),
|
||||||
|
(TabletToolTilt { x: 45, y: 0 }, TabletToolAngle { altitude: FRAC_PI_4, azimuth: 0. }),
|
||||||
|
(TabletToolTilt { x: -45, y: 0 }, TabletToolAngle { altitude: FRAC_PI_4, azimuth: PI }),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (tilt, angle) in TILT_TO_ANGLE {
|
||||||
|
assert_eq!(tilt.angle(), *angle, "{tilt:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// See <https://github.com/web-platform-tests/wpt/blob/5af3e9c2a2aba76ade00f0dbc3486e50a74a4506/pointerevents/pointerevent_tiltX_tiltY_to_azimuth_altitude.html#L38-L46>.
|
||||||
|
const ANGLE_TO_TILT: &[(TabletToolAngle, TabletToolTilt)] = &[
|
||||||
|
(TabletToolAngle { altitude: 0., azimuth: 0. }, TabletToolTilt { x: 90, y: 0 }),
|
||||||
|
(TabletToolAngle { altitude: FRAC_PI_4, azimuth: 0. }, TabletToolTilt { x: 45, y: 0 }),
|
||||||
|
(TabletToolAngle { altitude: FRAC_PI_2, azimuth: 0. }, TabletToolTilt { x: 0, y: 0 }),
|
||||||
|
(TabletToolAngle { altitude: 0., azimuth: FRAC_PI_2 }, TabletToolTilt { x: 0, y: 90 }),
|
||||||
|
(TabletToolAngle { altitude: FRAC_PI_4, azimuth: FRAC_PI_2 }, TabletToolTilt {
|
||||||
|
x: 0,
|
||||||
|
y: 45,
|
||||||
|
}),
|
||||||
|
(TabletToolAngle { altitude: 0., azimuth: PI }, TabletToolTilt { x: -90, y: 0 }),
|
||||||
|
(TabletToolAngle { altitude: FRAC_PI_4, azimuth: PI }, TabletToolTilt { x: -45, y: 0 }),
|
||||||
|
(TabletToolAngle { altitude: 0., azimuth: 3. * FRAC_PI_2 }, TabletToolTilt {
|
||||||
|
x: 0,
|
||||||
|
y: -90,
|
||||||
|
}),
|
||||||
|
(TabletToolAngle { altitude: FRAC_PI_4, azimuth: 3. * FRAC_PI_2 }, TabletToolTilt {
|
||||||
|
x: 0,
|
||||||
|
y: -45,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (angle, tilt) in ANGLE_TO_TILT {
|
||||||
|
assert_eq!(angle.tilt(), *tilt, "{angle:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_force_normalize() {
|
fn test_force_normalize() {
|
||||||
let force = event::Force::Normalized(0.0);
|
let force = event::Force::Normalized(0.0);
|
||||||
assert_eq!(force.normalized(), 0.0);
|
assert_eq!(force.normalized(None), 0.0);
|
||||||
|
|
||||||
let force2 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 };
|
let force2 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 };
|
||||||
assert_eq!(force2.normalized(), 2.0);
|
assert_eq!(force2.normalized(None), 2.0);
|
||||||
|
|
||||||
let force3 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 };
|
let force3 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 };
|
||||||
assert_eq!(force3.normalized(), 2.0);
|
assert_eq!(
|
||||||
|
force3.normalized(Some(event::TabletToolAngle {
|
||||||
|
altitude: std::f64::consts::PI / 2.0,
|
||||||
|
azimuth: 0.
|
||||||
|
})),
|
||||||
|
2.0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::clone_on_copy)]
|
#[allow(clippy::clone_on_copy)]
|
||||||
|
|
|
||||||
|
|
@ -461,6 +461,10 @@ impl WinitView {
|
||||||
let window = self.window().unwrap();
|
let window = self.window().unwrap();
|
||||||
let mut touch_events = Vec::new();
|
let mut touch_events = Vec::new();
|
||||||
for touch in touches {
|
for touch in touches {
|
||||||
|
if let UITouchType::Pencil = touch.r#type() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let logical_location = touch.locationInView(None);
|
let logical_location = touch.locationInView(None);
|
||||||
let touch_type = touch.r#type();
|
let touch_type = touch.r#type();
|
||||||
let force = if let UITouchType::Pencil = touch_type {
|
let force = if let UITouchType::Pencil = touch_type {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
|
||||||
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
|
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1;
|
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1;
|
||||||
|
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::ZwpTabletSeatV2;
|
||||||
use winit_core::event::WindowEvent;
|
use winit_core::event::WindowEvent;
|
||||||
use winit_core::keyboard::ModifiersState;
|
use winit_core::keyboard::ModifiersState;
|
||||||
|
|
||||||
|
|
@ -50,6 +51,9 @@ pub struct WinitSeatState {
|
||||||
/// The text input bound on the seat.
|
/// The text input bound on the seat.
|
||||||
text_input: Option<Arc<ZwpTextInputV3>>,
|
text_input: Option<Arc<ZwpTextInputV3>>,
|
||||||
|
|
||||||
|
/// The tablet input bound on the seat.
|
||||||
|
tablet: Option<Arc<ZwpTabletSeatV2>>,
|
||||||
|
|
||||||
/// The relative pointer bound on the seat.
|
/// The relative pointer bound on the seat.
|
||||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||||
|
|
||||||
|
|
@ -156,6 +160,12 @@ impl SeatHandler for WinitState {
|
||||||
TextInputData::default(),
|
TextInputData::default(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(tablet_state) =
|
||||||
|
seat_state.tablet.is_none().then_some(self.tablet_state.as_ref()).flatten()
|
||||||
|
{
|
||||||
|
seat_state.tablet = Some(Arc::new(tablet_state.get_tablet_seat(&seat, queue_handle)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_capability(
|
fn remove_capability(
|
||||||
|
|
@ -177,6 +187,11 @@ impl SeatHandler for WinitState {
|
||||||
text_input.destroy();
|
text_input.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: figure out when this should actually be destroyed.
|
||||||
|
if let Some(tablet) = seat_state.tablet.take() {
|
||||||
|
tablet.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
match capability {
|
match capability {
|
||||||
SeatCapability::Touch => {
|
SeatCapability::Touch => {
|
||||||
if let Some(touch) = seat_state.touch.take() {
|
if let Some(touch) = seat_state.touch.take() {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ use crate::seat::{
|
||||||
};
|
};
|
||||||
use crate::types::kwin_blur::KWinBlurManager;
|
use crate::types::kwin_blur::KWinBlurManager;
|
||||||
use crate::types::wp_fractional_scaling::FractionalScalingManager;
|
use crate::types::wp_fractional_scaling::FractionalScalingManager;
|
||||||
|
use crate::types::wp_tablet_input_v2::TabletManager;
|
||||||
use crate::types::wp_viewporter::ViewporterState;
|
use crate::types::wp_viewporter::ViewporterState;
|
||||||
use crate::types::xdg_activation::XdgActivationState;
|
use crate::types::xdg_activation::XdgActivationState;
|
||||||
use crate::types::xdg_toplevel_icon_manager::XdgToplevelIconManagerState;
|
use crate::types::xdg_toplevel_icon_manager::XdgToplevelIconManagerState;
|
||||||
|
|
@ -100,6 +101,9 @@ pub struct WinitState {
|
||||||
/// Relative pointer.
|
/// Relative pointer.
|
||||||
pub relative_pointer: Option<RelativePointerState>,
|
pub relative_pointer: Option<RelativePointerState>,
|
||||||
|
|
||||||
|
/// Tablet manager.
|
||||||
|
pub tablet_state: Option<TabletManager>,
|
||||||
|
|
||||||
/// Pointer constraints to handle pointer locking and confining.
|
/// Pointer constraints to handle pointer locking and confining.
|
||||||
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
|
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
|
||||||
|
|
||||||
|
|
@ -194,6 +198,7 @@ impl WinitState {
|
||||||
text_input_state: TextInputState::new(globals, queue_handle).ok(),
|
text_input_state: TextInputState::new(globals, queue_handle).ok(),
|
||||||
|
|
||||||
relative_pointer: RelativePointerState::new(globals, queue_handle).ok(),
|
relative_pointer: RelativePointerState::new(globals, queue_handle).ok(),
|
||||||
|
tablet_state: TabletManager::new(globals, queue_handle).ok(),
|
||||||
pointer_constraints: PointerConstraintsState::new(globals, queue_handle)
|
pointer_constraints: PointerConstraintsState::new(globals, queue_handle)
|
||||||
.map(Arc::new)
|
.map(Arc::new)
|
||||||
.ok(),
|
.ok(),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
pub mod kwin_blur;
|
pub mod kwin_blur;
|
||||||
pub mod wp_fractional_scaling;
|
pub mod wp_fractional_scaling;
|
||||||
|
pub mod wp_tablet_input_v2;
|
||||||
pub mod wp_viewporter;
|
pub mod wp_viewporter;
|
||||||
pub mod xdg_activation;
|
pub mod xdg_activation;
|
||||||
pub mod xdg_toplevel_icon_manager;
|
pub mod xdg_toplevel_icon_manager;
|
||||||
|
|
|
||||||
343
winit-wayland/src/types/wp_tablet_input_v2.rs
Normal file
343
winit-wayland/src/types/wp_tablet_input_v2.rs
Normal file
|
|
@ -0,0 +1,343 @@
|
||||||
|
//! Handling of wp_tablet_input_v2.
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use dpi::LogicalPosition;
|
||||||
|
use sctk::compositor::SurfaceData;
|
||||||
|
use sctk::globals::GlobalData;
|
||||||
|
use sctk::reexports::client::backend::smallvec::SmallVec;
|
||||||
|
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||||
|
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||||
|
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||||
|
use sctk::reexports::client::{
|
||||||
|
delegate_dispatch, event_created_child, Connection, Dispatch, Proxy, QueueHandle, WEnum,
|
||||||
|
};
|
||||||
|
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_manager_v2::ZwpTabletManagerV2;
|
||||||
|
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_pad_v2::ZwpTabletPadV2;
|
||||||
|
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::{
|
||||||
|
self, ZwpTabletSeatV2,
|
||||||
|
};
|
||||||
|
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_tool_v2::{
|
||||||
|
ButtonState, Event as ToolEvent, Type as ToolType, ZwpTabletToolV2,
|
||||||
|
};
|
||||||
|
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_v2::ZwpTabletV2;
|
||||||
|
use winit_core::event::{
|
||||||
|
ButtonSource, ElementState, Force, PointerKind, PointerSource, TabletToolButton,
|
||||||
|
TabletToolData as CoreTabletToolData, TabletToolKind, TabletToolTilt, WindowEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::state::WinitState;
|
||||||
|
|
||||||
|
/// KWin blur manager.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TabletManager {
|
||||||
|
manager: ZwpTabletManagerV2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabletManager {
|
||||||
|
pub fn new(
|
||||||
|
globals: &GlobalList,
|
||||||
|
queue_handle: &QueueHandle<WinitState>,
|
||||||
|
) -> Result<Self, BindError> {
|
||||||
|
// Ignore v2 since we are not interested in its events.
|
||||||
|
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||||
|
Ok(Self { manager })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tablet_seat(
|
||||||
|
&self,
|
||||||
|
seat: &WlSeat,
|
||||||
|
queue_handle: &QueueHandle<WinitState>,
|
||||||
|
) -> ZwpTabletSeatV2 {
|
||||||
|
self.manager.get_tablet_seat(seat, queue_handle, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Dispatch<ZwpTabletManagerV2, GlobalData, WinitState> for TabletManager {
|
||||||
|
fn event(
|
||||||
|
_: &mut WinitState,
|
||||||
|
_: &ZwpTabletManagerV2,
|
||||||
|
_: <ZwpTabletManagerV2 as Proxy>::Event,
|
||||||
|
_: &GlobalData,
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<WinitState>,
|
||||||
|
) {
|
||||||
|
unreachable!("no events defined for zwp_tablet_manager_v2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<ZwpTabletManagerV2, (), WinitState> for TabletManager {
|
||||||
|
fn event(
|
||||||
|
_: &mut WinitState,
|
||||||
|
_: &ZwpTabletManagerV2,
|
||||||
|
_: <ZwpTabletManagerV2 as Proxy>::Event,
|
||||||
|
_: &(),
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<WinitState>,
|
||||||
|
) {
|
||||||
|
unreachable!("no events defined for zwp_tablet_manager_v2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<ZwpTabletSeatV2, (), WinitState> for TabletManager {
|
||||||
|
event_created_child!(WinitState, ZwpTabletSeatV2, [
|
||||||
|
zwp_tablet_seat_v2::EVT_TABLET_ADDED_OPCODE => (ZwpTabletV2, Default::default()),
|
||||||
|
zwp_tablet_seat_v2::EVT_TOOL_ADDED_OPCODE => (ZwpTabletToolV2, Default::default()),
|
||||||
|
zwp_tablet_seat_v2::EVT_PAD_ADDED_OPCODE => (ZwpTabletPadV2, Default::default())
|
||||||
|
]);
|
||||||
|
|
||||||
|
fn event(
|
||||||
|
_: &mut WinitState,
|
||||||
|
_: &ZwpTabletSeatV2,
|
||||||
|
_: <ZwpTabletSeatV2 as Proxy>::Event,
|
||||||
|
_: &(),
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<WinitState>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<ZwpTabletToolV2, TabletToolData, WinitState> for TabletManager {
|
||||||
|
fn event(
|
||||||
|
state: &mut WinitState,
|
||||||
|
_: &ZwpTabletToolV2,
|
||||||
|
event: <ZwpTabletToolV2 as Proxy>::Event,
|
||||||
|
data: &TabletToolData,
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<WinitState>,
|
||||||
|
) {
|
||||||
|
let mut data = data.inner.lock().unwrap();
|
||||||
|
|
||||||
|
match event {
|
||||||
|
ToolEvent::Type { tool_type: WEnum::Value(tool_type) } => {
|
||||||
|
data.ty = match tool_type {
|
||||||
|
ToolType::Pen => TabletToolKind::Pen,
|
||||||
|
ToolType::Eraser => TabletToolKind::Eraser,
|
||||||
|
ToolType::Brush => TabletToolKind::Brush,
|
||||||
|
ToolType::Pencil => TabletToolKind::Pencil,
|
||||||
|
ToolType::Airbrush => TabletToolKind::Airbrush,
|
||||||
|
ToolType::Finger => TabletToolKind::Finger,
|
||||||
|
ToolType::Mouse => TabletToolKind::Mouse,
|
||||||
|
ToolType::Lens => TabletToolKind::Lens,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
ToolEvent::Capability { .. } => {},
|
||||||
|
ToolEvent::Done => (),
|
||||||
|
ToolEvent::ProximityIn { serial, surface, .. } => {
|
||||||
|
data.pending.push(TabletEvent::Enter { serial, surface });
|
||||||
|
},
|
||||||
|
ToolEvent::ProximityOut => data.pending.push(TabletEvent::Left),
|
||||||
|
ToolEvent::Down { serial } => {
|
||||||
|
let event = TabletEvent::Button {
|
||||||
|
state: ElementState::Pressed,
|
||||||
|
serial: Some(serial),
|
||||||
|
button: TabletToolButton::Contact,
|
||||||
|
};
|
||||||
|
|
||||||
|
data.pending.push(event);
|
||||||
|
},
|
||||||
|
ToolEvent::Up => {
|
||||||
|
let event = TabletEvent::Button {
|
||||||
|
state: ElementState::Released,
|
||||||
|
serial: None,
|
||||||
|
button: TabletToolButton::Contact,
|
||||||
|
};
|
||||||
|
|
||||||
|
data.pending.push(event);
|
||||||
|
},
|
||||||
|
ToolEvent::Tilt { tilt_x, tilt_y } => {
|
||||||
|
data.tool_state.tilt = Some(TabletToolTilt { x: tilt_x as i8, y: tilt_y as i8 });
|
||||||
|
},
|
||||||
|
ToolEvent::Motion { x, y } => {
|
||||||
|
data.position = (x, y).into();
|
||||||
|
data.pending.push(TabletEvent::Moved);
|
||||||
|
},
|
||||||
|
ToolEvent::Pressure { pressure } => {
|
||||||
|
data.tool_state.force = Some(Force::Normalized(pressure as f64 / u16::MAX as f64));
|
||||||
|
},
|
||||||
|
ToolEvent::Rotation { degrees } => {
|
||||||
|
data.tool_state.twist = Some(degrees as u16);
|
||||||
|
},
|
||||||
|
ToolEvent::Button { serial, button, state: WEnum::Value(state) } => {
|
||||||
|
let state = match state {
|
||||||
|
ButtonState::Released => ElementState::Released,
|
||||||
|
ButtonState::Pressed => ElementState::Pressed,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map similar to SDL.
|
||||||
|
let button = match button {
|
||||||
|
// BTN_STYLUS.
|
||||||
|
0x14b => TabletToolButton::Contact,
|
||||||
|
0x14c => TabletToolButton::Barrel,
|
||||||
|
// BTN_STYLUS3.
|
||||||
|
0x149 => TabletToolButton::Other(1),
|
||||||
|
// There's no defined conversion for any of that.
|
||||||
|
button => TabletToolButton::Other(button as u16),
|
||||||
|
};
|
||||||
|
|
||||||
|
let event = TabletEvent::Button { button, state, serial: Some(serial) };
|
||||||
|
data.pending.push(event);
|
||||||
|
},
|
||||||
|
ToolEvent::Frame { .. } => {
|
||||||
|
let kind = data.ty;
|
||||||
|
for event in std::mem::take(&mut data.pending) {
|
||||||
|
if let TabletEvent::Enter { surface, serial } = &event {
|
||||||
|
data.latest_enter_serial = Some(*serial);
|
||||||
|
data.surface = Some(surface.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle events only for top-level surface.
|
||||||
|
let surface = match data
|
||||||
|
.surface
|
||||||
|
.as_ref()
|
||||||
|
.map(|surface| (surface, surface.data::<SurfaceData>()))
|
||||||
|
{
|
||||||
|
Some((surface, Some(surface_data)))
|
||||||
|
if surface_data.parent_surface().is_none() =>
|
||||||
|
{
|
||||||
|
surface
|
||||||
|
},
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let window_id = crate::make_wid(surface);
|
||||||
|
|
||||||
|
// Ensure that window exists.
|
||||||
|
let window = match state.windows.get_mut().get_mut(&window_id) {
|
||||||
|
Some(window) => window.lock().unwrap(),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let position = data.position.to_physical(window.scale_factor());
|
||||||
|
|
||||||
|
let window_event = match event {
|
||||||
|
TabletEvent::Enter { .. } => WindowEvent::PointerEntered {
|
||||||
|
device_id: None,
|
||||||
|
position,
|
||||||
|
primary: true,
|
||||||
|
kind: PointerKind::TabletTool(kind),
|
||||||
|
},
|
||||||
|
TabletEvent::Moved => WindowEvent::PointerMoved {
|
||||||
|
device_id: None,
|
||||||
|
position,
|
||||||
|
primary: true,
|
||||||
|
source: PointerSource::TabletTool {
|
||||||
|
kind,
|
||||||
|
data: data.tool_state.clone(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TabletEvent::Button { button, state, serial } => {
|
||||||
|
// Update serial if we have it.
|
||||||
|
if let Some(serial) = serial {
|
||||||
|
data.latest_button_serial = Some(serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowEvent::PointerButton {
|
||||||
|
device_id: None,
|
||||||
|
state,
|
||||||
|
position,
|
||||||
|
primary: true,
|
||||||
|
button: ButtonSource::TabletTool {
|
||||||
|
kind,
|
||||||
|
button,
|
||||||
|
data: data.tool_state.clone(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TabletEvent::Left => WindowEvent::PointerLeft {
|
||||||
|
device_id: None,
|
||||||
|
position: Some(position),
|
||||||
|
primary: true,
|
||||||
|
kind: PointerKind::TabletTool(kind),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
state.events_sink.push_window_event(window_event, window_id);
|
||||||
|
|
||||||
|
// Clear up the surface after we've processed `Left` event.
|
||||||
|
if matches!(event, TabletEvent::Left) {
|
||||||
|
data.surface = None;
|
||||||
|
data.latest_button_serial = None;
|
||||||
|
data.latest_enter_serial = None;
|
||||||
|
data.tool_state = Default::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct TabletToolData {
|
||||||
|
inner: Mutex<TabletToolDataInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct TabletToolDataInner {
|
||||||
|
pub(crate) ty: TabletToolKind,
|
||||||
|
|
||||||
|
/// Core tablet tool data.
|
||||||
|
pub(crate) tool_state: CoreTabletToolData,
|
||||||
|
|
||||||
|
/// Pending events until the `frame` is received.
|
||||||
|
pub(crate) pending: SmallVec<[TabletEvent; 4]>,
|
||||||
|
|
||||||
|
/// Surface the tablet most recently entered.
|
||||||
|
pub(crate) surface: Option<WlSurface>,
|
||||||
|
|
||||||
|
/// Position relative to the surface.
|
||||||
|
pub(crate) position: LogicalPosition<f64>,
|
||||||
|
|
||||||
|
// NOTE: even though we don't utilize serials
|
||||||
|
// right now, track them anyway.
|
||||||
|
/// The serial of the latest enter event for the pointer
|
||||||
|
pub(crate) latest_enter_serial: Option<u32>,
|
||||||
|
|
||||||
|
/// The serial of the latest button event for the pointer
|
||||||
|
pub(crate) latest_button_serial: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to wayland using logical coordinates,
|
||||||
|
// delay the conversion to physical until the dispatch actually happens,
|
||||||
|
// so the scaling is applied at the time of actual dispatch, since it
|
||||||
|
// can technically change before the `frame` event.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) enum TabletEvent {
|
||||||
|
Enter { serial: u32, surface: WlSurface },
|
||||||
|
Left,
|
||||||
|
Moved,
|
||||||
|
Button { button: TabletToolButton, state: ElementState, serial: Option<u32> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<ZwpTabletV2, (), WinitState> for TabletManager {
|
||||||
|
fn event(
|
||||||
|
_: &mut WinitState,
|
||||||
|
_: &ZwpTabletV2,
|
||||||
|
_: <ZwpTabletV2 as Proxy>::Event,
|
||||||
|
_: &(),
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<WinitState>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<ZwpTabletPadV2, (), WinitState> for TabletManager {
|
||||||
|
fn event(
|
||||||
|
_: &mut WinitState,
|
||||||
|
_: &ZwpTabletPadV2,
|
||||||
|
_: <ZwpTabletPadV2 as Proxy>::Event,
|
||||||
|
_: &(),
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<WinitState>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate_dispatch!(WinitState: [ZwpTabletManagerV2: GlobalData] => TabletManager);
|
||||||
|
delegate_dispatch!(WinitState: [ZwpTabletManagerV2: ()] => TabletManager);
|
||||||
|
delegate_dispatch!(WinitState: [ZwpTabletSeatV2: ()] => TabletManager);
|
||||||
|
delegate_dispatch!(WinitState: [ZwpTabletV2: ()] => TabletManager);
|
||||||
|
delegate_dispatch!(WinitState: [ZwpTabletToolV2: TabletToolData] => TabletManager);
|
||||||
|
delegate_dispatch!(WinitState: [ZwpTabletPadV2: ()] => TabletManager);
|
||||||
|
|
@ -216,7 +216,7 @@ impl CursorHandler {
|
||||||
Cursor::Custom(cursor) => {
|
Cursor::Custom(cursor) => {
|
||||||
let cursor = match cursor.cast_ref::<CustomCursor>() {
|
let cursor = match cursor.cast_ref::<CustomCursor>() {
|
||||||
Some(cursor) => cursor,
|
Some(cursor) => cursor,
|
||||||
None => todo!(),
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let SelectedCursor::Loading { cursor: old_cursor, .. }
|
if let SelectedCursor::Loading { cursor: old_cursor, .. }
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ use crate::event_loop::ActiveEventLoop;
|
||||||
use crate::main_thread::MainThreadMarker;
|
use crate::main_thread::MainThreadMarker;
|
||||||
use crate::monitor::MonitorHandler;
|
use crate::monitor::MonitorHandler;
|
||||||
use crate::r#async::DispatchRunner;
|
use crate::r#async::DispatchRunner;
|
||||||
use crate::web_sys::event::mouse_button_to_id;
|
use crate::web_sys::event::ButtonsState;
|
||||||
use crate::window::Inner;
|
use crate::window::Inner;
|
||||||
use crate::{backend, event, EventLoop, PollStrategy, WaitUntilStrategy};
|
use crate::{backend, event, EventLoop, PollStrategy, WaitUntilStrategy};
|
||||||
|
|
||||||
|
|
@ -318,8 +318,10 @@ impl Shared {
|
||||||
// chorded button event
|
// chorded button event
|
||||||
let device_id = event::mkdid(event.pointer_id());
|
let device_id = event::mkdid(event.pointer_id());
|
||||||
|
|
||||||
if let Some(button) = backend::event::mouse_button(&event) {
|
if let Some(button) = backend::event::raw_button(&event) {
|
||||||
let state = if backend::event::mouse_buttons(&event).contains(button.into()) {
|
let state = if backend::event::pointer_buttons(&event)
|
||||||
|
.contains(ButtonsState::from_bits_retain(button))
|
||||||
|
{
|
||||||
ElementState::Pressed
|
ElementState::Pressed
|
||||||
} else {
|
} else {
|
||||||
ElementState::Released
|
ElementState::Released
|
||||||
|
|
@ -327,10 +329,7 @@ impl Shared {
|
||||||
|
|
||||||
runner.send_event(Event::DeviceEvent {
|
runner.send_event(Event::DeviceEvent {
|
||||||
device_id,
|
device_id,
|
||||||
event: DeviceEvent::Button {
|
event: DeviceEvent::Button { button: button.into(), state },
|
||||||
button: mouse_button_to_id(button).into(),
|
|
||||||
state,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -338,14 +337,16 @@ impl Shared {
|
||||||
|
|
||||||
// pointer move event
|
// pointer move event
|
||||||
let mut delta = backend::event::MouseDelta::init(&navigator, &event);
|
let mut delta = backend::event::MouseDelta::init(&navigator, &event);
|
||||||
runner.send_events(backend::event::pointer_move_event(event).map(|event| {
|
runner.send_events(backend::event::pointer_move_event(event).map(
|
||||||
|
|event: web_sys::PointerEvent| {
|
||||||
let delta = delta.delta(&event).to_physical(backend::scale_factor(&window));
|
let delta = delta.delta(&event).to_physical(backend::scale_factor(&window));
|
||||||
|
|
||||||
Event::DeviceEvent {
|
Event::DeviceEvent {
|
||||||
device_id,
|
device_id,
|
||||||
event: DeviceEvent::PointerMotion { delta: (delta.x, delta.y) },
|
event: DeviceEvent::PointerMotion { delta: (delta.x, delta.y) },
|
||||||
}
|
}
|
||||||
}));
|
},
|
||||||
|
));
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
let runner = self.clone();
|
let runner = self.clone();
|
||||||
|
|
@ -375,11 +376,11 @@ impl Shared {
|
||||||
return;
|
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 {
|
runner.send_event(Event::DeviceEvent {
|
||||||
device_id: event::mkdid(event.pointer_id()),
|
device_id: event::mkdid(event.pointer_id()),
|
||||||
event: DeviceEvent::Button {
|
event: DeviceEvent::Button {
|
||||||
button: mouse_button_to_id(button).into(),
|
button: button.into(),
|
||||||
state: ElementState::Pressed,
|
state: ElementState::Pressed,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -394,11 +395,11 @@ impl Shared {
|
||||||
return;
|
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 {
|
runner.send_event(Event::DeviceEvent {
|
||||||
device_id: event::mkdid(event.pointer_id()),
|
device_id: event::mkdid(event.pointer_id()),
|
||||||
event: DeviceEvent::Button {
|
event: DeviceEvent::Button {
|
||||||
button: mouse_button_to_id(button).into(),
|
button: button.into(),
|
||||||
state: ElementState::Released,
|
state: ElementState::Released,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
|
use std::f64;
|
||||||
|
|
||||||
use dpi::{LogicalPosition, PhysicalPosition, Position};
|
use dpi::{LogicalPosition, PhysicalPosition, Position};
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
|
use tracing::warn;
|
||||||
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, Navigator, PointerEvent, WheelEvent};
|
use web_sys::{Event, KeyboardEvent, MouseEvent, Navigator, PointerEvent, WheelEvent};
|
||||||
use winit_core::event::{FingerId, MouseButton, MouseScrollDelta, PointerKind};
|
use winit_core::event::{
|
||||||
|
ButtonSource, FingerId, Force, MouseButton, MouseScrollDelta, PointerKind, PointerSource,
|
||||||
|
TabletToolAngle, TabletToolButton, TabletToolData, TabletToolKind, TabletToolTilt,
|
||||||
|
};
|
||||||
use winit_core::keyboard::{
|
use winit_core::keyboard::{
|
||||||
Key, KeyCode, KeyLocation, ModifiersState, NamedKey, NativeKey, NativeKeyCode, PhysicalKey,
|
Key, KeyCode, KeyLocation, ModifiersState, NamedKey, NativeKey, NativeKeyCode, PhysicalKey,
|
||||||
};
|
};
|
||||||
|
|
@ -16,11 +21,12 @@ 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)]
|
||||||
pub struct ButtonsState: u16 {
|
pub struct ButtonsState: u16 {
|
||||||
const LEFT = 0b00001;
|
const LEFT = 0b000001;
|
||||||
const RIGHT = 0b00010;
|
const RIGHT = 0b000010;
|
||||||
const MIDDLE = 0b00100;
|
const MIDDLE = 0b000100;
|
||||||
const BACK = 0b01000;
|
const BACK = 0b001000;
|
||||||
const FORWARD = 0b10000;
|
const FORWARD = 0b010000;
|
||||||
|
const ERASER = 0b100000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,6 +43,15 @@ impl From<ButtonsState> for MouseButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ButtonSource> for ButtonsState {
|
||||||
|
fn from(value: ButtonSource) -> Self {
|
||||||
|
match value {
|
||||||
|
ButtonSource::TabletTool { button, .. } => button.into(),
|
||||||
|
other => ButtonsState::from(other.mouse_button()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<MouseButton> for ButtonsState {
|
impl From<MouseButton> for ButtonsState {
|
||||||
fn from(value: MouseButton) -> Self {
|
fn from(value: MouseButton) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
|
@ -50,37 +65,133 @@ impl From<MouseButton> for ButtonsState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mouse_buttons(event: &MouseEvent) -> ButtonsState {
|
impl From<TabletToolButton> for ButtonsState {
|
||||||
|
fn from(tool: TabletToolButton) -> Self {
|
||||||
|
match tool {
|
||||||
|
TabletToolButton::Contact => ButtonsState::LEFT,
|
||||||
|
TabletToolButton::Barrel => ButtonsState::RIGHT,
|
||||||
|
TabletToolButton::Other(value) => Self::from_bits_retain(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pointer_buttons(event: &MouseEvent) -> ButtonsState {
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
ButtonsState::from_bits_retain(event.buttons())
|
ButtonsState::from_bits_retain(event.buttons())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mouse_button(event: &MouseEvent) -> Option<MouseButton> {
|
pub fn raw_button(event: &MouseEvent) -> Option<u16> {
|
||||||
// https://www.w3.org/TR/pointerevents3/#the-button-property
|
// https://www.w3.org/TR/pointerevents3/#the-button-property
|
||||||
match event.button() {
|
#[allow(clippy::disallowed_methods)]
|
||||||
-1 => None,
|
let button = event.button();
|
||||||
0 => Some(MouseButton::Left),
|
|
||||||
1 => Some(MouseButton::Middle),
|
if button == -1 {
|
||||||
2 => Some(MouseButton::Right),
|
None
|
||||||
3 => Some(MouseButton::Back),
|
} else {
|
||||||
4 => Some(MouseButton::Forward),
|
Some(button.try_into().expect("unexpected negative mouse button value"))
|
||||||
i => {
|
}
|
||||||
Some(MouseButton::Other(i.try_into().expect("unexpected negative mouse button value")))
|
}
|
||||||
|
|
||||||
|
pub fn mouse_button(button: u16) -> MouseButton {
|
||||||
|
match button {
|
||||||
|
0 => MouseButton::Left,
|
||||||
|
1 => MouseButton::Middle,
|
||||||
|
2 => MouseButton::Right,
|
||||||
|
3 => MouseButton::Back,
|
||||||
|
4 => MouseButton::Forward,
|
||||||
|
other => MouseButton::Other(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tool_button(button: u16) -> TabletToolButton {
|
||||||
|
match button {
|
||||||
|
0 => TabletToolButton::Contact,
|
||||||
|
2 => TabletToolButton::Barrel,
|
||||||
|
other => TabletToolButton::Other(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum WebPointerType {
|
||||||
|
Mouse,
|
||||||
|
Touch,
|
||||||
|
Pen,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebPointerType {
|
||||||
|
pub fn from_event(event: &PointerEvent) -> Option<Self> {
|
||||||
|
#[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 type: {type}");
|
||||||
|
None
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mouse_button_to_id(button: MouseButton) -> u16 {
|
pub fn pointer_kind(event: &PointerEvent, pointer_id: i32) -> PointerKind {
|
||||||
match button {
|
match WebPointerType::from_event(event) {
|
||||||
MouseButton::Left => 0,
|
Some(WebPointerType::Mouse) => PointerKind::Mouse,
|
||||||
MouseButton::Right => 1,
|
Some(WebPointerType::Touch) => PointerKind::Touch(FingerId::from_raw(pointer_id as usize)),
|
||||||
MouseButton::Middle => 2,
|
Some(WebPointerType::Pen) => {
|
||||||
MouseButton::Back => 3,
|
PointerKind::TabletTool(if pointer_buttons(event).contains(ButtonsState::ERASER) {
|
||||||
MouseButton::Forward => 4,
|
TabletToolKind::Eraser
|
||||||
MouseButton::Other(value) => value,
|
} else {
|
||||||
|
TabletToolKind::Pen
|
||||||
|
})
|
||||||
|
},
|
||||||
|
None => PointerKind::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
|
pub fn pointer_source(event: &PointerEvent, kind: PointerKind) -> PointerSource {
|
||||||
|
#[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<f64>;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter, js_name = azimuthAngle)]
|
||||||
|
pub fn azimuth_angle(this: &PointerEventExt) -> f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
let event: &PointerEventExt = event.unchecked_ref();
|
||||||
|
|
||||||
|
match kind {
|
||||||
|
PointerKind::Mouse => PointerSource::Mouse,
|
||||||
|
PointerKind::Touch(id) => PointerSource::Touch {
|
||||||
|
finger_id: id,
|
||||||
|
force: Some(Force::Normalized(event.pressure().into())),
|
||||||
|
},
|
||||||
|
PointerKind::TabletTool(tool) => {
|
||||||
|
let data = TabletToolData {
|
||||||
|
force: Some(Force::Normalized(event.pressure().into())),
|
||||||
|
tangential_force: Some(event.tangential_pressure()),
|
||||||
|
twist: Some(event.twist().try_into().expect("found invalid `twist`")),
|
||||||
|
tilt: Some(TabletToolTilt {
|
||||||
|
x: event.tilt_x().try_into().expect("found invalid `tiltX`"),
|
||||||
|
y: event.tilt_y().try_into().expect("found invalid `tiltY`"),
|
||||||
|
}),
|
||||||
|
angle: event
|
||||||
|
.altitude_angle()
|
||||||
|
.map(|altitude| TabletToolAngle { altitude, azimuth: event.azimuth_angle() }),
|
||||||
|
};
|
||||||
|
|
||||||
|
PointerSource::TabletTool { kind: tool, data }
|
||||||
|
},
|
||||||
|
PointerKind::Unknown => PointerSource::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pointer_position(event: &MouseEvent) -> LogicalPosition<f64> {
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
type MouseEventExt;
|
type MouseEventExt;
|
||||||
|
|
@ -113,7 +224,7 @@ impl MouseDelta {
|
||||||
Some(Engine::Chromium) => Self::Chromium,
|
Some(Engine::Chromium) => Self::Chromium,
|
||||||
// Firefox has wrong movement values in coalesced events.
|
// Firefox has wrong movement values in coalesced events.
|
||||||
Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko {
|
Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko {
|
||||||
old_position: mouse_position(event),
|
old_position: pointer_position(event),
|
||||||
old_delta: LogicalPosition::new(
|
old_delta: LogicalPosition::new(
|
||||||
event.movement_x() as f64,
|
event.movement_x() as f64,
|
||||||
event.movement_y() as f64,
|
event.movement_y() as f64,
|
||||||
|
|
@ -129,7 +240,7 @@ impl MouseDelta {
|
||||||
PhysicalPosition::new(event.movement_x(), event.movement_y()).into()
|
PhysicalPosition::new(event.movement_x(), event.movement_y()).into()
|
||||||
},
|
},
|
||||||
MouseDelta::Gecko { old_position, old_delta } => {
|
MouseDelta::Gecko { old_position, old_delta } => {
|
||||||
let new_position = mouse_position(event);
|
let new_position = pointer_position(event);
|
||||||
let x = new_position.x - old_position.x + old_delta.x;
|
let x = new_position.x - old_position.x + old_delta.x;
|
||||||
let y = new_position.y - old_position.y + old_delta.y;
|
let y = new_position.y - old_position.y + old_delta.y;
|
||||||
*old_position = new_position;
|
*old_position = new_position;
|
||||||
|
|
@ -160,14 +271,6 @@ pub fn mouse_scroll_delta(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pointer_type(event: &PointerEvent, pointer_id: i32) -> PointerKind {
|
|
||||||
match event.pointer_type().as_str() {
|
|
||||||
"mouse" => PointerKind::Mouse,
|
|
||||||
"touch" => PointerKind::Touch(FingerId::from_raw(pointer_id as usize)),
|
|
||||||
_ => PointerKind::Unknown,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn key_code(event: &KeyboardEvent) -> PhysicalKey {
|
pub fn key_code(event: &KeyboardEvent) -> PhysicalKey {
|
||||||
// Use keyboard-types' parsing (it is based on the W3C standard).
|
// Use keyboard-types' parsing (it is based on the W3C standard).
|
||||||
match event.code().parse() {
|
match event.code().parse() {
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use dpi::PhysicalPosition;
|
use dpi::PhysicalPosition;
|
||||||
use web_sys::PointerEvent;
|
use web_sys::PointerEvent;
|
||||||
use winit_core::event::{ButtonSource, DeviceId, ElementState, Force, PointerKind, PointerSource};
|
use winit_core::event::{ButtonSource, DeviceId, ElementState, PointerKind, PointerSource};
|
||||||
use winit_core::keyboard::ModifiersState;
|
use winit_core::keyboard::ModifiersState;
|
||||||
|
|
||||||
use super::canvas::Common;
|
use super::canvas::Common;
|
||||||
use super::event;
|
use super::event;
|
||||||
use super::event_handle::EventListenerHandle;
|
use super::event_handle::EventListenerHandle;
|
||||||
use crate::event::mkdid;
|
use crate::event::mkdid;
|
||||||
use crate::web_sys::event::mouse_button_to_id;
|
use crate::web_sys::event::ButtonsState;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(super) struct PointerHandler {
|
pub(super) struct PointerHandler {
|
||||||
|
|
@ -46,8 +46,8 @@ impl PointerHandler {
|
||||||
let pointer_id = event.pointer_id();
|
let pointer_id = event.pointer_id();
|
||||||
let device_id = mkdid(pointer_id);
|
let device_id = mkdid(pointer_id);
|
||||||
let position =
|
let position =
|
||||||
event::mouse_position(&event).to_physical(super::scale_factor(&window));
|
event::pointer_position(&event).to_physical(super::scale_factor(&window));
|
||||||
let kind = event::pointer_type(&event, pointer_id);
|
let kind = event::pointer_kind(&event, pointer_id);
|
||||||
handler(modifiers, device_id, event.is_primary(), position, kind);
|
handler(modifiers, device_id, event.is_primary(), position, kind);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -64,8 +64,8 @@ impl PointerHandler {
|
||||||
let pointer_id = event.pointer_id();
|
let pointer_id = event.pointer_id();
|
||||||
let device_id = mkdid(pointer_id);
|
let device_id = mkdid(pointer_id);
|
||||||
let position =
|
let position =
|
||||||
event::mouse_position(&event).to_physical(super::scale_factor(&window));
|
event::pointer_position(&event).to_physical(super::scale_factor(&window));
|
||||||
let kind = event::pointer_type(&event, pointer_id);
|
let kind = event::pointer_kind(&event, pointer_id);
|
||||||
handler(modifiers, device_id, event.is_primary(), position, kind);
|
handler(modifiers, device_id, event.is_primary(), position, kind);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -80,24 +80,25 @@ impl PointerHandler {
|
||||||
Some(canvas_common.add_event("pointerup", move |event: PointerEvent| {
|
Some(canvas_common.add_event("pointerup", move |event: PointerEvent| {
|
||||||
let modifiers = event::mouse_modifiers(&event);
|
let modifiers = event::mouse_modifiers(&event);
|
||||||
let pointer_id = event.pointer_id();
|
let pointer_id = event.pointer_id();
|
||||||
let kind = event::pointer_type(&event, pointer_id);
|
let kind = event::pointer_kind(&event, pointer_id);
|
||||||
|
let button = event::raw_button(&event).expect("no button pressed");
|
||||||
|
|
||||||
let button = event::mouse_button(&event).expect("no mouse button pressed");
|
let source = match event::pointer_source(&event, kind) {
|
||||||
|
PointerSource::Mouse => ButtonSource::Mouse(event::mouse_button(button)),
|
||||||
let source = match kind {
|
PointerSource::Touch { finger_id, force } => {
|
||||||
PointerKind::Mouse => ButtonSource::Mouse(button),
|
ButtonSource::Touch { finger_id, force }
|
||||||
PointerKind::Touch(finger_id) => ButtonSource::Touch {
|
|
||||||
finger_id,
|
|
||||||
force: Some(Force::Normalized(event.pressure().into())),
|
|
||||||
},
|
},
|
||||||
PointerKind::Unknown => ButtonSource::Unknown(mouse_button_to_id(button)),
|
PointerSource::TabletTool { kind, data } => {
|
||||||
|
ButtonSource::TabletTool { kind, button: event::tool_button(button), data }
|
||||||
|
},
|
||||||
|
PointerSource::Unknown => ButtonSource::Unknown(button),
|
||||||
};
|
};
|
||||||
|
|
||||||
handler(
|
handler(
|
||||||
modifiers,
|
modifiers,
|
||||||
mkdid(pointer_id),
|
mkdid(pointer_id),
|
||||||
event.is_primary(),
|
event.is_primary(),
|
||||||
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
|
event::pointer_position(&event).to_physical(super::scale_factor(&window)),
|
||||||
source,
|
source,
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
|
@ -125,11 +126,11 @@ impl PointerHandler {
|
||||||
|
|
||||||
let modifiers = event::mouse_modifiers(&event);
|
let modifiers = event::mouse_modifiers(&event);
|
||||||
let pointer_id = event.pointer_id();
|
let pointer_id = event.pointer_id();
|
||||||
let kind = event::pointer_type(&event, pointer_id);
|
let kind = event::pointer_kind(&event, pointer_id);
|
||||||
let button = event::mouse_button(&event).expect("no mouse button pressed");
|
let button = event::raw_button(&event).expect("no button pressed");
|
||||||
|
|
||||||
let source = match kind {
|
let source = match event::pointer_source(&event, kind) {
|
||||||
PointerKind::Mouse => {
|
PointerSource::Mouse => {
|
||||||
// Error is swallowed here since the error would occur every time the
|
// Error is swallowed here since the error would occur every time the
|
||||||
// mouse is clicked when the cursor is
|
// mouse is clicked when the cursor is
|
||||||
// grabbed, and there is probably not a
|
// grabbed, and there is probably not a
|
||||||
|
|
@ -137,20 +138,29 @@ impl PointerHandler {
|
||||||
// care if it fails.
|
// care if it fails.
|
||||||
let _e = canvas.set_pointer_capture(pointer_id);
|
let _e = canvas.set_pointer_capture(pointer_id);
|
||||||
|
|
||||||
ButtonSource::Mouse(button)
|
ButtonSource::Mouse(event::mouse_button(button))
|
||||||
},
|
},
|
||||||
PointerKind::Touch(finger_id) => ButtonSource::Touch {
|
PointerSource::Touch { finger_id, force } => {
|
||||||
finger_id,
|
ButtonSource::Touch { finger_id, force }
|
||||||
force: Some(Force::Normalized(event.pressure().into())),
|
|
||||||
},
|
},
|
||||||
PointerKind::Unknown => ButtonSource::Unknown(mouse_button_to_id(button)),
|
PointerSource::TabletTool { kind, data } => {
|
||||||
|
// 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(pointer_id);
|
||||||
|
|
||||||
|
ButtonSource::TabletTool { kind, button: event::tool_button(button), data }
|
||||||
|
},
|
||||||
|
PointerSource::Unknown => ButtonSource::Unknown(button),
|
||||||
};
|
};
|
||||||
|
|
||||||
handler(
|
handler(
|
||||||
modifiers,
|
modifiers,
|
||||||
mkdid(pointer_id),
|
mkdid(pointer_id),
|
||||||
event.is_primary(),
|
event.is_primary(),
|
||||||
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
|
event::pointer_position(&event).to_physical(super::scale_factor(&window)),
|
||||||
source,
|
source,
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
|
@ -186,11 +196,11 @@ impl PointerHandler {
|
||||||
Some(canvas_common.add_event("pointermove", move |event: PointerEvent| {
|
Some(canvas_common.add_event("pointermove", move |event: PointerEvent| {
|
||||||
let pointer_id = event.pointer_id();
|
let pointer_id = event.pointer_id();
|
||||||
let device_id = mkdid(pointer_id);
|
let device_id = mkdid(pointer_id);
|
||||||
let kind = event::pointer_type(&event, pointer_id);
|
let kind = event::pointer_kind(&event, pointer_id);
|
||||||
let primary = event.is_primary();
|
let primary = event.is_primary();
|
||||||
|
|
||||||
// chorded button event
|
// chorded button event
|
||||||
if let Some(button) = event::mouse_button(&event) {
|
if let Some(button) = event::raw_button(&event) {
|
||||||
if prevent_default.get() {
|
if prevent_default.get() {
|
||||||
// prevent text selection
|
// prevent text selection
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
|
|
@ -198,34 +208,36 @@ impl PointerHandler {
|
||||||
let _ = canvas.focus();
|
let _ = canvas.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = if event::mouse_buttons(&event).contains(button.into()) {
|
let state = if event::pointer_buttons(&event)
|
||||||
|
.contains(ButtonsState::from_bits_retain(button))
|
||||||
|
{
|
||||||
ElementState::Pressed
|
ElementState::Pressed
|
||||||
} else {
|
} else {
|
||||||
ElementState::Released
|
ElementState::Released
|
||||||
};
|
};
|
||||||
|
|
||||||
let button = match kind {
|
let button = match event::pointer_source(&event, kind) {
|
||||||
PointerKind::Mouse => ButtonSource::Mouse(button),
|
PointerSource::Mouse => ButtonSource::Mouse(event::mouse_button(button)),
|
||||||
PointerKind::Touch(finger_id) => {
|
PointerSource::Touch { finger_id, force } => {
|
||||||
let button_id = mouse_button_to_id(button);
|
if button != 0 {
|
||||||
|
tracing::error!("unexpected touch button id: {button}");
|
||||||
if button_id != 1 {
|
|
||||||
tracing::error!("unexpected touch button id: {button_id}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonSource::Touch {
|
ButtonSource::Touch { finger_id, force }
|
||||||
finger_id,
|
|
||||||
force: Some(Force::Normalized(event.pressure().into())),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
PointerKind::Unknown => todo!(),
|
PointerSource::TabletTool { kind, data } => ButtonSource::TabletTool {
|
||||||
|
kind,
|
||||||
|
button: event::tool_button(button),
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
PointerSource::Unknown => ButtonSource::Unknown(button),
|
||||||
};
|
};
|
||||||
|
|
||||||
button_handler(
|
button_handler(
|
||||||
event::mouse_modifiers(&event),
|
event::mouse_modifiers(&event),
|
||||||
device_id,
|
device_id,
|
||||||
primary,
|
primary,
|
||||||
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
|
event::pointer_position(&event).to_physical(super::scale_factor(&window)),
|
||||||
state,
|
state,
|
||||||
button,
|
button,
|
||||||
);
|
);
|
||||||
|
|
@ -242,15 +254,8 @@ impl PointerHandler {
|
||||||
(
|
(
|
||||||
event::mouse_modifiers(&event),
|
event::mouse_modifiers(&event),
|
||||||
event.is_primary(),
|
event.is_primary(),
|
||||||
event::mouse_position(&event).to_physical(scale),
|
event::pointer_position(&event).to_physical(scale),
|
||||||
match kind {
|
event::pointer_source(&event, kind),
|
||||||
PointerKind::Mouse => PointerSource::Mouse,
|
|
||||||
PointerKind::Touch(finger_id) => PointerSource::Touch {
|
|
||||||
finger_id,
|
|
||||||
force: Some(Force::Normalized(event.pressure().into())),
|
|
||||||
},
|
|
||||||
PointerKind::Unknown => PointerSource::Unknown,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -44,28 +44,29 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||||
GetMenu, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW,
|
GetMenu, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW,
|
||||||
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, SystemParametersInfoW,
|
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, SystemParametersInfoW,
|
||||||
TranslateMessage, CREATESTRUCTW, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO,
|
TranslateMessage, CREATESTRUCTW, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO,
|
||||||
MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_REMOVE, PT_TOUCH, QS_ALLINPUT,
|
MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PEN_FLAG_BARREL, PEN_FLAG_ERASER,
|
||||||
RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
|
PEN_MASK_PRESSURE, PEN_MASK_ROTATION, PEN_MASK_TILT_X, PEN_MASK_TILT_Y, PM_REMOVE, PT_PEN,
|
||||||
SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE,
|
PT_TOUCH, QS_ALLINPUT, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE,
|
||||||
SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT,
|
SIZE_MAXIMIZED, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SWP_NOACTIVATE, SWP_NOMOVE,
|
||||||
WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT, WM_CAPTURECHANGED, WM_CLOSE,
|
SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT,
|
||||||
WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO,
|
WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT,
|
||||||
WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION,
|
WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE,
|
||||||
WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
|
WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION,
|
||||||
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
|
WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS,
|
||||||
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
|
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL,
|
||||||
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS,
|
WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
|
||||||
WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH,
|
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN,
|
||||||
WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW,
|
WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND,
|
||||||
WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP,
|
WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING,
|
||||||
WS_VISIBLE,
|
WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW,
|
||||||
|
WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
|
||||||
};
|
};
|
||||||
use winit_core::application::ApplicationHandler;
|
use winit_core::application::ApplicationHandler;
|
||||||
use winit_core::cursor::{CustomCursor, CustomCursorSource};
|
use winit_core::cursor::{CustomCursor, CustomCursorSource};
|
||||||
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
|
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
|
||||||
use winit_core::event::{
|
use winit_core::event::{
|
||||||
DeviceEvent, DeviceId, FingerId, Force, Ime, RawKeyEvent, SurfaceSizeWriter, TouchPhase,
|
DeviceEvent, DeviceId, FingerId, Force, Ime, RawKeyEvent, SurfaceSizeWriter, TabletToolButton,
|
||||||
WindowEvent,
|
TabletToolData, TabletToolKind, TabletToolTilt, TouchPhase, WindowEvent,
|
||||||
};
|
};
|
||||||
use winit_core::event_loop::pump_events::PumpStatus;
|
use winit_core::event_loop::pump_events::PumpStatus;
|
||||||
use winit_core::event_loop::{
|
use winit_core::event_loop::{
|
||||||
|
|
@ -106,6 +107,7 @@ pub(crate) struct WindowData {
|
||||||
pub key_event_builder: KeyEventBuilder,
|
pub key_event_builder: KeyEventBuilder,
|
||||||
pub _file_drop_handler: Option<FileDropHandler>,
|
pub _file_drop_handler: Option<FileDropHandler>,
|
||||||
pub userdata_removed: Cell<bool>,
|
pub userdata_removed: Cell<bool>,
|
||||||
|
pub last_tablet_down_button_state: Cell<u32>,
|
||||||
pub recurse_depth: Cell<u32>,
|
pub recurse_depth: Cell<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2085,22 +2087,6 @@ unsafe fn public_window_callback_inner(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let force = if let PT_TOUCH = pointer_info.pointerType {
|
|
||||||
let mut touch_info = mem::MaybeUninit::uninit();
|
|
||||||
util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
|
||||||
match unsafe {
|
|
||||||
GetPointerTouchInfo(pointer_info.pointerId, touch_info.as_mut_ptr())
|
|
||||||
} {
|
|
||||||
0 => None,
|
|
||||||
_ => normalize_pointer_pressure(unsafe {
|
|
||||||
touch_info.assume_init().pressure
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let x = location.x as f64 + x.fract();
|
let x = location.x as f64 + x.fract();
|
||||||
let y = location.y as f64 + y.fract();
|
let y = location.y as f64 + y.fract();
|
||||||
let position = PhysicalPosition::new(x, y);
|
let position = PhysicalPosition::new(x, y);
|
||||||
|
|
@ -2108,60 +2094,83 @@ unsafe fn public_window_callback_inner(
|
||||||
let finger_id = FingerId::from_raw(pointer_info.pointerId as usize);
|
let finger_id = FingerId::from_raw(pointer_info.pointerId as usize);
|
||||||
let primary = util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_PRIMARY);
|
let primary = util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_PRIMARY);
|
||||||
|
|
||||||
if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_DOWN) {
|
let is_down = util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_DOWN);
|
||||||
|
if is_down || util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UP) {
|
||||||
|
let (kind, button) = match pointer_info.pointerType {
|
||||||
|
PT_TOUCH => (PointerKind::Touch(finger_id), ButtonSource::Touch {
|
||||||
|
finger_id,
|
||||||
|
force: force_for_touch(pointer_info.pointerId),
|
||||||
|
}),
|
||||||
|
PT_PEN => {
|
||||||
|
let kind = PointerKind::TabletTool(TabletToolKind::Pen);
|
||||||
|
let (mut pen_flags, data) =
|
||||||
|
tablet_tool_info_for_pen(pointer_info.pointerId);
|
||||||
|
let old_pen_flags =
|
||||||
|
userdata.last_tablet_down_button_state.replace(pen_flags);
|
||||||
|
// For release, use a diff.
|
||||||
|
if !is_down {
|
||||||
|
pen_flags ^= old_pen_flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
let button = ButtonSource::TabletTool {
|
||||||
|
kind: TabletToolKind::Pen,
|
||||||
|
button: pen_flags_to_button(pen_flags),
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
|
(kind, button)
|
||||||
|
},
|
||||||
|
_ => (PointerKind::Unknown, ButtonSource::Unknown(0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_down {
|
||||||
userdata.send_window_event(window, WindowEvent::PointerEntered {
|
userdata.send_window_event(window, WindowEvent::PointerEntered {
|
||||||
device_id: None,
|
device_id: None,
|
||||||
primary,
|
primary,
|
||||||
position,
|
position,
|
||||||
kind: if let PT_TOUCH = pointer_info.pointerType {
|
kind,
|
||||||
PointerKind::Touch(finger_id)
|
|
||||||
} else {
|
|
||||||
PointerKind::Unknown
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
userdata.send_window_event(window, WindowEvent::PointerButton {
|
userdata.send_window_event(window, WindowEvent::PointerButton {
|
||||||
device_id: None,
|
device_id: None,
|
||||||
primary,
|
primary,
|
||||||
state: Pressed,
|
state: Pressed,
|
||||||
position,
|
position,
|
||||||
button: if let PT_TOUCH = pointer_info.pointerType {
|
button,
|
||||||
ButtonSource::Touch { finger_id, force }
|
|
||||||
} else {
|
|
||||||
ButtonSource::Unknown(0)
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UP) {
|
} else {
|
||||||
userdata.send_window_event(window, WindowEvent::PointerButton {
|
userdata.send_window_event(window, WindowEvent::PointerButton {
|
||||||
device_id: None,
|
device_id: None,
|
||||||
primary,
|
primary,
|
||||||
state: Released,
|
state: Released,
|
||||||
position,
|
position,
|
||||||
button: if let PT_TOUCH = pointer_info.pointerType {
|
button,
|
||||||
ButtonSource::Touch { finger_id, force }
|
|
||||||
} else {
|
|
||||||
ButtonSource::Unknown(0)
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
userdata.send_window_event(window, WindowEvent::PointerLeft {
|
userdata.send_window_event(window, WindowEvent::PointerLeft {
|
||||||
device_id: None,
|
device_id: None,
|
||||||
primary,
|
primary,
|
||||||
position: Some(position),
|
position: Some(position),
|
||||||
kind: if let PT_TOUCH = pointer_info.pointerType {
|
kind,
|
||||||
PointerKind::Touch(finger_id)
|
|
||||||
} else {
|
|
||||||
PointerKind::Unknown
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UPDATE) {
|
} else if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UPDATE) {
|
||||||
|
let source = match pointer_info.pointerType {
|
||||||
|
PT_TOUCH => PointerSource::Touch {
|
||||||
|
finger_id,
|
||||||
|
force: force_for_touch(pointer_info.pointerId),
|
||||||
|
},
|
||||||
|
PT_PEN => PointerSource::TabletTool {
|
||||||
|
kind: TabletToolKind::Pen,
|
||||||
|
data: tablet_tool_info_for_pen(pointer_info.pointerId).1,
|
||||||
|
},
|
||||||
|
_ => PointerSource::Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
userdata.send_window_event(window, WindowEvent::PointerMoved {
|
userdata.send_window_event(window, WindowEvent::PointerMoved {
|
||||||
device_id: None,
|
device_id: None,
|
||||||
primary,
|
primary,
|
||||||
position,
|
position,
|
||||||
source: if let PT_TOUCH = pointer_info.pointerType {
|
source,
|
||||||
PointerSource::Touch { finger_id, force }
|
|
||||||
} else {
|
|
||||||
PointerSource::Unknown
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -2691,3 +2700,57 @@ fn apply_win10_dpi_adjustment(
|
||||||
|
|
||||||
conservative_rect
|
conservative_rect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn force_for_touch(pointer_id: u32) -> Option<Force> {
|
||||||
|
let mut touch_info = mem::MaybeUninit::uninit();
|
||||||
|
util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| {
|
||||||
|
match unsafe { GetPointerTouchInfo(pointer_id, touch_info.as_mut_ptr()) } {
|
||||||
|
0 => None,
|
||||||
|
_ => normalize_pointer_pressure(unsafe { touch_info.assume_init().pressure }),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Information is stored on the same thing, so we don't save anything by
|
||||||
|
// splitting the functions.
|
||||||
|
fn tablet_tool_info_for_pen(pointer_id: u32) -> (u32, TabletToolData) {
|
||||||
|
let mut tool_data = TabletToolData::default();
|
||||||
|
let mut tool_button = 0;
|
||||||
|
|
||||||
|
let mut tablet_info = mem::MaybeUninit::uninit();
|
||||||
|
util::GET_POINTER_PEN_INFO.map(|GetPointerPenInfo| {
|
||||||
|
if unsafe { GetPointerPenInfo(pointer_id, tablet_info.as_mut_ptr()) } == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pen_info = unsafe { tablet_info.assume_init() };
|
||||||
|
if pen_info.penMask & PEN_MASK_PRESSURE != 0 {
|
||||||
|
tool_data.force = normalize_pointer_pressure(pen_info.pressure);
|
||||||
|
}
|
||||||
|
if pen_info.penMask & (PEN_MASK_TILT_X | PEN_MASK_TILT_Y) != 0 {
|
||||||
|
tool_data.tilt =
|
||||||
|
Some(TabletToolTilt { x: pen_info.tiltX as i8, y: pen_info.tiltY as i8 });
|
||||||
|
}
|
||||||
|
if pen_info.penMask & PEN_MASK_ROTATION != 0 {
|
||||||
|
tool_data.twist = Some(pen_info.rotation as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_button = pen_info.penFlags;
|
||||||
|
});
|
||||||
|
|
||||||
|
(tool_button, tool_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: According to firefox, the buttons while can be combined, in
|
||||||
|
// reality they are not.
|
||||||
|
fn pen_flags_to_button(flags: u32) -> TabletToolButton {
|
||||||
|
if flags & PEN_FLAG_BARREL != 0 {
|
||||||
|
TabletToolButton::Barrel
|
||||||
|
} else if flags & PEN_FLAG_ERASER != 0 {
|
||||||
|
// NOTE: this is likely not a correct thing to do, but route
|
||||||
|
// it that way for now.
|
||||||
|
TabletToolButton::Other(PEN_FLAG_ERASER as u16)
|
||||||
|
} else {
|
||||||
|
TabletToolButton::Contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use windows_sys::Win32::UI::HiDpi::{
|
||||||
DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS,
|
DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS,
|
||||||
};
|
};
|
||||||
use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow;
|
use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow;
|
||||||
use windows_sys::Win32::UI::Input::Pointer::{POINTER_INFO, POINTER_TOUCH_INFO};
|
use windows_sys::Win32::UI::Input::Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO};
|
||||||
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||||
ClipCursor, GetClientRect, GetClipCursor, GetCursorPos, GetSystemMetrics, GetWindowPlacement,
|
ClipCursor, GetClientRect, GetClipCursor, GetCursorPos, GetSystemMetrics, GetWindowPlacement,
|
||||||
GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP,
|
GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP,
|
||||||
|
|
@ -254,6 +254,8 @@ pub type GetPointerDeviceRects = unsafe extern "system" fn(
|
||||||
|
|
||||||
pub type GetPointerTouchInfo =
|
pub type GetPointerTouchInfo =
|
||||||
unsafe extern "system" fn(pointer_id: u32, touch_info: *mut POINTER_TOUCH_INFO) -> BOOL;
|
unsafe extern "system" fn(pointer_id: u32, touch_info: *mut POINTER_TOUCH_INFO) -> BOOL;
|
||||||
|
pub type GetPointerPenInfo =
|
||||||
|
unsafe extern "system" fn(pointer_id: u32, pen_info: *mut POINTER_PEN_INFO) -> BOOL;
|
||||||
|
|
||||||
pub(crate) static WIN10_BUILD_VERSION: LazyLock<Option<u32>> = LazyLock::new(|| {
|
pub(crate) static WIN10_BUILD_VERSION: LazyLock<Option<u32>> = LazyLock::new(|| {
|
||||||
type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS;
|
type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS;
|
||||||
|
|
@ -306,6 +308,8 @@ pub(crate) static GET_POINTER_DEVICE_RECTS: LazyLock<Option<GetPointerDeviceRect
|
||||||
LazyLock::new(|| get_function!("user32.dll", GetPointerDeviceRects));
|
LazyLock::new(|| get_function!("user32.dll", GetPointerDeviceRects));
|
||||||
pub(crate) static GET_POINTER_TOUCH_INFO: LazyLock<Option<GetPointerTouchInfo>> =
|
pub(crate) static GET_POINTER_TOUCH_INFO: LazyLock<Option<GetPointerTouchInfo>> =
|
||||||
LazyLock::new(|| get_function!("user32.dll", GetPointerTouchInfo));
|
LazyLock::new(|| get_function!("user32.dll", GetPointerTouchInfo));
|
||||||
|
pub(crate) static GET_POINTER_PEN_INFO: LazyLock<Option<GetPointerPenInfo>> =
|
||||||
|
LazyLock::new(|| get_function!("user32.dll", GetPointerPenInfo));
|
||||||
|
|
||||||
pub(crate) fn wrap_device_id(id: u32) -> DeviceId {
|
pub(crate) fn wrap_device_id(id: u32) -> DeviceId {
|
||||||
DeviceId::from_raw(id as i64)
|
DeviceId::from_raw(id as i64)
|
||||||
|
|
|
||||||
|
|
@ -1263,6 +1263,7 @@ impl InitData<'_> {
|
||||||
_file_drop_handler: file_drop_handler,
|
_file_drop_handler: file_drop_handler,
|
||||||
userdata_removed: Cell::new(false),
|
userdata_removed: Cell::new(false),
|
||||||
recurse_depth: Cell::new(0),
|
recurse_depth: Cell::new(0),
|
||||||
|
last_tablet_down_button_state: Cell::new(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,14 @@ atom_manager! {
|
||||||
_NET_SUPPORTED,
|
_NET_SUPPORTED,
|
||||||
_NET_SUPPORTING_WM_CHECK,
|
_NET_SUPPORTING_WM_CHECK,
|
||||||
_XEMBED,
|
_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<AtomName> for Atoms {
|
impl Index<AtomName> for Atoms {
|
||||||
|
|
|
||||||
|
|
@ -1006,6 +1006,15 @@ pub struct Device {
|
||||||
// For master devices, this is the paired device (pointer <-> keyboard).
|
// For master devices, this is the paired device (pointer <-> keyboard).
|
||||||
// For slave devices, this is the master.
|
// For slave devices, this is the master.
|
||||||
pub(crate) attachment: c_int,
|
pub(crate) attachment: c_int,
|
||||||
|
pub(crate) r#type: DeviceType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) enum DeviceType {
|
||||||
|
Mouse,
|
||||||
|
Touch,
|
||||||
|
Pen,
|
||||||
|
Eraser,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
|
@ -1022,9 +1031,10 @@ pub(crate) enum ScrollOrientation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
pub(crate) fn new(info: &ffi::XIDeviceInfo) -> Self {
|
pub(crate) fn new(info: &ffi::XIDeviceInfo, atoms: &Atoms) -> Self {
|
||||||
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
|
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
|
||||||
let mut scroll_axes = Vec::new();
|
let mut scroll_axes = Vec::new();
|
||||||
|
let mut r#type = None;
|
||||||
|
|
||||||
if Device::physical_device(info) {
|
if Device::physical_device(info) {
|
||||||
// Identify scroll axes
|
// Identify scroll axes
|
||||||
|
|
@ -1041,12 +1051,34 @@ impl Device {
|
||||||
},
|
},
|
||||||
position: 0.0,
|
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 =
|
let mut device = Device {
|
||||||
Device { _name: name.into_owned(), scroll_axes, attachment: info.attachment };
|
_name: name.into_owned(),
|
||||||
|
scroll_axes,
|
||||||
|
attachment: info.attachment,
|
||||||
|
r#type: r#type.unwrap_or(DeviceType::Mouse),
|
||||||
|
};
|
||||||
device.reset_scroll_position(info);
|
device.reset_scroll_position(info);
|
||||||
device
|
device
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ use xkbcommon_dl::xkb_mod_mask_t;
|
||||||
use crate::atoms::*;
|
use crate::atoms::*;
|
||||||
use crate::dnd::{Dnd, DndState};
|
use crate::dnd::{Dnd, DndState};
|
||||||
use crate::event_loop::{
|
use crate::event_loop::{
|
||||||
mkdid, mkwid, ActiveEventLoop, CookieResultExt, Device, DeviceInfo, ScrollOrientation,
|
mkdid, mkwid, ActiveEventLoop, CookieResultExt, Device, DeviceInfo, DeviceType,
|
||||||
ALL_DEVICES,
|
ScrollOrientation, ALL_DEVICES,
|
||||||
};
|
};
|
||||||
use crate::ime::{ImeEvent, ImeEventReceiver, ImeReceiver, ImeRequest};
|
use crate::ime::{ImeEvent, ImeEventReceiver, ImeReceiver, ImeRequest};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
@ -323,8 +323,10 @@ impl EventProcessor {
|
||||||
pub fn init_device(&self, device: xinput::DeviceId) {
|
pub fn init_device(&self, device: xinput::DeviceId) {
|
||||||
let mut devices = self.devices.borrow_mut();
|
let mut devices = self.devices.borrow_mut();
|
||||||
if let Some(info) = DeviceInfo::get(&self.target.xconn, device as _) {
|
if let Some(info) = DeviceInfo::get(&self.target.xconn, device as _) {
|
||||||
|
let atoms = self.target.x_connection().atoms();
|
||||||
|
|
||||||
for info in info.iter() {
|
for info in info.iter() {
|
||||||
devices.insert(mkdid(info.deviceid as xinput::DeviceId), Device::new(info));
|
devices.insert(mkdid(info.deviceid as xinput::DeviceId), Device::new(info, atoms));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -974,6 +976,15 @@ impl EventProcessor {
|
||||||
// Set the timestamp.
|
// Set the timestamp.
|
||||||
self.target.xconn.set_timestamp(event.time as xproto::Timestamp);
|
self.target.xconn.set_timestamp(event.time as xproto::Timestamp);
|
||||||
|
|
||||||
|
let Some(DeviceType::Mouse) = self
|
||||||
|
.devices
|
||||||
|
.borrow()
|
||||||
|
.get(&mkdid(event.sourceid as xinput::DeviceId))
|
||||||
|
.map(|device| device.r#type)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
// Deliver multi-touch events instead of emulated mouse events.
|
// Deliver multi-touch events instead of emulated mouse events.
|
||||||
if (event.flags & xinput2::XIPointerEmulated) != 0 {
|
if (event.flags & xinput2::XIPointerEmulated) != 0 {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1051,6 +1062,15 @@ impl EventProcessor {
|
||||||
// Set the timestamp.
|
// Set the timestamp.
|
||||||
self.target.xconn.set_timestamp(event.time as xproto::Timestamp);
|
self.target.xconn.set_timestamp(event.time as xproto::Timestamp);
|
||||||
|
|
||||||
|
let Some(DeviceType::Mouse) = self
|
||||||
|
.devices
|
||||||
|
.borrow()
|
||||||
|
.get(&mkdid(event.sourceid as xinput::DeviceId))
|
||||||
|
.map(|device| device.r#type)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let device_id = Some(mkdid(event.deviceid as xinput::DeviceId));
|
let device_id = Some(mkdid(event.deviceid as xinput::DeviceId));
|
||||||
let window = event.event as xproto::Window;
|
let window = event.event as xproto::Window;
|
||||||
let window_id = mkwid(window);
|
let window_id = mkwid(window);
|
||||||
|
|
@ -1376,7 +1396,6 @@ impl EventProcessor {
|
||||||
self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||||
|
|
||||||
let did = Some(mkdid(xev.deviceid as xinput::DeviceId));
|
let did = Some(mkdid(xev.deviceid as xinput::DeviceId));
|
||||||
|
|
||||||
let mask =
|
let mask =
|
||||||
unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) };
|
unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) };
|
||||||
let mut value = xev.raw_values;
|
let mut value = xev.raw_values;
|
||||||
|
|
@ -1401,6 +1420,15 @@ impl EventProcessor {
|
||||||
value = unsafe { value.offset(1) };
|
value = unsafe { value.offset(1) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(DeviceType::Mouse) = self
|
||||||
|
.devices
|
||||||
|
.borrow()
|
||||||
|
.get(&mkdid(xev.sourceid as xinput::DeviceId))
|
||||||
|
.map(|device| device.r#type)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(mouse_delta) = mouse_delta.consume() {
|
if let Some(mouse_delta) = mouse_delta.consume() {
|
||||||
app.device_event(&self.target, did, DeviceEvent::PointerMotion { delta: mouse_delta });
|
app.device_event(&self.target, did, DeviceEvent::PointerMotion { delta: mouse_delta });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,10 @@ These files are created by [Mads Marquart](https://github.com/madsmtm) using
|
||||||
[draw.io](https://draw.io/), and compressed using [svgomg.net](https://svgomg.net/).
|
[draw.io](https://draw.io/), and compressed using [svgomg.net](https://svgomg.net/).
|
||||||
|
|
||||||
They are licensed under the [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.
|
They are licensed under the [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
|
|
||||||
BIN
winit/docs/res/tool_altitude.webp
Normal file
BIN
winit/docs/res/tool_altitude.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
winit/docs/res/tool_azimuth.webp
Normal file
BIN
winit/docs/res/tool_azimuth.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
winit/docs/res/tool_tilt_x.webp
Normal file
BIN
winit/docs/res/tool_tilt_x.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
winit/docs/res/tool_tilt_y.webp
Normal file
BIN
winit/docs/res/tool_tilt_y.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
|
|
@ -87,6 +87,7 @@ changelog entry.
|
||||||
- Add `Ime::DeleteSurrounding` to let the input method delete text.
|
- Add `Ime::DeleteSurrounding` to let the input method delete text.
|
||||||
- Add more `ImePurpose` values.
|
- Add more `ImePurpose` values.
|
||||||
- Add `ImeHints` to request particular IME behaviour.
|
- Add `ImeHints` to request particular IME behaviour.
|
||||||
|
- Add Pen input support on Wayland, Windows, and Web via new Pointer event.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
@ -210,6 +211,7 @@ changelog entry.
|
||||||
- Move `EventLoopExtRunOnDemand` from platform module to `winit::event_loop::run_on_demand`.
|
- Move `EventLoopExtRunOnDemand` from platform module to `winit::event_loop::run_on_demand`.
|
||||||
- Use `NamedKey`, `Code` and `Location` from the `keyboard-types` v0.8 crate.
|
- Use `NamedKey`, `Code` and `Location` from the `keyboard-types` v0.8 crate.
|
||||||
- Deprecate `Window::set_ime_allowed`, `Window::set_ime_cursor_area`, and `Window::set_ime_purpose`.
|
- Deprecate `Window::set_ime_allowed`, `Window::set_ime_cursor_area`, and `Window::set_ime_purpose`.
|
||||||
|
- `Force::normalized()` now takes a `Option<ToolAngle>` to calculate the perpendicular force.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
|
@ -249,6 +251,7 @@ changelog entry.
|
||||||
- Remove `NamedKey::Space`, match on `Key::Character(" ")` instead.
|
- Remove `NamedKey::Space`, match on `Key::Character(" ")` instead.
|
||||||
- Remove `PartialEq` impl for `WindowAttributes`.
|
- Remove `PartialEq` impl for `WindowAttributes`.
|
||||||
- `WindowAttributesExt*` platform extensions; use `WindowAttributes*` instead.
|
- `WindowAttributesExt*` platform extensions; use `WindowAttributes*` instead.
|
||||||
|
- Remove `Force::Calibrated::altitude_angle` in favor of `ToolAngle::altitude`.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
@ -264,3 +267,4 @@ changelog entry.
|
||||||
- On Windows, `Window::theme` will return the correct theme after setting it through `Window::set_theme`.
|
- On Windows, `Window::theme` will return the correct theme after setting it through `Window::set_theme`.
|
||||||
- On Windows, `Window::set_theme` will change the title bar color immediately now.
|
- On Windows, `Window::set_theme` will change the title bar color immediately now.
|
||||||
- On Windows 11, prevent incorrect shifting when dragging window onto a monitor with different DPI.
|
- On Windows 11, prevent incorrect shifting when dragging window onto a monitor with different DPI.
|
||||||
|
- On Web, device events are emitted regardless of cursor type.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue