embedded_touch/
lib.rs

1//! Common traits and types for touch screen drivers (and mice)
2
3#![no_std]
4
5use core::fmt::Debug;
6
7use fixed::{traits::ToFixed, types::U17F15};
8use fixed_macro::types::{I17F15, U17F15};
9
10/// Blocking interface for touch devices.
11pub trait TouchInputDevice {
12    /// Error type from the underlying interface
13    type Error;
14
15    /// Read current touch points, blocking until data is available
16    ///
17    /// Returns an iterator of touch points currently detected.
18    /// Drivers must track touch IDs across calls to maintain correct phase information.
19    fn touches(&mut self) -> Result<impl IntoIterator<Item = Touch>, Error<Self::Error>>;
20}
21
22/// Async interface for event-driven operation of touch devices
23pub trait AsyncTouchInputDevice {
24    /// Error type from the underlying interface
25    type Error;
26
27    /// Asynchronously wait until touch points are available
28    ///
29    /// Returns an iterator of touch points currently detected.
30    /// Drivers must track touch IDs across calls to maintain correct phase information.
31    fn touches(
32        &mut self,
33    ) -> impl Future<Output = Result<impl IntoIterator<Item = Touch>, Error<Self::Error>>>;
34}
35
36/// Represents a single touch point on the screen
37#[derive(Debug, Clone, PartialEq)]
38pub struct Touch {
39    /// Unique ID for tracking this touch point across frames
40    ///
41    /// The ID should remain stable for a given finger/stylus while it remains in contact
42    /// but can be reused for new touches after sending [`Phase::Ended`] or [`Phase::Cancelled`]
43    pub id: u8,
44
45    /// X coordinate in screen pixels
46    pub x: u16,
47
48    /// Y coordinate in screen pixels
49    pub y: u16,
50
51    /// Current phase of this touch interaction
52    pub phase: Phase,
53
54    /// The tool used for this touch point
55    pub tool: Tool,
56
57    /// Optional proximity distance (implementation-specific units)
58    pub proximity: Option<u16>,
59}
60
61/// Phase of a touch interaction
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum Phase {
64    /// Touch just started
65    Started,
66    /// Touch moved from previous position
67    Moved,
68    /// Touch ended normally
69    Ended,
70    /// Touch was cancelled (e.g., palm rejection triggered)
71    Cancelled,
72    /// Touch is hovering above the screen without contact
73    Hovering,
74}
75
76/// Tool/instrument used for touch interaction
77#[derive(Debug, Clone, Copy, PartialEq)]
78pub enum Tool {
79    /// Finger or unknown tool
80    Finger,
81    /// Virtual pointing device (e.g., mouse cursor)
82    Pointer {
83        /// The button pressed on the virtual pointer
84        button: PointerButton,
85    },
86    /// Passive or active stylus
87    Stylus {
88        /// Pressure, in grams
89        pressure: Option<u16>,
90        /// Tilt angle
91        ///
92        /// 0 degrees is a vector normal to the screen, and 90 degrees is parallel to the screen.
93        tilt: Option<UnitAngle>,
94        /// Azimuth angle
95        ///
96        /// 0 degrees points up to the top of the screen in its default orientation.
97        azimuth: Option<UnitAngle>,
98    },
99}
100
101/// The button state of a virtual pointer device
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum PointerButton {
104    /// No button pressed, e.g., mouse hover state
105    None,
106    /// Primary mouse button, typically left
107    Primary,
108    /// Secondary mouse button, typically right
109    Secondary,
110    /// Tertiary mouse button, typically middle or wheel
111    Tertiary,
112}
113
114/// Error types for touch operations
115#[derive(Debug, Clone, Copy, PartialEq)]
116pub enum Error<E> {
117    /// I2C/SPI communication error
118    Interface(E),
119    /// Data corruption detected (e.g., checksum failure)
120    DataCorruption,
121    /// Device not responding or not initialized
122    DeviceError,
123}
124
125impl<E> From<E> for Error<E> {
126    fn from(error: E) -> Self {
127        Error::Interface(error)
128    }
129}
130
131/// An angle in the range [0, 2π) radians
132///
133/// The angle is stored as a [`fixed::types::U1F15`]
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub struct UnitAngle(fixed::types::U1F15);
136
137impl UnitAngle {
138    /// Create a new angle from an angle in π * radians.
139    ///
140    /// Angles outside the range [0, 2) are wrapped.
141    ///
142    /// This method does not result in loss of precision if the argument is
143    /// [`fixed::types::U1F15`]
144    #[must_use]
145    pub fn from_pi_radians(value: impl ToFixed) -> Self {
146        UnitAngle(value.wrapping_to_fixed())
147    }
148
149    /// Create a new angle from an angle in radians
150    ///
151    /// Angles outside the range [0, 2π) are wrapped.
152    #[must_use]
153    pub fn from_radians(value: impl ToFixed) -> Self {
154        let fixed_radians = value.to_fixed::<fixed::types::U17F15>();
155        let pi_radians = fixed_radians / U17F15!(3.14159265359);
156        UnitAngle(pi_radians.wrapping_to_fixed())
157    }
158
159    /// Create a new angle from an angle in degrees
160    ///
161    /// Angles outside the range [0, 360) are wrapped.
162    #[must_use]
163    pub fn from_degrees(value: impl ToFixed) -> Self {
164        let fixed_degrees = value.to_fixed::<fixed::types::I17F15>();
165        let radians = fixed_degrees / I17F15!(180);
166        UnitAngle(radians.wrapping_to_fixed())
167    }
168
169    /// Returns the angle in π radians, in the range [0, 2)
170    ///
171    /// This method does not result in loss of precision from the original value.
172    #[must_use]
173    pub fn as_pi_radians(&self) -> fixed::types::U1F15 {
174        self.0
175    }
176
177    #[must_use]
178    pub fn as_radians_f32(&self) -> f32 {
179        (self.0.to_fixed::<U17F15>() * U17F15!(3.14159265359)).to_num::<f32>()
180    }
181
182    #[must_use]
183    pub fn as_degrees_f32(&self) -> f32 {
184        (self.0.to_fixed::<U17F15>() * U17F15!(180.0)).to_num::<f32>()
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use core::f32;
191
192    use super::*;
193
194    #[test]
195    #[expect(clippy::cast_precision_loss)]
196    fn angle_from_pi_radians() {
197        let angle = UnitAngle::from_pi_radians(0.0);
198        assert_eq!(angle.as_pi_radians(), fixed::types::U1F15::from_num(0.0));
199        assert!(angle.as_radians_f32().abs() < 0.00001);
200        assert!(angle.as_degrees_f32().abs() < 0.00001);
201
202        for i in -8..8 {
203            let offset = (i * 2) as f32;
204            let angle = UnitAngle::from_pi_radians(1.0 + offset);
205            assert_eq!(angle.as_pi_radians(), fixed::types::U1F15::from_num(1.0));
206            assert!((angle.as_radians_f32() - 1.0 * f32::consts::PI).abs() < 0.00001);
207            assert!((angle.as_degrees_f32() - 180.0).abs() < 0.00001);
208        }
209    }
210
211    #[test]
212    #[expect(clippy::cast_precision_loss)]
213    fn sweep_360_degrees() {
214        for i in -1080..1080 {
215            let angle = UnitAngle::from_degrees(i);
216
217            let unit_degrees = (i + 360 * 20) % 360;
218            let radians = unit_degrees as f32 * f32::consts::PI / 180.0;
219
220            assert!(
221                (angle.as_degrees_f32() - unit_degrees as f32).abs() < 0.01,
222                "Expected {} to be nearly {unit_degrees}",
223                angle.as_degrees_f32()
224            );
225            assert!(
226                (angle.as_radians_f32() - radians).abs() < 0.001,
227                "Expected {}  to be nearly {radians}",
228                angle.as_radians_f32()
229            );
230        }
231    }
232}