1#![no_std]
4
5use core::fmt::Debug;
6
7use fixed::{traits::ToFixed, types::U17F15};
8use fixed_macro::types::{I17F15, U17F15};
9
10pub trait TouchScreen {
12    type Error;
14
15    fn touches(&mut self) -> Result<impl IntoIterator<Item = Touch>, Error<Self::Error>>;
20}
21
22pub trait AsyncTouchScreen {
24    type Error;
26
27    fn touches(
32        &mut self,
33    ) -> impl Future<Output = Result<impl IntoIterator<Item = Touch>, Error<Self::Error>>>;
34}
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct Touch {
39    pub id: u8,
44
45    pub x: u16,
47
48    pub y: u16,
50
51    pub phase: Phase,
53
54    pub tool: Tool,
56
57    pub proximity: Option<u16>,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum Phase {
64    Started,
66    Moved,
68    Ended,
70    Cancelled,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq)]
76pub enum Tool {
77    Finger,
79    Stylus {
81        pressure: Option<u16>,
83        tilt: Option<UnitAngle>,
87        azimuth: Option<UnitAngle>,
91    },
92}
93
94#[derive(Debug, Clone, Copy, PartialEq)]
96pub enum Error<E> {
97    Interface(E),
99    DataCorruption,
101    DeviceError,
103}
104
105impl<E> From<E> for Error<E> {
106    fn from(error: E) -> Self {
107        Error::Interface(error)
108    }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub struct UnitAngle(fixed::types::U1F15);
116
117impl UnitAngle {
118    #[must_use]
125    pub fn from_pi_radians(value: impl ToFixed) -> Self {
126        UnitAngle(value.wrapping_to_fixed())
127    }
128
129    #[must_use]
133    pub fn from_radians(value: impl ToFixed) -> Self {
134        let fixed_radians = value.to_fixed::<fixed::types::U17F15>();
135        let pi_radians = fixed_radians / U17F15!(3.14159265359);
136        UnitAngle(pi_radians.wrapping_to_fixed())
137    }
138
139    #[must_use]
143    pub fn from_degrees(value: impl ToFixed) -> Self {
144        let fixed_degrees = value.to_fixed::<fixed::types::I17F15>();
145        let radians = fixed_degrees / I17F15!(180);
146        UnitAngle(radians.wrapping_to_fixed())
147    }
148
149    #[must_use]
153    pub fn as_pi_radians(&self) -> fixed::types::U1F15 {
154        self.0
155    }
156
157    #[must_use]
158    pub fn as_radians_f32(&self) -> f32 {
159        (self.0.to_fixed::<U17F15>() * U17F15!(3.14159265359)).to_num::<f32>()
160    }
161
162    #[must_use]
163    pub fn as_degrees_f32(&self) -> f32 {
164        (self.0.to_fixed::<U17F15>() * U17F15!(180.0)).to_num::<f32>()
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use core::f32;
171
172    use super::*;
173
174    #[test]
175    #[expect(clippy::cast_precision_loss)]
176    fn angle_from_pi_radians() {
177        let angle = UnitAngle::from_pi_radians(0.0);
178        assert_eq!(angle.as_pi_radians(), fixed::types::U1F15::from_num(0.0));
179        assert!(angle.as_radians_f32().abs() < 0.00001);
180        assert!(angle.as_degrees_f32().abs() < 0.00001);
181
182        for i in -8..8 {
183            let offset = (i * 2) as f32;
184            let angle = UnitAngle::from_pi_radians(1.0 + offset);
185            assert_eq!(angle.as_pi_radians(), fixed::types::U1F15::from_num(1.0));
186            assert!((angle.as_radians_f32() - 1.0 * f32::consts::PI).abs() < 0.00001);
187            assert!((angle.as_degrees_f32() - 180.0).abs() < 0.00001);
188        }
189    }
190
191    #[test]
192    #[expect(clippy::cast_precision_loss)]
193    fn sweep_360_degrees() {
194        for i in -1080..1080 {
195            let angle = UnitAngle::from_degrees(i);
196
197            let unit_degrees = (i + 360 * 20) % 360;
198            let radians = unit_degrees as f32 * f32::consts::PI / 180.0;
199
200            assert!(
201                (angle.as_degrees_f32() - unit_degrees as f32).abs() < 0.01,
202                "Expected {} to be nearly {unit_degrees}",
203                angle.as_degrees_f32()
204            );
205            assert!(
206                (angle.as_radians_f32() - radians).abs() < 0.001,
207                "Expected {}  to be nearly {radians}",
208                angle.as_radians_f32()
209            );
210        }
211    }
212}