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::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::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::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" },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
//! The event enums and assorted supporting types.
|
||||
use std::cell::LazyCell;
|
||||
use std::cmp::Ordering;
|
||||
use std::f64;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Mutex, Weak};
|
||||
|
||||
|
|
@ -435,6 +438,7 @@ pub enum PointerKind {
|
|||
///
|
||||
/// **macOS:** Unsupported.
|
||||
Touch(FingerId),
|
||||
TabletTool(TabletToolKind),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
|
@ -485,6 +489,13 @@ pub enum PointerSource {
|
|||
/// force will be 0.5 when a button is pressed or 0.0 otherwise.
|
||||
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,
|
||||
}
|
||||
|
||||
|
|
@ -493,6 +504,7 @@ impl From<PointerSource> for PointerKind {
|
|||
match source {
|
||||
PointerSource::Mouse => Self::Mouse,
|
||||
PointerSource::Touch { finger_id, .. } => Self::Touch(finger_id),
|
||||
PointerSource::TabletTool { kind, .. } => Self::TabletTool(kind),
|
||||
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
|
||||
/// system.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ButtonSource {
|
||||
Mouse(MouseButton),
|
||||
/// See [`PointerSource::Touch`] for more details.
|
||||
|
|
@ -514,6 +526,11 @@ pub enum ButtonSource {
|
|||
finger_id: FingerId,
|
||||
force: Option<Force>,
|
||||
},
|
||||
TabletTool {
|
||||
kind: TabletToolKind,
|
||||
button: TabletToolButton,
|
||||
data: TabletToolData,
|
||||
},
|
||||
Unknown(u16),
|
||||
}
|
||||
|
||||
|
|
@ -525,6 +542,7 @@ impl ButtonSource {
|
|||
match self {
|
||||
ButtonSource::Mouse(mouse) => mouse,
|
||||
ButtonSource::Touch { .. } => MouseButton::Left,
|
||||
ButtonSource::TabletTool { button, .. } => button.into(),
|
||||
ButtonSource::Unknown(button) => match button {
|
||||
0 => MouseButton::Left,
|
||||
1 => MouseButton::Middle,
|
||||
|
|
@ -981,6 +999,7 @@ pub enum TouchPhase {
|
|||
/// Describes the force of a touch event
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[doc(alias = "Pressure")]
|
||||
pub enum Force {
|
||||
/// On iOS, the force is calibrated so that the same number corresponds to
|
||||
/// roughly the same amount of pressure on the screen regardless of the
|
||||
|
|
@ -991,7 +1010,7 @@ pub enum Force {
|
|||
///
|
||||
/// The force reported by Apple Pencil is measured along the axis of the
|
||||
/// pencil. If you want a force perpendicular to the device, you need to
|
||||
/// calculate this value using the `altitude_angle` value.
|
||||
/// calculate this value using the [`TabletToolAngle::altitude`] value.
|
||||
force: f64,
|
||||
/// The maximum possible force for a touch.
|
||||
///
|
||||
|
|
@ -1012,9 +1031,17 @@ impl Force {
|
|||
/// Instead of normalizing the force, you should prefer to handle
|
||||
/// [`Force::Calibrated`] so that the amount of force the user has to apply is
|
||||
/// consistent across devices.
|
||||
pub fn normalized(&self) -> f64 {
|
||||
///
|
||||
/// Passing in a [`TabletToolAngle`], returns the perpendicular force.
|
||||
pub fn normalized(&self, angle: Option<TabletToolAngle>) -> f64 {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
@ -1026,6 +1053,264 @@ pub type AxisId = u32;
|
|||
/// Identifier for a specific button on some device.
|
||||
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.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
|
@ -1058,6 +1343,28 @@ pub enum MouseButton {
|
|||
Other(u16),
|
||||
}
|
||||
|
||||
/// Describes a button of a tool, e.g. a pen.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum 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.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[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]
|
||||
fn test_force_normalize() {
|
||||
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 };
|
||||
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 };
|
||||
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)]
|
||||
|
|
|
|||
|
|
@ -461,6 +461,10 @@ impl WinitView {
|
|||
let window = self.window().unwrap();
|
||||
let mut touch_events = Vec::new();
|
||||
for touch in touches {
|
||||
if let UITouchType::Pencil = touch.r#type() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let logical_location = touch.locationInView(None);
|
||||
let touch_type = touch.r#type();
|
||||
let force = if let UITouchType::Pencil = touch_type {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
|
|||
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
|
||||
use tracing::warn;
|
||||
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::keyboard::ModifiersState;
|
||||
|
||||
|
|
@ -50,6 +51,9 @@ pub struct WinitSeatState {
|
|||
/// The text input bound on the seat.
|
||||
text_input: Option<Arc<ZwpTextInputV3>>,
|
||||
|
||||
/// The tablet input bound on the seat.
|
||||
tablet: Option<Arc<ZwpTabletSeatV2>>,
|
||||
|
||||
/// The relative pointer bound on the seat.
|
||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||
|
||||
|
|
@ -156,6 +160,12 @@ impl SeatHandler for WinitState {
|
|||
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(
|
||||
|
|
@ -177,6 +187,11 @@ impl SeatHandler for WinitState {
|
|||
text_input.destroy();
|
||||
}
|
||||
|
||||
// NOTE: figure out when this should actually be destroyed.
|
||||
if let Some(tablet) = seat_state.tablet.take() {
|
||||
tablet.destroy();
|
||||
}
|
||||
|
||||
match capability {
|
||||
SeatCapability::Touch => {
|
||||
if let Some(touch) = seat_state.touch.take() {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use crate::seat::{
|
|||
};
|
||||
use crate::types::kwin_blur::KWinBlurManager;
|
||||
use crate::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use crate::types::wp_tablet_input_v2::TabletManager;
|
||||
use crate::types::wp_viewporter::ViewporterState;
|
||||
use crate::types::xdg_activation::XdgActivationState;
|
||||
use crate::types::xdg_toplevel_icon_manager::XdgToplevelIconManagerState;
|
||||
|
|
@ -100,6 +101,9 @@ pub struct WinitState {
|
|||
/// Relative pointer.
|
||||
pub relative_pointer: Option<RelativePointerState>,
|
||||
|
||||
/// Tablet manager.
|
||||
pub tablet_state: Option<TabletManager>,
|
||||
|
||||
/// Pointer constraints to handle pointer locking and confining.
|
||||
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
|
||||
|
||||
|
|
@ -194,6 +198,7 @@ impl WinitState {
|
|||
text_input_state: TextInputState::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)
|
||||
.map(Arc::new)
|
||||
.ok(),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
pub mod cursor;
|
||||
pub mod kwin_blur;
|
||||
pub mod wp_fractional_scaling;
|
||||
pub mod wp_tablet_input_v2;
|
||||
pub mod wp_viewporter;
|
||||
pub mod xdg_activation;
|
||||
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) => {
|
||||
let cursor = match cursor.cast_ref::<CustomCursor>() {
|
||||
Some(cursor) => cursor,
|
||||
None => todo!(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
if let SelectedCursor::Loading { cursor: old_cursor, .. }
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use crate::event_loop::ActiveEventLoop;
|
|||
use crate::main_thread::MainThreadMarker;
|
||||
use crate::monitor::MonitorHandler;
|
||||
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::{backend, event, EventLoop, PollStrategy, WaitUntilStrategy};
|
||||
|
||||
|
|
@ -318,8 +318,10 @@ impl Shared {
|
|||
// chorded button event
|
||||
let device_id = event::mkdid(event.pointer_id());
|
||||
|
||||
if let Some(button) = backend::event::mouse_button(&event) {
|
||||
let state = if backend::event::mouse_buttons(&event).contains(button.into()) {
|
||||
if let Some(button) = backend::event::raw_button(&event) {
|
||||
let state = if backend::event::pointer_buttons(&event)
|
||||
.contains(ButtonsState::from_bits_retain(button))
|
||||
{
|
||||
ElementState::Pressed
|
||||
} else {
|
||||
ElementState::Released
|
||||
|
|
@ -327,10 +329,7 @@ impl Shared {
|
|||
|
||||
runner.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: DeviceEvent::Button {
|
||||
button: mouse_button_to_id(button).into(),
|
||||
state,
|
||||
},
|
||||
event: DeviceEvent::Button { button: button.into(), state },
|
||||
});
|
||||
|
||||
return;
|
||||
|
|
@ -338,14 +337,16 @@ impl Shared {
|
|||
|
||||
// pointer move 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));
|
||||
|
||||
Event::DeviceEvent {
|
||||
device_id,
|
||||
event: DeviceEvent::PointerMotion { delta: (delta.x, delta.y) },
|
||||
}
|
||||
}));
|
||||
},
|
||||
));
|
||||
}),
|
||||
));
|
||||
let runner = self.clone();
|
||||
|
|
@ -375,11 +376,11 @@ impl Shared {
|
|||
return;
|
||||
}
|
||||
|
||||
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
|
||||
let button = backend::event::raw_button(&event).expect("no pointer button pressed");
|
||||
runner.send_event(Event::DeviceEvent {
|
||||
device_id: event::mkdid(event.pointer_id()),
|
||||
event: DeviceEvent::Button {
|
||||
button: mouse_button_to_id(button).into(),
|
||||
button: button.into(),
|
||||
state: ElementState::Pressed,
|
||||
},
|
||||
});
|
||||
|
|
@ -394,11 +395,11 @@ impl Shared {
|
|||
return;
|
||||
}
|
||||
|
||||
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
|
||||
let button = backend::event::raw_button(&event).expect("no pointer button pressed");
|
||||
runner.send_event(Event::DeviceEvent {
|
||||
device_id: event::mkdid(event.pointer_id()),
|
||||
event: DeviceEvent::Button {
|
||||
button: mouse_button_to_id(button).into(),
|
||||
button: button.into(),
|
||||
state: ElementState::Released,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
use std::cell::OnceCell;
|
||||
use std::f64;
|
||||
|
||||
use dpi::{LogicalPosition, PhysicalPosition, Position};
|
||||
use smol_str::SmolStr;
|
||||
use tracing::warn;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{KeyboardEvent, MouseEvent, Navigator, PointerEvent, WheelEvent};
|
||||
use winit_core::event::{FingerId, MouseButton, MouseScrollDelta, PointerKind};
|
||||
use web_sys::{Event, KeyboardEvent, MouseEvent, Navigator, PointerEvent, WheelEvent};
|
||||
use winit_core::event::{
|
||||
ButtonSource, FingerId, Force, MouseButton, MouseScrollDelta, PointerKind, PointerSource,
|
||||
TabletToolAngle, TabletToolButton, TabletToolData, TabletToolKind, TabletToolTilt,
|
||||
};
|
||||
use winit_core::keyboard::{
|
||||
Key, KeyCode, KeyLocation, ModifiersState, NamedKey, NativeKey, NativeKeyCode, PhysicalKey,
|
||||
};
|
||||
|
|
@ -16,11 +21,12 @@ bitflags::bitflags! {
|
|||
// https://www.w3.org/TR/pointerevents3/#the-buttons-property
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ButtonsState: u16 {
|
||||
const LEFT = 0b00001;
|
||||
const RIGHT = 0b00010;
|
||||
const MIDDLE = 0b00100;
|
||||
const BACK = 0b01000;
|
||||
const FORWARD = 0b10000;
|
||||
const LEFT = 0b000001;
|
||||
const RIGHT = 0b000010;
|
||||
const MIDDLE = 0b000100;
|
||||
const BACK = 0b001000;
|
||||
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 {
|
||||
fn from(value: MouseButton) -> Self {
|
||||
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())
|
||||
}
|
||||
|
||||
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
|
||||
match event.button() {
|
||||
-1 => None,
|
||||
0 => Some(MouseButton::Left),
|
||||
1 => Some(MouseButton::Middle),
|
||||
2 => Some(MouseButton::Right),
|
||||
3 => Some(MouseButton::Back),
|
||||
4 => Some(MouseButton::Forward),
|
||||
i => {
|
||||
Some(MouseButton::Other(i.try_into().expect("unexpected negative mouse button value")))
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let button = event.button();
|
||||
|
||||
if button == -1 {
|
||||
None
|
||||
} else {
|
||||
Some(button.try_into().expect("unexpected negative mouse button value"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn 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 {
|
||||
match button {
|
||||
MouseButton::Left => 0,
|
||||
MouseButton::Right => 1,
|
||||
MouseButton::Middle => 2,
|
||||
MouseButton::Back => 3,
|
||||
MouseButton::Forward => 4,
|
||||
MouseButton::Other(value) => value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
|
||||
pub fn pointer_kind(event: &PointerEvent, pointer_id: i32) -> PointerKind {
|
||||
match WebPointerType::from_event(event) {
|
||||
Some(WebPointerType::Mouse) => PointerKind::Mouse,
|
||||
Some(WebPointerType::Touch) => PointerKind::Touch(FingerId::from_raw(pointer_id as usize)),
|
||||
Some(WebPointerType::Pen) => {
|
||||
PointerKind::TabletTool(if pointer_buttons(event).contains(ButtonsState::ERASER) {
|
||||
TabletToolKind::Eraser
|
||||
} else {
|
||||
TabletToolKind::Pen
|
||||
})
|
||||
},
|
||||
None => PointerKind::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
extern "C" {
|
||||
type MouseEventExt;
|
||||
|
|
@ -113,7 +224,7 @@ impl MouseDelta {
|
|||
Some(Engine::Chromium) => Self::Chromium,
|
||||
// Firefox has wrong movement values in coalesced events.
|
||||
Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko {
|
||||
old_position: mouse_position(event),
|
||||
old_position: pointer_position(event),
|
||||
old_delta: LogicalPosition::new(
|
||||
event.movement_x() as f64,
|
||||
event.movement_y() as f64,
|
||||
|
|
@ -129,7 +240,7 @@ impl MouseDelta {
|
|||
PhysicalPosition::new(event.movement_x(), event.movement_y()).into()
|
||||
},
|
||||
MouseDelta::Gecko { old_position, old_delta } => {
|
||||
let new_position = mouse_position(event);
|
||||
let new_position = pointer_position(event);
|
||||
let x = new_position.x - old_position.x + old_delta.x;
|
||||
let y = new_position.y - old_position.y + old_delta.y;
|
||||
*old_position = new_position;
|
||||
|
|
@ -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 {
|
||||
// Use keyboard-types' parsing (it is based on the W3C standard).
|
||||
match event.code().parse() {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ use std::rc::Rc;
|
|||
|
||||
use dpi::PhysicalPosition;
|
||||
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 super::canvas::Common;
|
||||
use super::event;
|
||||
use super::event_handle::EventListenerHandle;
|
||||
use crate::event::mkdid;
|
||||
use crate::web_sys::event::mouse_button_to_id;
|
||||
use crate::web_sys::event::ButtonsState;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) struct PointerHandler {
|
||||
|
|
@ -46,8 +46,8 @@ impl PointerHandler {
|
|||
let pointer_id = event.pointer_id();
|
||||
let device_id = mkdid(pointer_id);
|
||||
let position =
|
||||
event::mouse_position(&event).to_physical(super::scale_factor(&window));
|
||||
let kind = event::pointer_type(&event, pointer_id);
|
||||
event::pointer_position(&event).to_physical(super::scale_factor(&window));
|
||||
let kind = event::pointer_kind(&event, pointer_id);
|
||||
handler(modifiers, device_id, event.is_primary(), position, kind);
|
||||
}));
|
||||
}
|
||||
|
|
@ -64,8 +64,8 @@ impl PointerHandler {
|
|||
let pointer_id = event.pointer_id();
|
||||
let device_id = mkdid(pointer_id);
|
||||
let position =
|
||||
event::mouse_position(&event).to_physical(super::scale_factor(&window));
|
||||
let kind = event::pointer_type(&event, pointer_id);
|
||||
event::pointer_position(&event).to_physical(super::scale_factor(&window));
|
||||
let kind = event::pointer_kind(&event, pointer_id);
|
||||
handler(modifiers, device_id, event.is_primary(), position, kind);
|
||||
}));
|
||||
}
|
||||
|
|
@ -80,24 +80,25 @@ impl PointerHandler {
|
|||
Some(canvas_common.add_event("pointerup", move |event: PointerEvent| {
|
||||
let modifiers = event::mouse_modifiers(&event);
|
||||
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 kind {
|
||||
PointerKind::Mouse => ButtonSource::Mouse(button),
|
||||
PointerKind::Touch(finger_id) => ButtonSource::Touch {
|
||||
finger_id,
|
||||
force: Some(Force::Normalized(event.pressure().into())),
|
||||
let source = match event::pointer_source(&event, kind) {
|
||||
PointerSource::Mouse => ButtonSource::Mouse(event::mouse_button(button)),
|
||||
PointerSource::Touch { finger_id, force } => {
|
||||
ButtonSource::Touch { finger_id, force }
|
||||
},
|
||||
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(
|
||||
modifiers,
|
||||
mkdid(pointer_id),
|
||||
event.is_primary(),
|
||||
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
|
||||
event::pointer_position(&event).to_physical(super::scale_factor(&window)),
|
||||
source,
|
||||
)
|
||||
}));
|
||||
|
|
@ -125,11 +126,11 @@ impl PointerHandler {
|
|||
|
||||
let modifiers = event::mouse_modifiers(&event);
|
||||
let pointer_id = event.pointer_id();
|
||||
let kind = event::pointer_type(&event, pointer_id);
|
||||
let button = event::mouse_button(&event).expect("no mouse button pressed");
|
||||
let kind = event::pointer_kind(&event, pointer_id);
|
||||
let button = event::raw_button(&event).expect("no button pressed");
|
||||
|
||||
let source = match kind {
|
||||
PointerKind::Mouse => {
|
||||
let source = match event::pointer_source(&event, kind) {
|
||||
PointerSource::Mouse => {
|
||||
// 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
|
||||
|
|
@ -137,20 +138,29 @@ impl PointerHandler {
|
|||
// care if it fails.
|
||||
let _e = canvas.set_pointer_capture(pointer_id);
|
||||
|
||||
ButtonSource::Mouse(button)
|
||||
ButtonSource::Mouse(event::mouse_button(button))
|
||||
},
|
||||
PointerKind::Touch(finger_id) => ButtonSource::Touch {
|
||||
finger_id,
|
||||
force: Some(Force::Normalized(event.pressure().into())),
|
||||
PointerSource::Touch { finger_id, force } => {
|
||||
ButtonSource::Touch { finger_id, force }
|
||||
},
|
||||
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(
|
||||
modifiers,
|
||||
mkdid(pointer_id),
|
||||
event.is_primary(),
|
||||
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
|
||||
event::pointer_position(&event).to_physical(super::scale_factor(&window)),
|
||||
source,
|
||||
)
|
||||
}));
|
||||
|
|
@ -186,11 +196,11 @@ impl PointerHandler {
|
|||
Some(canvas_common.add_event("pointermove", move |event: PointerEvent| {
|
||||
let pointer_id = event.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();
|
||||
|
||||
// chorded button event
|
||||
if let Some(button) = event::mouse_button(&event) {
|
||||
if let Some(button) = event::raw_button(&event) {
|
||||
if prevent_default.get() {
|
||||
// prevent text selection
|
||||
event.prevent_default();
|
||||
|
|
@ -198,34 +208,36 @@ impl PointerHandler {
|
|||
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
|
||||
} else {
|
||||
ElementState::Released
|
||||
};
|
||||
|
||||
let button = match kind {
|
||||
PointerKind::Mouse => ButtonSource::Mouse(button),
|
||||
PointerKind::Touch(finger_id) => {
|
||||
let button_id = mouse_button_to_id(button);
|
||||
|
||||
if button_id != 1 {
|
||||
tracing::error!("unexpected touch button id: {button_id}");
|
||||
let button = match event::pointer_source(&event, kind) {
|
||||
PointerSource::Mouse => ButtonSource::Mouse(event::mouse_button(button)),
|
||||
PointerSource::Touch { finger_id, force } => {
|
||||
if button != 0 {
|
||||
tracing::error!("unexpected touch button id: {button}");
|
||||
}
|
||||
|
||||
ButtonSource::Touch {
|
||||
finger_id,
|
||||
force: Some(Force::Normalized(event.pressure().into())),
|
||||
}
|
||||
ButtonSource::Touch { finger_id, force }
|
||||
},
|
||||
PointerKind::Unknown => todo!(),
|
||||
PointerSource::TabletTool { kind, data } => ButtonSource::TabletTool {
|
||||
kind,
|
||||
button: event::tool_button(button),
|
||||
data,
|
||||
},
|
||||
PointerSource::Unknown => ButtonSource::Unknown(button),
|
||||
};
|
||||
|
||||
button_handler(
|
||||
event::mouse_modifiers(&event),
|
||||
device_id,
|
||||
primary,
|
||||
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
|
||||
event::pointer_position(&event).to_physical(super::scale_factor(&window)),
|
||||
state,
|
||||
button,
|
||||
);
|
||||
|
|
@ -242,15 +254,8 @@ impl PointerHandler {
|
|||
(
|
||||
event::mouse_modifiers(&event),
|
||||
event.is_primary(),
|
||||
event::mouse_position(&event).to_physical(scale),
|
||||
match kind {
|
||||
PointerKind::Mouse => PointerSource::Mouse,
|
||||
PointerKind::Touch(finger_id) => PointerSource::Touch {
|
||||
finger_id,
|
||||
force: Some(Force::Normalized(event.pressure().into())),
|
||||
},
|
||||
PointerKind::Unknown => PointerSource::Unknown,
|
||||
},
|
||||
event::pointer_position(&event).to_physical(scale),
|
||||
event::pointer_source(&event, kind),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -44,28 +44,29 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
|
|||
GetMenu, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW,
|
||||
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, SystemParametersInfoW,
|
||||
TranslateMessage, CREATESTRUCTW, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO,
|
||||
MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_REMOVE, PT_TOUCH, QS_ALLINPUT,
|
||||
RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
|
||||
SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE,
|
||||
SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT,
|
||||
WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT, WM_CAPTURECHANGED, WM_CLOSE,
|
||||
WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO,
|
||||
WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION,
|
||||
WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
|
||||
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
|
||||
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
|
||||
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS,
|
||||
WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH,
|
||||
WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW,
|
||||
WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP,
|
||||
WS_VISIBLE,
|
||||
MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PEN_FLAG_BARREL, PEN_FLAG_ERASER,
|
||||
PEN_MASK_PRESSURE, PEN_MASK_ROTATION, PEN_MASK_TILT_X, PEN_MASK_TILT_Y, PM_REMOVE, PT_PEN,
|
||||
PT_TOUCH, QS_ALLINPUT, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE,
|
||||
SIZE_MAXIMIZED, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SWP_NOACTIVATE, SWP_NOMOVE,
|
||||
SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT,
|
||||
WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT,
|
||||
WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE,
|
||||
WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION,
|
||||
WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS,
|
||||
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL,
|
||||
WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
|
||||
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN,
|
||||
WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND,
|
||||
WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING,
|
||||
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::cursor::{CustomCursor, CustomCursorSource};
|
||||
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
|
||||
use winit_core::event::{
|
||||
DeviceEvent, DeviceId, FingerId, Force, Ime, RawKeyEvent, SurfaceSizeWriter, TouchPhase,
|
||||
WindowEvent,
|
||||
DeviceEvent, DeviceId, FingerId, Force, Ime, RawKeyEvent, SurfaceSizeWriter, TabletToolButton,
|
||||
TabletToolData, TabletToolKind, TabletToolTilt, TouchPhase, WindowEvent,
|
||||
};
|
||||
use winit_core::event_loop::pump_events::PumpStatus;
|
||||
use winit_core::event_loop::{
|
||||
|
|
@ -106,6 +107,7 @@ pub(crate) struct WindowData {
|
|||
pub key_event_builder: KeyEventBuilder,
|
||||
pub _file_drop_handler: Option<FileDropHandler>,
|
||||
pub userdata_removed: Cell<bool>,
|
||||
pub last_tablet_down_button_state: Cell<u32>,
|
||||
pub recurse_depth: Cell<u32>,
|
||||
}
|
||||
|
||||
|
|
@ -2085,22 +2087,6 @@ unsafe fn public_window_callback_inner(
|
|||
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 y = location.y as f64 + y.fract();
|
||||
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 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 {
|
||||
device_id: None,
|
||||
primary,
|
||||
position,
|
||||
kind: if let PT_TOUCH = pointer_info.pointerType {
|
||||
PointerKind::Touch(finger_id)
|
||||
} else {
|
||||
PointerKind::Unknown
|
||||
},
|
||||
kind,
|
||||
});
|
||||
|
||||
userdata.send_window_event(window, WindowEvent::PointerButton {
|
||||
device_id: None,
|
||||
primary,
|
||||
state: Pressed,
|
||||
position,
|
||||
button: if let PT_TOUCH = pointer_info.pointerType {
|
||||
ButtonSource::Touch { finger_id, force }
|
||||
} else {
|
||||
ButtonSource::Unknown(0)
|
||||
},
|
||||
button,
|
||||
});
|
||||
} else if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UP) {
|
||||
} else {
|
||||
userdata.send_window_event(window, WindowEvent::PointerButton {
|
||||
device_id: None,
|
||||
primary,
|
||||
state: Released,
|
||||
position,
|
||||
button: if let PT_TOUCH = pointer_info.pointerType {
|
||||
ButtonSource::Touch { finger_id, force }
|
||||
} else {
|
||||
ButtonSource::Unknown(0)
|
||||
},
|
||||
button,
|
||||
});
|
||||
userdata.send_window_event(window, WindowEvent::PointerLeft {
|
||||
device_id: None,
|
||||
primary,
|
||||
position: Some(position),
|
||||
kind: if let PT_TOUCH = pointer_info.pointerType {
|
||||
PointerKind::Touch(finger_id)
|
||||
} else {
|
||||
PointerKind::Unknown
|
||||
},
|
||||
kind,
|
||||
});
|
||||
}
|
||||
} 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 {
|
||||
device_id: None,
|
||||
primary,
|
||||
position,
|
||||
source: if let PT_TOUCH = pointer_info.pointerType {
|
||||
PointerSource::Touch { finger_id, force }
|
||||
} else {
|
||||
PointerSource::Unknown
|
||||
},
|
||||
source,
|
||||
});
|
||||
} else {
|
||||
continue;
|
||||
|
|
@ -2691,3 +2700,57 @@ fn apply_win10_dpi_adjustment(
|
|||
|
||||
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,
|
||||
};
|
||||
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::{
|
||||
ClipCursor, GetClientRect, GetClipCursor, GetCursorPos, GetSystemMetrics, GetWindowPlacement,
|
||||
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 =
|
||||
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(|| {
|
||||
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));
|
||||
pub(crate) static GET_POINTER_TOUCH_INFO: LazyLock<Option<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 {
|
||||
DeviceId::from_raw(id as i64)
|
||||
|
|
|
|||
|
|
@ -1263,6 +1263,7 @@ impl InitData<'_> {
|
|||
_file_drop_handler: file_drop_handler,
|
||||
userdata_removed: Cell::new(false),
|
||||
recurse_depth: Cell::new(0),
|
||||
last_tablet_down_button_state: Cell::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,14 @@ atom_manager! {
|
|||
_NET_SUPPORTED,
|
||||
_NET_SUPPORTING_WM_CHECK,
|
||||
_XEMBED,
|
||||
_XSETTINGS_SETTINGS
|
||||
_XSETTINGS_SETTINGS,
|
||||
|
||||
// Stylus Atoms
|
||||
ABS_X: b"Abs X",
|
||||
ABS_Y: b"Abs Y",
|
||||
ABS_PRESSURE: b"Abs Pressure",
|
||||
ABS_TILT_X: b"Abs Tilt X",
|
||||
ABS_TILT_Y: b"Abs Tilt Y"
|
||||
}
|
||||
|
||||
impl Index<AtomName> for Atoms {
|
||||
|
|
|
|||
|
|
@ -1006,6 +1006,15 @@ pub struct Device {
|
|||
// For master devices, this is the paired device (pointer <-> keyboard).
|
||||
// For slave devices, this is the master.
|
||||
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)]
|
||||
|
|
@ -1022,9 +1031,10 @@ pub(crate) enum ScrollOrientation {
|
|||
}
|
||||
|
||||
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 mut scroll_axes = Vec::new();
|
||||
let mut r#type = None;
|
||||
|
||||
if Device::physical_device(info) {
|
||||
// Identify scroll axes
|
||||
|
|
@ -1041,12 +1051,34 @@ impl Device {
|
|||
},
|
||||
position: 0.0,
|
||||
}));
|
||||
} else if ty == ffi::XITouchClass {
|
||||
r#type = Some(DeviceType::Touch);
|
||||
} else if r#type.is_none() && ty == ffi::XIValuatorClass {
|
||||
let info = unsafe { &*(class_ptr as *const ffi::XIValuatorClassInfo) };
|
||||
let atom = info.label as xproto::Atom;
|
||||
|
||||
if atom == atoms[ABS_X]
|
||||
|| atom == atoms[ABS_Y]
|
||||
|| atom == atoms[ABS_PRESSURE]
|
||||
|| atom == atoms[ABS_TILT_X]
|
||||
|| atom == atoms[ABS_TILT_Y]
|
||||
{
|
||||
if name.contains("eraser") {
|
||||
r#type = Some(DeviceType::Eraser);
|
||||
} else {
|
||||
r#type = Some(DeviceType::Pen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut device =
|
||||
Device { _name: name.into_owned(), scroll_axes, attachment: info.attachment };
|
||||
let mut device = Device {
|
||||
_name: name.into_owned(),
|
||||
scroll_axes,
|
||||
attachment: info.attachment,
|
||||
r#type: r#type.unwrap_or(DeviceType::Mouse),
|
||||
};
|
||||
device.reset_scroll_position(info);
|
||||
device
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ use xkbcommon_dl::xkb_mod_mask_t;
|
|||
use crate::atoms::*;
|
||||
use crate::dnd::{Dnd, DndState};
|
||||
use crate::event_loop::{
|
||||
mkdid, mkwid, ActiveEventLoop, CookieResultExt, Device, DeviceInfo, ScrollOrientation,
|
||||
ALL_DEVICES,
|
||||
mkdid, mkwid, ActiveEventLoop, CookieResultExt, Device, DeviceInfo, DeviceType,
|
||||
ScrollOrientation, ALL_DEVICES,
|
||||
};
|
||||
use crate::ime::{ImeEvent, ImeEventReceiver, ImeReceiver, ImeRequest};
|
||||
use crate::util;
|
||||
|
|
@ -323,8 +323,10 @@ impl EventProcessor {
|
|||
pub fn init_device(&self, device: xinput::DeviceId) {
|
||||
let mut devices = self.devices.borrow_mut();
|
||||
if let Some(info) = DeviceInfo::get(&self.target.xconn, device as _) {
|
||||
let atoms = self.target.x_connection().atoms();
|
||||
|
||||
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.
|
||||
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.
|
||||
if (event.flags & xinput2::XIPointerEmulated) != 0 {
|
||||
return;
|
||||
|
|
@ -1051,6 +1062,15 @@ impl EventProcessor {
|
|||
// Set the 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 window = event.event as xproto::Window;
|
||||
let window_id = mkwid(window);
|
||||
|
|
@ -1376,7 +1396,6 @@ impl EventProcessor {
|
|||
self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
let did = Some(mkdid(xev.deviceid as xinput::DeviceId));
|
||||
|
||||
let mask =
|
||||
unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) };
|
||||
let mut value = xev.raw_values;
|
||||
|
|
@ -1401,6 +1420,15 @@ impl EventProcessor {
|
|||
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() {
|
||||
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/).
|
||||
|
||||
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 more `ImePurpose` values.
|
||||
- Add `ImeHints` to request particular IME behaviour.
|
||||
- Add Pen input support on Wayland, Windows, and Web via new Pointer event.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
@ -210,6 +211,7 @@ changelog entry.
|
|||
- Move `EventLoopExtRunOnDemand` from platform module to `winit::event_loop::run_on_demand`.
|
||||
- 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`.
|
||||
- `Force::normalized()` now takes a `Option<ToolAngle>` to calculate the perpendicular force.
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
@ -249,6 +251,7 @@ changelog entry.
|
|||
- Remove `NamedKey::Space`, match on `Key::Character(" ")` instead.
|
||||
- Remove `PartialEq` impl for `WindowAttributes`.
|
||||
- `WindowAttributesExt*` platform extensions; use `WindowAttributes*` instead.
|
||||
- Remove `Force::Calibrated::altitude_angle` in favor of `ToolAngle::altitude`.
|
||||
|
||||
### 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::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 Web, device events are emitted regardless of cursor type.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue