TinyGPU
Loading...
Searching...
No Matches
GestureDetector.h
Go to the documentation of this file.
1#pragma once
2#include <math.h>
3#include <stdint.h>
4
5#include "TinyGPU/Emulation.h"
6#include "TinyGPU/Input/TouchDriver.h"
7
8namespace tinygpu {
9
10/// Lifecycle phase of a continuous gesture (drag/pan/scroll/pinch/rotate).
11/// Discrete gestures (tap/double_tap/long_press/swipe_*) always report
12/// kEnded, since they are recognized once, at the moment they complete.
14
15enum class GestureType : uint8_t {
16 kNone,
17 kTap,
19 kLongPress, ///< aka "press"
24 kDrag, ///< continuous move that started over a draggable target
25 kPan, ///< continuous move over the background, not vertical-dominant
26 kScroll, ///< continuous move over the background, vertical-dominant
27 kPinchIn, ///< two fingers moving together (zoom out)
28 kPinchOut, ///< two fingers moving apart (zoom in)
29 kRotate, ///< two-finger rotation
30};
31
32/// Human-readable name for a GestureType, useful for logging.
33inline const char* toString(GestureType type) {
34 switch (type) {
36 return "tap";
38 return "double_tap";
40 return "long_press";
42 return "swipe_left";
44 return "swipe_right";
46 return "swipe_up";
48 return "swipe_down";
50 return "drag";
52 return "pan";
54 return "scroll";
56 return "pinch_in";
58 return "pinch_out";
60 return "rotate";
61 default:
62 return "none";
63 }
64}
65
69 Point point; ///< current touch position
70 Point startPoint; ///< where the touch began
71 int16_t deltaX = 0; ///< cumulative movement since the touch began
72 int16_t deltaY = 0;
73 int16_t stepDeltaX = 0; ///< movement since the previous kChanged event
74 int16_t stepDeltaY = 0;
75 float scale = 1.0f; ///< pinch: current / initial two-finger distance
76 float rotationDegrees = 0.0f; ///< rotate: cumulative angle change
77 uint32_t durationMs = 0; ///< how long the touch has been held
78};
79
80/**
81 * @brief Recognizes touch gestures from a stream of single-point touch
82 * samples, and reports them through a single callback.
83 *
84 * This does not replace TouchDriver - feed it the same driver you'd
85 * otherwise poll directly, once per loop():
86 *
87 * GestureDetector gestures;
88 * gestures.onGesture = handleGesture;
89 * gestures.isDraggable = [](int16_t x, int16_t y) { ... }; // optional
90 * void loop() { gestures.update(touchDriver); }
91 *
92 * isDraggable, if set, is checked against the position where a touch
93 * started; if it returns true the resulting continuous-move gesture is
94 * reported as kDrag, otherwise as kPan/kScroll. Without it, continuous
95 * moves are always reported as kPan/kScroll (never kDrag).
96 *
97 * pinch/rotate require TouchDriver::getSecondPoint() to return a genuine
98 * second simultaneous touch; none of the drivers built into this library
99 * can supply one (see TouchDriver::getSecondPoint), so those two gesture
100 * types will not fire against them.
101 */
103 public:
104 void (*onGesture)(GestureEvent&) = nullptr;
105 bool (*isDraggable)(int16_t x, int16_t y) = nullptr;
106
107 // Tuning knobs, in pixels / milliseconds.
108 uint16_t tapMaxDurationMs = 300;
109 uint16_t tapMaxMovePx = 12;
110 uint16_t doubleTapMaxGapMs = 350;
112 uint16_t swipeMinDistancePx = 40;
113 uint16_t swipeMaxDurationMs = 500;
115
116 /// Call once per loop() with the same driver you'd otherwise poll
117 /// directly via isTouched()/getPoint().
118 void update(TouchDriver& driver) {
119 Point point;
120 const bool touched = driver.isTouched() && driver.getPoint(point);
121 const uint32_t now = millis();
122
123 if (touched) {
124 handleTouched(point, now);
125 Point second;
126 updatePinchRotate(driver.getSecondPoint(second), point, second);
127 } else if (isDown_) {
128 handleReleased(now);
129 }
130 }
131
132 private:
133 bool isDown_ = false;
134 bool dragActive_ = false;
135 bool longPressFired_ = false;
136 bool lastTapValid_ = false;
137 GestureType dragType_ = GestureType::kPan;
138 uint32_t downTime_ = 0;
139 uint32_t lastTapTime_ = 0;
140 Point downPoint_;
141 Point lastPoint_;
142
143 bool secondActive_ = false;
144 float initialPinchDistance_ = 1.0f;
145 float lastPinchDistance_ = 0.0f;
146 float initialPinchAngle_ = 0.0f;
147 float lastPinchAngle_ = 0.0f;
148
149 static int16_t iabs(int16_t v) { return v < 0 ? static_cast<int16_t>(-v) : v; }
150
151 void handleTouched(Point point, uint32_t now) {
152 if (!isDown_) {
153 isDown_ = true;
154 downTime_ = now;
155 downPoint_ = point;
156 lastPoint_ = point;
157 dragActive_ = false;
158 longPressFired_ = false;
159 return;
160 }
161
162 const int16_t dx = static_cast<int16_t>(point.x - downPoint_.x);
163 const int16_t dy = static_cast<int16_t>(point.y - downPoint_.y);
164 const int16_t adx = iabs(dx);
165 const int16_t ady = iabs(dy);
166
167 if (!dragActive_ && (adx > dragStartThresholdPx || ady > dragStartThresholdPx)) {
168 dragActive_ = true;
169 dragType_ = classifyDragType(downPoint_, dx, dy);
170 emitContinuous(dragType_, GesturePhase::kBegan, point, dx, dy, 0, 0, now);
171 } else if (dragActive_) {
172 const int16_t stepDx = static_cast<int16_t>(point.x - lastPoint_.x);
173 const int16_t stepDy = static_cast<int16_t>(point.y - lastPoint_.y);
174 if (stepDx != 0 || stepDy != 0) {
175 emitContinuous(dragType_, GesturePhase::kChanged, point, dx, dy, stepDx, stepDy, now);
176 }
177 }
178
179 if (!dragActive_ && !longPressFired_ && (now - downTime_) >= longPressMinDurationMs) {
180 longPressFired_ = true;
181 emitDiscrete(GestureType::kLongPress, point, dx, dy, now - downTime_);
182 }
183
184 lastPoint_ = point;
185 }
186
187 void handleReleased(uint32_t now) {
188 isDown_ = false;
189 const uint32_t duration = now - downTime_;
190 const int16_t dx = static_cast<int16_t>(lastPoint_.x - downPoint_.x);
191 const int16_t dy = static_cast<int16_t>(lastPoint_.y - downPoint_.y);
192 const int16_t adx = iabs(dx);
193 const int16_t ady = iabs(dy);
194
195 if (dragActive_) {
196 emitContinuous(dragType_, GesturePhase::kEnded, lastPoint_, dx, dy, 0, 0, now);
197 // A drag/pan/scroll that was also fast and far enough is additionally
198 // reported as a swipe, so callers that only care about swipes don't
199 // need to track distance/duration themselves.
200 if (duration <= swipeMaxDurationMs &&
201 (adx >= swipeMinDistancePx || ady >= swipeMinDistancePx)) {
202 emitDiscrete(swipeTypeFor(dx, dy, adx, ady), lastPoint_, dx, dy, duration);
203 }
204 dragActive_ = false;
205 } else if (!longPressFired_ && adx <= tapMaxMovePx && ady <= tapMaxMovePx &&
206 duration <= tapMaxDurationMs) {
207 if (lastTapValid_ && (downTime_ - lastTapTime_) <= doubleTapMaxGapMs) {
208 emitDiscrete(GestureType::kDoubleTap, lastPoint_, dx, dy, duration);
209 lastTapValid_ = false;
210 } else {
211 emitDiscrete(GestureType::kTap, lastPoint_, dx, dy, duration);
212 lastTapValid_ = true;
213 lastTapTime_ = downTime_;
214 }
215 }
216
217 secondActive_ = false;
218 }
219
220 GestureType classifyDragType(Point downPoint, int16_t dx, int16_t dy) const {
221 if (isDraggable != nullptr && isDraggable(downPoint.x, downPoint.y)) {
222 return GestureType::kDrag;
223 }
224 return (iabs(dy) >= iabs(dx)) ? GestureType::kScroll : GestureType::kPan;
225 }
226
227 static GestureType swipeTypeFor(int16_t dx, int16_t dy, int16_t adx, int16_t ady) {
228 if (adx >= ady) {
230 }
232 }
233
234 void emitContinuous(GestureType type, GesturePhase phase, Point point, int16_t dx,
235 int16_t dy, int16_t stepDx, int16_t stepDy, uint32_t now) {
236 if (!onGesture) return;
237 GestureEvent ev;
238 ev.type = type;
239 ev.phase = phase;
240 ev.point = point;
241 ev.startPoint = downPoint_;
242 ev.deltaX = dx;
243 ev.deltaY = dy;
244 ev.stepDeltaX = stepDx;
245 ev.stepDeltaY = stepDy;
246 ev.durationMs = now - downTime_;
247 onGesture(ev);
248 }
249
250 void emitDiscrete(GestureType type, Point point, int16_t dx, int16_t dy,
251 uint32_t duration) {
252 if (!onGesture) return;
253 GestureEvent ev;
254 ev.type = type;
256 ev.point = point;
257 ev.startPoint = downPoint_;
258 ev.deltaX = dx;
259 ev.deltaY = dy;
260 ev.durationMs = duration;
261 onGesture(ev);
262 }
263
264 /// Tracks a second touch point (if the driver ever supplies one) to
265 /// recognize pinch/rotate. See TouchDriver::getSecondPoint.
266 void updatePinchRotate(bool hasSecond, Point first, Point second) {
267 if (!hasSecond) {
268 secondActive_ = false;
269 return;
270 }
271
272 const float dxp = static_cast<float>(second.x - first.x);
273 const float dyp = static_cast<float>(second.y - first.y);
274 const float distance = sqrtf(dxp * dxp + dyp * dyp);
275 const float angle = atan2f(dyp, dxp) * 180.0f / 3.14159265f;
276
277 if (!secondActive_) {
278 secondActive_ = true;
279 initialPinchDistance_ = distance > 0.0f ? distance : 1.0f;
280 lastPinchDistance_ = distance;
281 initialPinchAngle_ = angle;
282 lastPinchAngle_ = angle;
283 return;
284 }
285
286 if (!onGesture) {
287 lastPinchDistance_ = distance;
288 lastPinchAngle_ = angle;
289 return;
290 }
291
292 const float distDelta = distance - lastPinchDistance_;
293 if (distDelta > 0.5f || distDelta < -0.5f) {
294 GestureEvent ev;
295 ev.type = (distDelta > 0.0f) ? GestureType::kPinchOut : GestureType::kPinchIn;
297 ev.point = first;
298 ev.startPoint = downPoint_;
299 ev.scale = distance / initialPinchDistance_;
300 ev.durationMs = millis() - downTime_;
301 onGesture(ev);
302 }
303
304 float angleDelta = angle - lastPinchAngle_;
305 if (angleDelta > 180.0f) angleDelta -= 360.0f;
306 if (angleDelta < -180.0f) angleDelta += 360.0f;
307 if (angleDelta > 1.0f || angleDelta < -1.0f) {
308 GestureEvent ev;
311 ev.point = first;
312 ev.startPoint = downPoint_;
313 ev.rotationDegrees = angle - initialPinchAngle_;
314 ev.durationMs = millis() - downTime_;
315 onGesture(ev);
316 }
317
318 lastPinchDistance_ = distance;
319 lastPinchAngle_ = angle;
320 }
321};
322
323} // namespace tinygpu
Recognizes touch gestures from a stream of single-point touch samples, and reports them through a sin...
Definition: GestureDetector.h:102
uint16_t tapMaxMovePx
Definition: GestureDetector.h:109
uint16_t doubleTapMaxGapMs
Definition: GestureDetector.h:110
void(* onGesture)(GestureEvent &)
Definition: GestureDetector.h:104
uint16_t tapMaxDurationMs
Definition: GestureDetector.h:108
void update(TouchDriver &driver)
Definition: GestureDetector.h:118
uint16_t dragStartThresholdPx
Definition: GestureDetector.h:114
uint16_t swipeMaxDurationMs
Definition: GestureDetector.h:113
uint16_t longPressMinDurationMs
Definition: GestureDetector.h:111
bool(* isDraggable)(int16_t x, int16_t y)
Definition: GestureDetector.h:105
uint16_t swipeMinDistancePx
Definition: GestureDetector.h:112
Base class for touch controllers.
Definition: TouchDriverCommon.h:72
virtual bool isTouched()=0
Return whether the panel currently appears touched.
virtual bool getPoint(Point &outPoint)=0
Read the current touch point.
virtual bool getSecondPoint(Point &outPoint)
Read a second, simultaneous touch point, for multi-touch gestures (pinch/rotate).
Definition: TouchDriverCommon.h:110
Definition: DSIBusESP32.h:19
const char * toString(GestureType type)
Human-readable name for a GestureType, useful for logging.
Definition: GestureDetector.h:33
GestureType
Definition: GestureDetector.h:15
@ kDrag
continuous move that started over a draggable target
@ kLongPress
aka "press"
@ kPan
continuous move over the background, not vertical-dominant
@ kPinchOut
two fingers moving apart (zoom in)
@ kRotate
two-finger rotation
@ kPinchIn
two fingers moving together (zoom out)
@ kScroll
continuous move over the background, vertical-dominant
GesturePhase
Definition: GestureDetector.h:13
unsigned long millis()
Definition: EmulationDesktop.h:43
Definition: GestureDetector.h:66
float scale
pinch: current / initial two-finger distance
Definition: GestureDetector.h:75
int16_t stepDeltaX
movement since the previous kChanged event
Definition: GestureDetector.h:73
Point startPoint
where the touch began
Definition: GestureDetector.h:70
GesturePhase phase
Definition: GestureDetector.h:68
int16_t deltaY
Definition: GestureDetector.h:72
int16_t stepDeltaY
Definition: GestureDetector.h:74
uint32_t durationMs
how long the touch has been held
Definition: GestureDetector.h:77
GestureType type
Definition: GestureDetector.h:67
int16_t deltaX
cumulative movement since the touch began
Definition: GestureDetector.h:71
float rotationDegrees
rotate: cumulative angle change
Definition: GestureDetector.h:76
Point point
current touch position
Definition: GestureDetector.h:69
Touch point.
Definition: TouchDriverCommon.h:32
int16_t x
Definition: TouchDriverCommon.h:33
int16_t y
Definition: TouchDriverCommon.h:34