1#![no_std]
4
5use core::{
6 fmt::Debug,
7 ops::{Add, AddAssign, Sub, SubAssign},
8};
9
10use fixed::{traits::ToFixed, types::U17F15};
11use fixed_macro::types::{I17F15, U17F15};
12
13pub mod traits;
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub struct Touch {
18 pub id: u8,
23
24 pub location: TouchPoint,
26
27 pub phase: Phase,
29
30 pub tool: Tool,
32}
33
34impl Touch {
35 #[must_use]
37 pub fn new(id: u8, location: TouchPoint, phase: Phase, tool: Tool) -> Self {
38 Self {
39 id,
40 location,
41 phase,
42 tool,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum Phase {
50 Started,
52 Moved,
54 Ended,
56 Cancelled,
58 Hovering(Option<u16>),
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65pub enum Tool {
66 Finger,
68 Pointer {
70 button: PointerButton,
72 },
73 Stylus {
75 pressure: Option<u16>,
77 tilt: Option<UnitAngle>,
81 azimuth: Option<UnitAngle>,
85 },
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
90pub enum PointerButton {
91 None,
93 Primary,
95 Secondary,
97 Tertiary,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
105pub struct UnitAngle(fixed::types::U1F15);
106
107impl UnitAngle {
108 #[must_use]
115 pub fn from_pi_radians(value: impl ToFixed) -> Self {
116 UnitAngle(value.wrapping_to_fixed())
117 }
118
119 #[must_use]
123 pub fn from_radians(value: impl ToFixed) -> Self {
124 let fixed_radians = value.to_fixed::<fixed::types::U17F15>();
125 let pi_radians = fixed_radians / U17F15!(3.14159265359);
126 UnitAngle(pi_radians.wrapping_to_fixed())
127 }
128
129 #[must_use]
133 pub fn from_degrees(value: impl ToFixed) -> Self {
134 let fixed_degrees = value.to_fixed::<fixed::types::I17F15>();
135 let radians = fixed_degrees / I17F15!(180);
136 UnitAngle(radians.wrapping_to_fixed())
137 }
138
139 #[must_use]
143 #[inline]
144 pub fn as_pi_radians(&self) -> fixed::types::U1F15 {
145 self.0
146 }
147
148 #[must_use]
149 #[inline]
150 pub fn as_radians_f32(&self) -> f32 {
151 (self.0.to_fixed::<U17F15>() * U17F15!(3.14159265359)).to_num::<f32>()
152 }
153
154 #[must_use]
155 #[inline]
156 pub fn as_degrees_f32(&self) -> f32 {
157 (self.0.to_fixed::<U17F15>() * U17F15!(180.0)).to_num::<f32>()
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
162pub struct TouchPoint {
163 pub x: i32,
164 pub y: i32,
165}
166
167impl TouchPoint {
168 #[must_use]
170 pub fn new(x: impl Into<i32>, y: impl Into<i32>) -> Self {
171 Self {
172 x: x.into(),
173 y: y.into(),
174 }
175 }
176}
177
178impl Add for TouchPoint {
179 type Output = Self;
180
181 fn add(self, rhs: Self) -> Self::Output {
182 TouchPoint {
183 x: self.x + rhs.x,
184 y: self.y + rhs.y,
185 }
186 }
187}
188
189impl AddAssign for TouchPoint {
190 fn add_assign(&mut self, rhs: Self) {
191 self.x += rhs.x;
192 self.y += rhs.y;
193 }
194}
195
196impl Sub for TouchPoint {
197 type Output = Self;
198
199 fn sub(self, rhs: Self) -> Self::Output {
200 TouchPoint {
201 x: self.x - rhs.x,
202 y: self.y - rhs.y,
203 }
204 }
205}
206
207impl SubAssign for TouchPoint {
208 fn sub_assign(&mut self, rhs: Self) {
209 self.x -= rhs.x;
210 self.y -= rhs.y;
211 }
212}
213
214impl core::ops::Neg for TouchPoint {
215 type Output = Self;
216 fn neg(self) -> Self {
217 Self {
218 x: -self.x,
219 y: -self.y,
220 }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use core::f32;
227
228 use super::*;
229
230 #[test]
231 #[expect(clippy::cast_precision_loss)]
232 fn angle_from_pi_radians() {
233 let angle = UnitAngle::from_pi_radians(0.0);
234 assert_eq!(angle.as_pi_radians(), fixed::types::U1F15::from_num(0.0));
235 assert!(angle.as_radians_f32().abs() < 0.00001);
236 assert!(angle.as_degrees_f32().abs() < 0.00001);
237
238 for i in -8..8 {
239 let offset = (i * 2) as f32;
240 let angle = UnitAngle::from_pi_radians(1.0 + offset);
241 assert_eq!(angle.as_pi_radians(), fixed::types::U1F15::from_num(1.0));
242 assert!((angle.as_radians_f32() - 1.0 * f32::consts::PI).abs() < 0.00001);
243 assert!((angle.as_degrees_f32() - 180.0).abs() < 0.00001);
244 }
245 }
246
247 #[test]
248 #[expect(clippy::cast_precision_loss)]
249 fn sweep_360_degrees() {
250 for i in -1080..1080 {
251 let angle = UnitAngle::from_degrees(i);
252
253 let unit_degrees = (i + 360 * 20) % 360;
254 let radians = unit_degrees as f32 * f32::consts::PI / 180.0;
255
256 assert!(
257 (angle.as_degrees_f32() - unit_degrees as f32).abs() < 0.01,
258 "Expected {} to be nearly {unit_degrees}",
259 angle.as_degrees_f32()
260 );
261 assert!(
262 (angle.as_radians_f32() - radians).abs() < 0.001,
263 "Expected {} to be nearly {radians}",
264 angle.as_radians_f32()
265 );
266 }
267 }
268}