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