1use embedded_graphics::{geometry::Point, primitives::Rectangle};
4use embedded_hal::i2c::I2c;
5
6use crate::{devices, display::DisplayOrientation};
7
8const REG_DEVICE_MODE: u8 = 0x00;
9const REG_GESTURE_ID: u8 = 0x01;
10
11#[cfg_attr(feature = "defmt", derive(defmt::Format))]
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum TouchPhase {
14 Down,
15 Move,
16 Up,
17}
18
19#[cfg_attr(feature = "defmt", derive(defmt::Format))]
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum Gesture {
22 None,
23 MoveUp,
24 MoveRight,
25 MoveDown,
26 MoveLeft,
27 ZoomIn,
28 ZoomOut,
29 Unknown(u8),
30}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct TouchEvent {
34 pub id: u8,
35 pub point: Point,
36 pub phase: TouchPhase,
37}
38
39impl TouchEvent {
40 pub fn hits(self, area: Rectangle) -> bool {
41 area.contains(self.point)
42 }
43}
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub struct TouchReport {
47 pub gesture: Gesture,
48 pub events: [Option<TouchEvent>; 2],
49}
50
51impl TouchReport {
52 pub const fn empty() -> Self {
53 Self {
54 gesture: Gesture::None,
55 events: [None, None],
56 }
57 }
58}
59
60pub struct Ft6336u<I2C> {
61 i2c: I2C,
62 address: u8,
63 orientation: DisplayOrientation,
64 width: u16,
65 height: u16,
66}
67
68impl<I2C> Ft6336u<I2C> {
69 pub const fn new(i2c: I2C) -> Self {
70 Self {
71 i2c,
72 address: devices::i2c::FT6336U_TOUCH,
73 orientation: DisplayOrientation::Landscape,
74 width: devices::display::WIDTH,
75 height: devices::display::HEIGHT,
76 }
77 }
78
79 pub fn set_orientation(&mut self, orientation: DisplayOrientation) {
80 self.orientation = orientation;
81 }
82
83 pub fn release(self) -> I2C {
84 self.i2c
85 }
86}
87
88impl<I2C, Error> Ft6336u<I2C>
89where
90 I2C: I2c<Error = Error>,
91{
92 pub fn init(&mut self) -> Result<(), Error> {
93 self.write_register(REG_DEVICE_MODE, 0x00)
94 }
95
96 pub fn read_report(&mut self) -> Result<TouchReport, Error> {
97 let mut data = [0u8; 12];
98 self.i2c
99 .write_read(self.address, &[REG_GESTURE_ID], &mut data)?;
100 let gesture = decode_gesture(data[0]);
101 let count = (data[1] & 0x0F).min(2);
102 let mut report = TouchReport {
103 gesture,
104 events: [None, None],
105 };
106 if count >= 1 {
107 report.events[0] = decode_touch(&data[2..8], self.orientation, self.width, self.height);
108 }
109 if count >= 2 {
110 report.events[1] =
111 decode_touch(&data[8..12], self.orientation, self.width, self.height);
112 }
113 Ok(report)
114 }
115
116 pub fn read_register(&mut self, register: u8) -> Result<u8, Error> {
117 let mut value = [0u8];
118 self.i2c.write_read(self.address, &[register], &mut value)?;
119 Ok(value[0])
120 }
121
122 pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), Error> {
123 self.i2c.write(self.address, &[register, value])
124 }
125}
126
127fn decode_touch(
128 data: &[u8],
129 orientation: DisplayOrientation,
130 width: u16,
131 height: u16,
132) -> Option<TouchEvent> {
133 if data.len() < 4 {
134 return None;
135 }
136 let event = (data[0] >> 6) & 0x03;
137 let x = (u16::from(data[0] & 0x0F) << 8) | u16::from(data[1]);
138 let y = (u16::from(data[2] & 0x0F) << 8) | u16::from(data[3]);
139 let id = data.get(2).copied().unwrap_or(0) >> 4;
140 let phase = match event {
141 0 => TouchPhase::Down,
142 1 => TouchPhase::Up,
143 2 => TouchPhase::Move,
144 _ => TouchPhase::Move,
145 };
146 Some(TouchEvent {
147 id,
148 point: map_touch_point(
149 Point::new(i32::from(x), i32::from(y)),
150 orientation,
151 width,
152 height,
153 ),
154 phase,
155 })
156}
157
158const fn decode_gesture(value: u8) -> Gesture {
159 match value {
160 0x00 => Gesture::None,
161 0x10 => Gesture::MoveUp,
162 0x14 => Gesture::MoveRight,
163 0x18 => Gesture::MoveDown,
164 0x1C => Gesture::MoveLeft,
165 0x48 => Gesture::ZoomIn,
166 0x49 => Gesture::ZoomOut,
167 other => Gesture::Unknown(other),
168 }
169}
170
171pub fn map_touch_point(
172 point: Point,
173 orientation: DisplayOrientation,
174 width: u16,
175 height: u16,
176) -> Point {
177 let w = i32::from(width);
178 let h = i32::from(height);
179 match orientation {
180 DisplayOrientation::Landscape => point,
181 DisplayOrientation::LandscapeInverted => Point::new(w - 1 - point.x, h - 1 - point.y),
182 DisplayOrientation::Portrait => Point::new(point.y, w - 1 - point.x),
183 DisplayOrientation::PortraitInverted => Point::new(h - 1 - point.y, point.x),
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use embedded_graphics::prelude::Size;
191
192 #[test]
193 fn maps_touch_for_all_rotations() {
194 let p = Point::new(10, 20);
195 assert_eq!(
196 map_touch_point(p, DisplayOrientation::Landscape, 320, 240),
197 p
198 );
199 assert_eq!(
200 map_touch_point(p, DisplayOrientation::LandscapeInverted, 320, 240),
201 Point::new(309, 219)
202 );
203 assert_eq!(
204 map_touch_point(p, DisplayOrientation::Portrait, 320, 240),
205 Point::new(20, 309)
206 );
207 assert_eq!(
208 map_touch_point(p, DisplayOrientation::PortraitInverted, 320, 240),
209 Point::new(219, 10)
210 );
211 }
212
213 #[test]
214 fn hit_tests_event() {
215 let event = TouchEvent {
216 id: 0,
217 point: Point::new(12, 14),
218 phase: TouchPhase::Down,
219 };
220 assert!(event.hits(Rectangle::new(Point::new(10, 10), Size::new(20, 20))));
221 }
222}