1 /******************************************************************
2 * @file lv_indev_gesture.c
3 *
4 * Recognize gestures that consist of multiple touch events
5 *
6 * Copyright (c) 2024 EDGEMTech Ltd
7 *
8 * Author EDGEMTech Ltd. (erik.tagirov@edgemtech.ch)
9 *
10 ******************************************************************/
11
12 /********************
13 * INCLUDES
14 ********************/
15
16 #include "lv_indev_private.h"
17 #include "../misc/lv_event_private.h"
18
19 #if LV_USE_GESTURE_RECOGNITION
20
21 #include <math.h>
22 #include "lv_indev_gesture.h"
23 #include "lv_indev_gesture_private.h"
24
25 /********************
26 * DEFINES
27 ********************/
28
29 #define LV_GESTURE_PINCH_DOWN_THRESHOLD 0.75f /* Default value - start sending events when reached */
30 #define LV_GESTURE_PINCH_UP_THRESHOLD 1.5f /* Default value - start sending events when reached */
31 #define LV_GESTURE_PINCH_MAX_INITIAL_SCALE 2.5f /* Default value */
32
33
34 /********************
35 * TYPEDEFS
36 ********************/
37
38 /********************
39 * STATIC PROTOTYPES
40 ********************/
41
42 static lv_indev_gesture_t * init_gesture_info(void);
43 static lv_indev_gesture_motion_t * get_motion(uint8_t id, lv_indev_gesture_t * info);
44 static int8_t get_motion_idx(uint8_t id, lv_indev_gesture_t * info);
45 static void process_touch_event(lv_indev_touch_data_t * touch, lv_indev_gesture_t * info);
46 static void gesture_update_center_point(lv_indev_gesture_t * gesture, int touch_points_nb);
47 static void gesture_calculate_factors(lv_indev_gesture_t * gesture, int touch_points_nb);
48 static void reset_recognizer(lv_indev_gesture_recognizer_t * recognizer);
49 static lv_indev_gesture_recognizer_t * lv_indev_get_gesture_recognizer(lv_event_t * gesture_event);
50
51 /********************
52 * STATIC VARIABLES
53 ********************/
54
55 /********************
56 * MACROS
57 ********************/
58
59 /********************
60 * GLOBAL FUNCTIONS
61 ********************/
62
lv_indev_set_pinch_up_threshold(lv_indev_gesture_recognizer_t * recognizer,float threshold)63 void lv_indev_set_pinch_up_threshold(lv_indev_gesture_recognizer_t * recognizer, float threshold)
64 {
65 /* A up threshold MUST always be bigger than 1 */
66 LV_ASSERT(threshold > 1.0f);
67
68 if(recognizer->config == NULL) {
69
70 recognizer->config = lv_malloc(sizeof(lv_indev_gesture_configuration_t));
71 LV_ASSERT(recognizer->config != NULL);
72 recognizer->config->pinch_down_threshold = LV_GESTURE_PINCH_DOWN_THRESHOLD;
73 }
74
75 recognizer->config->pinch_up_threshold = threshold;
76 }
77
lv_indev_set_pinch_down_threshold(lv_indev_gesture_recognizer_t * recognizer,float threshold)78 void lv_indev_set_pinch_down_threshold(lv_indev_gesture_recognizer_t * recognizer, float threshold)
79 {
80 /* A down threshold MUST always be smaller than 1 */
81 LV_ASSERT(threshold < 1.0f);
82
83 if(recognizer->config == NULL) {
84
85 recognizer->config = lv_malloc(sizeof(lv_indev_gesture_configuration_t));
86 LV_ASSERT(recognizer->config != NULL);
87 recognizer->config->pinch_up_threshold = LV_GESTURE_PINCH_UP_THRESHOLD;
88 }
89
90 recognizer->config->pinch_down_threshold = threshold;
91 }
92
lv_indev_get_gesture_primary_point(lv_indev_gesture_recognizer_t * recognizer,lv_point_t * point)93 void lv_indev_get_gesture_primary_point(lv_indev_gesture_recognizer_t * recognizer, lv_point_t * point)
94 {
95 if(recognizer->info->motions[0].finger != -1) {
96 point->x = recognizer->info->motions[0].point.x;
97 point->y = recognizer->info->motions[0].point.y;
98 return;
99 }
100
101 /* There are currently no active contact points */
102 point->x = 0;
103 point->y = 0;
104 }
105
lv_indev_recognizer_is_active(lv_indev_gesture_recognizer_t * recognizer)106 bool lv_indev_recognizer_is_active(lv_indev_gesture_recognizer_t * recognizer)
107 {
108 if(recognizer->state == LV_INDEV_GESTURE_STATE_ENDED ||
109 recognizer->info->finger_cnt == 0) {
110 return false;
111 }
112
113 return true;
114 }
115
lv_event_get_pinch_scale(lv_event_t * gesture_event)116 float lv_event_get_pinch_scale(lv_event_t * gesture_event)
117 {
118 lv_indev_gesture_recognizer_t * recognizer;
119
120 if((recognizer = lv_indev_get_gesture_recognizer(gesture_event)) == NULL) {
121 return 0.0f;
122 }
123
124 return recognizer->scale;
125 }
126
lv_indev_get_gesture_center_point(lv_indev_gesture_recognizer_t * recognizer,lv_point_t * point)127 void lv_indev_get_gesture_center_point(lv_indev_gesture_recognizer_t * recognizer, lv_point_t * point)
128 {
129 if(lv_indev_recognizer_is_active(recognizer) == false) {
130 point->x = 0;
131 point->y = 0;
132 return;
133 }
134
135 point->x = recognizer->info->center.x;
136 point->y = recognizer->info->center.y;
137
138 }
139
lv_event_get_gesture_state(lv_event_t * gesture_event)140 lv_indev_gesture_state_t lv_event_get_gesture_state(lv_event_t * gesture_event)
141 {
142 lv_indev_gesture_recognizer_t * recognizer;
143
144 if((recognizer = lv_indev_get_gesture_recognizer(gesture_event)) == NULL) {
145 return LV_INDEV_GESTURE_STATE_NONE;
146 }
147
148 return recognizer->state;
149 }
150
151
lv_indev_set_gesture_data(lv_indev_data_t * data,lv_indev_gesture_recognizer_t * recognizer)152 void lv_indev_set_gesture_data(lv_indev_data_t * data, lv_indev_gesture_recognizer_t * recognizer)
153 {
154 bool is_active;
155 lv_point_t cur_pnt;
156
157 if(recognizer == NULL) return;
158
159 /* If there is a single contact point use its coords,
160 * when there are no contact points it's set to 0,0
161 *
162 * Note: If a gesture was detected, the primary point is overwritten below
163 */
164
165 lv_indev_get_gesture_primary_point(recognizer, &cur_pnt);
166 data->point.x = cur_pnt.x;
167 data->point.y = cur_pnt.y;
168
169 data->gesture_type = LV_INDEV_GESTURE_NONE;
170 data->gesture_data = NULL;
171
172 /* The call below returns false if there are no active contact points */
173 /* - OR when the gesture has ended, false is considered as a RELEASED state */
174 is_active = lv_indev_recognizer_is_active(recognizer);
175
176 if(is_active == false) {
177 data->state = LV_INDEV_STATE_RELEASED;
178
179 }
180 else {
181 data->state = LV_INDEV_STATE_PRESSED;
182 }
183
184 switch(recognizer->state) {
185 case LV_INDEV_GESTURE_STATE_RECOGNIZED:
186 lv_indev_get_gesture_center_point(recognizer, &cur_pnt);
187 data->point.x = cur_pnt.x;
188 data->point.y = cur_pnt.y;
189 data->gesture_type = LV_INDEV_GESTURE_PINCH;
190 data->gesture_data = (void *) recognizer;
191 break;
192
193 case LV_INDEV_GESTURE_STATE_ENDED:
194 data->gesture_type = LV_INDEV_GESTURE_PINCH;
195 data->gesture_data = (void *) recognizer;
196 break;
197
198 default:
199 break;
200 }
201 }
202
203
lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer,lv_indev_touch_data_t * touches,uint16_t touch_cnt)204 void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, lv_indev_touch_data_t * touches,
205 uint16_t touch_cnt)
206 {
207 lv_indev_touch_data_t * touch;
208 lv_indev_gesture_recognizer_t * r = recognizer;
209 uint8_t i;
210
211 if(r->info == NULL) {
212 LV_LOG_TRACE("init gesture info");
213 r->info = init_gesture_info();
214 }
215
216 if(r->config == NULL) {
217 LV_LOG_TRACE("init gesture configuration - set defaults");
218 r->config = lv_malloc(sizeof(lv_indev_gesture_configuration_t));
219
220 LV_ASSERT(r->config != NULL);
221
222 r->config->pinch_up_threshold = LV_GESTURE_PINCH_UP_THRESHOLD;
223 r->config->pinch_down_threshold = LV_GESTURE_PINCH_DOWN_THRESHOLD;
224 }
225
226 /* Process collected touch events */
227 for(i = 0; i < touch_cnt; i++) {
228
229 touch = touches;
230 process_touch_event(touch, r->info);
231 touches++;
232
233 LV_LOG_TRACE("processed touch ev: %d finger id: %d state: %d x: %d y: %d finger_cnt: %d",
234 i, touch->id, touch->state, touch->point.x, touch->point.y, r->info->finger_cnt);
235 }
236
237 LV_LOG_TRACE("Current finger count: %d state: %d", r->info->finger_cnt, r->state);
238
239
240 if(r->info->finger_cnt == 2) {
241
242 switch(r->state) {
243 case LV_INDEV_GESTURE_STATE_ENDED:
244 case LV_INDEV_GESTURE_STATE_CANCELED:
245 case LV_INDEV_GESTURE_STATE_NONE:
246
247 /* 2 fingers down - potential pinch or swipe */
248 reset_recognizer(recognizer);
249 gesture_update_center_point(r->info, 2);
250 r->state = LV_INDEV_GESTURE_STATE_ONGOING;
251 break;
252
253 case LV_INDEV_GESTURE_STATE_ONGOING:
254 case LV_INDEV_GESTURE_STATE_RECOGNIZED:
255
256 /* It's an ongoing pinch gesture - update the factors */
257 gesture_calculate_factors(r->info, 2);
258
259 if(r->info->scale > LV_GESTURE_PINCH_MAX_INITIAL_SCALE &&
260 r->state == LV_INDEV_GESTURE_STATE_ONGOING) {
261 r->state = LV_INDEV_GESTURE_STATE_CANCELED;
262 break;
263 }
264
265 LV_ASSERT(r->config != NULL);
266
267 if(r->info->scale > r->config->pinch_up_threshold ||
268 r->info->scale < r->config->pinch_down_threshold) {
269
270 if(r->info->scale > 1.0f) {
271 r->scale = r->info->scale - (r->config->pinch_up_threshold - 1.0f);
272
273 }
274 else if(r->info->scale < 1.0f) {
275
276 r->scale = r->info->scale + (1.0f - r->config->pinch_down_threshold);
277 }
278
279 r->type = LV_INDEV_GESTURE_PINCH;
280 r->state = LV_INDEV_GESTURE_STATE_RECOGNIZED;
281 }
282 break;
283
284 default:
285 LV_ASSERT_MSG(true, "invalid gesture recognizer state");
286 }
287
288 }
289 else {
290
291 switch(r->state) {
292 case LV_INDEV_GESTURE_STATE_RECOGNIZED:
293 /* Gesture has ended */
294 r->state = LV_INDEV_GESTURE_STATE_ENDED;
295 r->type = LV_INDEV_GESTURE_PINCH;
296 break;
297
298 case LV_INDEV_GESTURE_STATE_ONGOING:
299 /* User lifted a finger before reaching threshold */
300 r->state = LV_INDEV_GESTURE_STATE_CANCELED;
301 reset_recognizer(r);
302 break;
303
304 case LV_INDEV_GESTURE_STATE_CANCELED:
305 case LV_INDEV_GESTURE_STATE_ENDED:
306 reset_recognizer(r);
307 break;
308
309 default:
310 LV_ASSERT_MSG(true, "invalid gesture recognizer state");
311 }
312 }
313 }
314
315 /********************
316 * STATIC FUNCTIONS
317 ********************/
318
319 /**
320 * Get the gesture recognizer associated to the event
321 * @param gesture_event an LV_GESTURE_EVENT event
322 * @return A pointer to the gesture recognizer that emitted the event
323 */
lv_indev_get_gesture_recognizer(lv_event_t * gesture_event)324 lv_indev_gesture_recognizer_t * lv_indev_get_gesture_recognizer(lv_event_t * gesture_event)
325 {
326 lv_indev_t * indev;
327
328 if(gesture_event == NULL || gesture_event->param == NULL) return NULL;
329
330 indev = (lv_indev_t *) gesture_event->param;
331
332 if(indev == NULL || indev->gesture_data == NULL) return NULL;
333
334 return (lv_indev_gesture_recognizer_t *) indev->gesture_data;
335 }
336
337 /**
338 * Resets a gesture recognizer, motion descriptors are preserved
339 * @param recognizer a pointer to the recognizer to reset
340 */
reset_recognizer(lv_indev_gesture_recognizer_t * recognizer)341 static void reset_recognizer(lv_indev_gesture_recognizer_t * recognizer)
342 {
343 size_t motion_arr_sz;
344 lv_indev_gesture_t * info;
345 lv_indev_gesture_configuration_t * conf;
346
347 if(recognizer == NULL) return;
348
349 info = recognizer->info;
350 conf = recognizer->config;
351
352 /* Set everything to zero but preserve the motion descriptors,
353 * which are located at the start of the lv_indev_gesture_t struct */
354 motion_arr_sz = sizeof(lv_indev_gesture_motion_t) * LV_GESTURE_MAX_POINTS;
355 lv_memset(info + motion_arr_sz, 0, sizeof(lv_indev_gesture_t) - motion_arr_sz);
356 lv_memset(recognizer, 0, sizeof(lv_indev_gesture_recognizer_t));
357
358 recognizer->scale = info->scale = 1;
359 recognizer->info = info;
360 recognizer->config = conf;
361 }
362
363 /**
364 * Initializes a motion descriptors used with the recognizer(s)
365 * @return a pointer to gesture descriptor
366 */
init_gesture_info(void)367 static lv_indev_gesture_t * init_gesture_info(void)
368 {
369 lv_indev_gesture_t * info;
370 uint8_t i;
371
372 info = lv_malloc(sizeof(lv_indev_gesture_t));
373 LV_ASSERT_NULL(info);
374
375 lv_memset(info, 0, sizeof(lv_indev_gesture_t));
376 info->scale = 1;
377
378 for(i = 0; i < LV_GESTURE_MAX_POINTS; i++) {
379 info->motions[i].finger = -1;
380 }
381
382 return info;
383 }
384
385 /**
386 * Obtains the contact point motion descriptor with id
387 * @param id the id of the contact point
388 * @param info a pointer to the gesture descriptor that stores the motion of each contact point
389 * @return a pointer to the motion descriptor or NULL if not found
390 */
get_motion(uint8_t id,lv_indev_gesture_t * info)391 static lv_indev_gesture_motion_t * get_motion(uint8_t id, lv_indev_gesture_t * info)
392 {
393 uint8_t i;
394
395 for(i = 0; i < LV_GESTURE_MAX_POINTS; i++) {
396 if(info->motions[i].finger == id) {
397 return &info->motions[i];
398 }
399 }
400
401 return NULL;
402
403 }
404
405 /**
406 * Obtains the index of the contact point motion descriptor
407 * @param id the id of the contact point
408 * @param info a pointer to the gesture descriptor that stores the motion of each contact point
409 * @return the index of the motion descriptor or -1 if not found
410 */
get_motion_idx(uint8_t id,lv_indev_gesture_t * info)411 static int8_t get_motion_idx(uint8_t id, lv_indev_gesture_t * info)
412 {
413 uint8_t i;
414
415 for(i = 0; i < LV_GESTURE_MAX_POINTS; i++) {
416 if(info->motions[i].finger == id) {
417 return i;
418 }
419 }
420
421 return -1;
422
423 }
424
425 /**
426 * Update the motion descriptors of a gesture
427 * @param touch a pointer to a touch data structure
428 * @param info a pointer to a gesture descriptor
429 */
process_touch_event(lv_indev_touch_data_t * touch,lv_indev_gesture_t * info)430 static void process_touch_event(lv_indev_touch_data_t * touch, lv_indev_gesture_t * info)
431 {
432 lv_indev_gesture_t * g = info;
433 lv_indev_gesture_motion_t * motion;
434 int8_t motion_idx;
435 uint8_t len;
436
437 motion_idx = get_motion_idx(touch->id, g);
438
439 if(motion_idx == -1 && touch->state == LV_INDEV_STATE_PRESSED) {
440
441 if(g->finger_cnt == LV_GESTURE_MAX_POINTS) {
442 /* Skip touch */
443 return;
444 }
445
446 /* New touch point id */
447 motion = &g->motions[g->finger_cnt];
448 motion->start_point.x = touch->point.x;
449 motion->start_point.y = touch->point.y;
450 motion->point.x = touch->point.x;
451 motion->point.y = touch->point.y;
452 motion->finger = touch->id;
453 motion->state = touch->state;
454
455 g->finger_cnt++;
456
457 }
458 else if(motion_idx >= 0 && touch->state == LV_INDEV_STATE_RELEASED) {
459
460 if(motion_idx == g->finger_cnt - 1) {
461
462 /* Mark last item as un-used */
463 motion = get_motion(touch->id, g);
464 motion->finger = -1;
465 motion->state = touch->state;
466
467 }
468 else {
469
470 /* Move back by one */
471 len = (g->finger_cnt - 1) - motion_idx;
472 lv_memmove(g->motions + motion_idx,
473 g->motions + motion_idx + 1,
474 sizeof(lv_indev_gesture_motion_t) * len);
475
476 g->motions[g->finger_cnt - 1].finger = -1;
477
478 LV_ASSERT(g->motions[motion_idx + 1].finger == -1);
479
480 }
481 g->finger_cnt--;
482
483 }
484 else if(motion_idx >= 0) {
485
486 motion = get_motion(touch->id, g);
487 motion->point.x = touch->point.x;
488 motion->point.y = touch->point.y;
489 motion->state = touch->state;
490
491 }
492 else {
493 LV_LOG_TRACE("Ignore extra touch id: %d", touch->id);
494 }
495 }
496
497 /**
498 * Calculate the center point of a gesture, called when there
499 * is a probability for the gesture to occur
500 * @param touch a pointer to a touch data structure
501 * @param touch_points_nb The number of contact point to take into account
502 */
gesture_update_center_point(lv_indev_gesture_t * gesture,int touch_points_nb)503 static void gesture_update_center_point(lv_indev_gesture_t * gesture, int touch_points_nb)
504 {
505 lv_indev_gesture_motion_t * motion;
506 lv_indev_gesture_t * g = gesture;
507 int32_t x = 0;
508 int32_t y = 0;
509 uint8_t i;
510 float scale_factor = 0.0f;
511 float delta_x[LV_GESTURE_MAX_POINTS] = {0.0f};
512 float delta_y[LV_GESTURE_MAX_POINTS] = {0.0f};
513 uint8_t touch_cnt = 0;
514 x = y = 0;
515
516 g->p_scale = g->scale;
517 g->p_delta_x = g->delta_x;
518 g->p_delta_y = g->delta_y;
519 g->p_rotation = g->rotation;
520
521 for(i = 0; i < touch_points_nb; i++) {
522 motion = &g->motions[i];
523
524 if(motion->finger >= 0) {
525 x += motion->point.x;
526 y += motion->point.y;
527 touch_cnt++;
528
529 }
530 else {
531 break;
532 }
533 }
534
535 g->center.x = x / touch_cnt;
536 g->center.y = y / touch_cnt;
537
538 for(i = 0; i < touch_points_nb; i++) {
539
540 motion = &g->motions[i];
541 if(motion->finger >= 0) {
542 delta_x[i] = motion->point.x - g->center.x;
543 delta_y[i] = motion->point.y - g->center.y;
544 scale_factor += (delta_x[i] * delta_x[i]) + (delta_y[i] * delta_y[i]);
545 }
546 }
547 for(i = 0; i < touch_points_nb; i++) {
548
549 motion = &g->motions[i];
550 if(motion->finger >= 0) {
551 g->scale_factors_x[i] = delta_x[i] / scale_factor;
552 g->scale_factors_y[i] = delta_y[i] / scale_factor;
553 }
554 }
555 }
556
557 /**
558 * Calculate the scale, translation and rotation of a gesture, called when
559 * the gesture has been recognized
560 * @param gesture a pointer to the gesture descriptor
561 * @param touch_points_nb the number of contact points to take into account
562 */
gesture_calculate_factors(lv_indev_gesture_t * gesture,int touch_points_nb)563 static void gesture_calculate_factors(lv_indev_gesture_t * gesture, int touch_points_nb)
564 {
565 lv_indev_gesture_motion_t * motion;
566 lv_indev_gesture_t * g = gesture;
567 float center_x = 0;
568 float center_y = 0;
569 float a = 0;
570 float b = 0;
571 float d_x;
572 float d_y;
573 int8_t i;
574 int8_t touch_cnt = 0;
575
576 for(i = 0; i < touch_points_nb; i++) {
577 motion = &g->motions[i];
578
579 if(motion->finger >= 0) {
580 center_x += motion->point.x;
581 center_y += motion->point.y;
582 touch_cnt++;
583
584 }
585 else {
586 break;
587 }
588 }
589
590 center_x = center_x / touch_cnt;
591 center_y = center_y / touch_cnt;
592
593 /* translation */
594 g->delta_x = g->p_delta_x + (center_x - g->center.x);
595 g->delta_y = g->p_delta_x + (center_y - g->center.y);
596
597 /* rotation & scaling */
598 for(i = 0; i < touch_points_nb; i++) {
599 motion = &g->motions[i];
600
601 if(motion->finger >= 0) {
602 d_x = (motion->point.x - center_x);
603 d_y = (motion->point.y - center_y);
604 a += g->scale_factors_x[i] * d_x + g->scale_factors_y[i] * d_y;
605 b += g->scale_factors_x[i] * d_y + g->scale_factors_y[i] * d_x;
606 }
607 }
608
609 g->rotation = g->p_rotation + atan2f(b, a);
610 g->scale = g->p_scale * sqrtf((a * a) + (b * b));
611
612 g->center.x = (int32_t)center_x;
613 g->center.y = (int32_t)center_y;
614
615 }
616
617 #endif /* LV_USE_GESTURE_RECOGNITION */
618