diff --git a/CHANGELOG.md b/CHANGELOG.md index 4352b252..95e7385b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - On iOS, add `set_prefers_status_bar_hidden` extension function instead of hijacking `set_decorations` for this purpose. - On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`. +- On iOS, add touch pressure information for touch events. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/FEATURES.md b/FEATURES.md index 134aef96..fe8e50ea 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -103,6 +103,7 @@ If your PR makes notable changes to Winit's features, please update this section - **Cursor grab**: Locking the cursor so it cannot exit the client area of a window. - **Cursor icon**: Changing the cursor icon, or hiding the cursor. - **Touch events**: Single-touch events. +- **Touch pressure**: Touch events contain information about the amount of force being applied. - **Multitouch**: Multi-touch events, including cancellation of a gesture. - **Keyboard events**: Properly processing keyboard events using the user-specified keymap and translating keypresses into UTF-8 characters, handling dead keys and IMEs. @@ -192,6 +193,7 @@ Legend: |Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ | |Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ | +|Touch pressure |❌ |❌ |❌ |❌ |❌ |✔️ |❌ | |Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❌ | |Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | diff --git a/src/event.rs b/src/event.rs index 8e0ac043..401ed8b6 100644 --- a/src/event.rs +++ b/src/event.rs @@ -303,30 +303,94 @@ pub enum TouchPhase { Cancelled, } -/// Represents touch event +/// Represents a touch event /// -/// Every time user touches screen new Start event with some finger id is generated. -/// When the finger is removed from the screen End event with same id is generated. +/// Every time the user touches the screen, a new `Start` event with an unique +/// identifier for the finger is generated. When the finger is lifted, an `End` +/// event is generated with the same finger id. /// -/// For every id there will be at least 2 events with phases Start and End (or Cancelled). -/// There may be 0 or more Move events. +/// After a `Start` event has been emitted, there may be zero or more `Move` +/// events when the finger is moved or the touch pressure changes. /// +/// The finger id may be reused by the system after an `End` event. The user +/// should assume that a new `Start` event received with the same id has nothing +/// to do with the old finger and is a new finger. /// -/// Depending on platform implementation id may or may not be reused by system after End event. -/// -/// Gesture regonizer using this event should assume that Start event received with same id -/// as previously received End event is a new finger and has nothing to do with an old one. -/// -/// Touch may be cancelled if for example window lost focus. +/// A `Cancelled` event is emitted when the system has canceled tracking this +/// touch, such as when the window loses focus, or on iOS if the user moves the +/// device against their face. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Touch { pub device_id: DeviceId, pub phase: TouchPhase, pub location: LogicalPosition, - /// unique identifier of a finger. + /// Describes how hard the screen was pressed. May be `None` if the platform + /// does not support pressure sensitivity. + /// + /// ## Platform-specific + /// + /// - Only available on **iOS**. + pub force: Option, + /// Unique identifier of a finger. pub id: u64, } +/// Describes the force of a touch event +#[derive(Debug, Clone, Copy, PartialEq)] +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 + /// device. + Calibrated { + /// The force of the touch, where a value of 1.0 represents the force of + /// an average touch (predetermined by the system, not user-specific). + /// + /// 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. + force: f64, + /// The maximum possible force for a touch. + /// + /// The value of this field is sufficiently high to provide a wide + /// dynamic range for values of the `force` field. + max_possible_force: f64, + /// The altitude (in radians) of the stylus. + /// + /// A value of 0 radians indicates that the stylus is parallel to the + /// surface. The value of this property is Pi/2 when the stylus is + /// perpendicular to the surface. + altitude_angle: Option, + }, + /// If the platform reports the force as normalized, we have no way of + /// knowing how much pressure 1.0 corresponds to – we know it's the maximum + /// amount of force, but as to how much force, you might either have to + /// press really really hard, or not hard at all, depending on the device. + Normalized(f64), +} + +impl Force { + /// Returns the force normalized to the range between 0.0 and 1.0 inclusive. + /// 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 { + match self { + Force::Calibrated { + force, + max_possible_force, + altitude_angle, + } => { + let force = match altitude_angle { + Some(altitude_angle) => force / altitude_angle.sin(), + None => *force, + }; + force / max_possible_force + } + Force::Normalized(force) => *force, + } + } +} + /// Hardware-dependent keyboard scan code. pub type ScanCode = u32; diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 42430d01..f88251ea 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -75,6 +75,7 @@ impl EventLoop { android_glue::MotionAction::Cancel => TouchPhase::Cancelled, }, location, + force: None, // TODO id: motion.pointer_id as u64, device_id: DEVICE_ID, }), diff --git a/src/platform_impl/emscripten/mod.rs b/src/platform_impl/emscripten/mod.rs index 9148dab0..2e9c9709 100644 --- a/src/platform_impl/emscripten/mod.rs +++ b/src/platform_impl/emscripten/mod.rs @@ -362,6 +362,7 @@ extern "C" fn touch_callback( phase, id: touch.identifier as u64, location, + force: None, // TODO }), }); } diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 1536c7b2..fcaf395e 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -70,6 +70,24 @@ pub enum UITouchPhase { Cancelled, } +#[derive(Debug, PartialEq)] +#[allow(dead_code)] +#[repr(isize)] +pub enum UIForceTouchCapability { + Unknown = 0, + Unavailable, + Available, +} + +#[derive(Debug, PartialEq)] +#[allow(dead_code)] +#[repr(isize)] +pub enum UITouchType { + Direct = 0, + Indirect, + Pencil, +} + #[repr(C)] #[derive(Debug, Clone)] pub struct UIEdgeInsets { diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 2e385c6f..dda1dbdf 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -6,13 +6,14 @@ use objc::{ }; use crate::{ - event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent}, + event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, platform::ios::MonitorHandleExtIOS, platform_impl::platform::{ app_state::AppState, event_loop, ffi::{ - id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UIRectEdge, UITouchPhase, + id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, + UIRectEdge, UITouchPhase, UITouchType, }, window::PlatformSpecificWindowBuilderAttributes, DeviceId, @@ -218,6 +219,27 @@ unsafe fn get_window_class() -> &'static Class { break; } let location: CGPoint = msg_send![touch, locationInView: nil]; + let touch_type: UITouchType = msg_send![touch, type]; + let trait_collection: id = msg_send![object, traitCollection]; + let touch_capability: UIForceTouchCapability = + msg_send![trait_collection, forceTouchCapability]; + let force = if touch_capability == UIForceTouchCapability::Available { + let force: CGFloat = msg_send![touch, force]; + let max_possible_force: CGFloat = msg_send![touch, maximumPossibleForce]; + let altitude_angle: Option = if touch_type == UITouchType::Pencil { + let angle: CGFloat = msg_send![touch, altitudeAngle]; + Some(angle as _) + } else { + None + }; + Some(Force::Calibrated { + force: force as _, + max_possible_force: max_possible_force as _, + altitude_angle, + }) + } else { + None + }; let touch_id = touch as u64; let phase: UITouchPhase = msg_send![touch, phase]; let phase = match phase { @@ -235,6 +257,7 @@ unsafe fn get_window_class() -> &'static Class { device_id: RootDeviceId(DeviceId { uiscreen }), id: touch_id, location: (location.x as f64, location.y as f64).into(), + force, phase, }), }); diff --git a/src/platform_impl/linux/wayland/touch.rs b/src/platform_impl/linux/wayland/touch.rs index b72be7b8..33644a86 100644 --- a/src/platform_impl/linux/wayland/touch.rs +++ b/src/platform_impl/linux/wayland/touch.rs @@ -39,6 +39,7 @@ pub(crate) fn implement_touch( ), phase: TouchPhase::Started, location: (x, y).into(), + force: None, // TODO id: id as u64, }), wid, @@ -61,6 +62,7 @@ pub(crate) fn implement_touch( ), phase: TouchPhase::Ended, location: pt.location.into(), + force: None, // TODO id: id as u64, }), pt.wid, @@ -78,6 +80,7 @@ pub(crate) fn implement_touch( ), phase: TouchPhase::Moved, location: (x, y).into(), + force: None, // TODO id: id as u64, }), pt.wid, @@ -94,6 +97,7 @@ pub(crate) fn implement_touch( ), phase: TouchPhase::Cancelled, location: pt.location.into(), + force: None, // TODO id: pt.id as u64, }), pt.wid, diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 572889b4..4dea6d27 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -918,6 +918,7 @@ impl EventProcessor { device_id: mkdid(xev.deviceid), phase, location, + force: None, // TODO id: xev.detail as u64, }), }) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3b1052cf..749c0d5d 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1499,6 +1499,7 @@ unsafe extern "system" fn public_window_callback( continue; }, location, + force: None, // TODO id: input.dwID as u64, device_id: DEVICE_ID, }), @@ -1603,6 +1604,7 @@ unsafe extern "system" fn public_window_callback( continue; }, location, + force: None, // TODO id: pointer_info.pointerId as u64, device_id: DEVICE_ID, }),