From f677c25abcecfc68afd159b4bb76c7f7590e9461 Mon Sep 17 00:00:00 2001
From: Erik Tagirov <162967732+etag4048@users.noreply.github.com>
Date: Mon, 18 Nov 2024 09:52:25 +0100
Subject: [PATCH] feat(indev): add multi touch gestures (#7078)
---
docs/details/main-components/indev.rst | 67 +-
.../others/gestures/lv_example_gestures.c | 200 ++++++
.../others/gestures/lv_example_gestures.h | 44 ++
examples/others/lv_example_others.h | 1 +
lv_conf_template.h | 4 +
lvgl.h | 2 +
src/drivers/wayland/lv_wayland.c | 128 +++-
src/drivers/wayland/lv_wayland.h | 1 +
src/indev/lv_indev.c | 24 +
src/indev/lv_indev.h | 15 +
src/indev/lv_indev_gesture.c | 615 ++++++++++++++++++
src/indev/lv_indev_gesture.h | 165 +++++
src/indev/lv_indev_gesture_private.h | 93 +++
src/indev/lv_indev_private.h | 3 +
src/lv_conf_internal.h | 10 +
15 files changed, 1340 insertions(+), 32 deletions(-)
create mode 100644 examples/others/gestures/lv_example_gestures.c
create mode 100644 examples/others/gestures/lv_example_gestures.h
create mode 100644 src/indev/lv_indev_gesture.c
create mode 100644 src/indev/lv_indev_gesture.h
create mode 100644 src/indev/lv_indev_gesture_private.h
diff --git a/docs/details/main-components/indev.rst b/docs/details/main-components/indev.rst
index 7ac1b96c0..2a0d63b18 100644
--- a/docs/details/main-components/indev.rst
+++ b/docs/details/main-components/indev.rst
@@ -123,7 +123,6 @@ If you did some action on a gesture you can call
:cpp:expr:`lv_indev_wait_release(lv_indev_active())` in the event handler to
prevent LVGL sending further input-device-related events.
-
.. _indev_crown:
Crown Behavior
@@ -157,6 +156,72 @@ For example, if both the indev and widget sensitivity is set to 128 (0.5), the i
diff will be multiplied by 0.25. The value of the Widget will be incremented by that
value or the Widget will be scrolled that amount of pixels.
+Multi-touch gestures
+====================
+
+LVGL has the ability to recognize multi-touch gestures, when a gesture
+is detected a ``LV_EVENT_GESTURE`` is passed to the object on which the
+gesture occurred. Currently, only the pinch gesture is supported
+more gesture types will be implemented soon.
+
+To enable the multi-touch gesture recognition set the
+``LV_USE_GESTURE_RECOGNITION`` option in the ``lv_conf.h`` file.
+
+Touch event collection
+~~~~~~~~~~~~~~~~~~~~~~
+
+The driver or application collects touch events until the indev read callback
+is called. It is the responsibility of the driver to call
+the gesture recognition function of the appropriate type. For example
+to recognise pinch gestures call ``lv_indev_gesture_detect_pinch``.
+
+After calling the gesture detection function, it's necessary to call
+the ``lv_indev_set_gesture_data`` function to set the ``gesture_data``
+and ``gesture_type`` fields of the structure ``lv_indev_data_t``
+
+.. code-block::
+
+ /* The recognizer keeps the state of the gesture */
+ static lv_indev_gesture_recognizer_t recognizer;
+
+ /* An array that stores the collected touch events */
+ static lv_indev_touch_data_t touches[10];
+
+ /* A counter that needs to be incremented each time a touch event is recieved */
+ static uint8_t touch_cnt;
+
+ static void touch_read_callback(lv_indev_t * drv, lv_indev_data_t * data)
+ {
+
+ lv_indev_touch_data_t * touch;
+ uint8_t i;
+
+
+ touch = &touches[0];
+ lv_indev_gesture_detect_pinch(recognizer, &touches[0],
+ touch_cnt);
+
+ touch_cnt = 0;
+
+ /* Set the gesture information, before returning to LVGL */
+ lv_indev_set_gesture_data(data, recognizer);
+
+ }
+
+A touch event is represented by the ``lv_indev_touch_data_t`` structure, the fields
+being 1:1 compatible with events emitted by the `libinput `_ library
+
+Handling touch events
+~~~~~~~~~~~~~~~~~~~~~
+
+Touch events are handled like any other event. First, setup a listener for the ``LV_EVENT_GESTURE`` event type by defining and setting the callback function.
+
+The state or scale of the pinch gesture can be retrieved by
+calling the ``lv_event_get_pinch_scale`` and ``lv_indev_get_gesture_state`` from within the
+callback.
+
+An example of such an application is available in
+the source tree ``examples/others/gestures/lv_example_gestures.c``
Keypad or Keyboard
------------------
diff --git a/examples/others/gestures/lv_example_gestures.c b/examples/others/gestures/lv_example_gestures.c
new file mode 100644
index 000000000..68af3e100
--- /dev/null
+++ b/examples/others/gestures/lv_example_gestures.c
@@ -0,0 +1,200 @@
+/*******************************************************************
+ *
+ * @file lv_example_gestures.c
+ *
+ * This is a simple example program that demonstrates how to use
+ * the gesture recognition API, please refer to lv_indev_gesture.h or the documentation
+ * for more details
+ *
+ * The application starts with a single rectangle that is scaled when a pinch gesture
+ * is detected. A single finger moves the rectangle around,
+ *
+ * Copyright (c) 2024 EDGEMTech Ltd
+ *
+ * Author: EDGEMTech Ltd, Erik Tagirov (erik.tagirov@edgemtech.ch)
+ *
+ ******************************************************************/
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../../lv_examples.h"
+
+#if LV_USE_GESTURE_RECOGNITION && \
+ LV_USE_FLOAT
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define RECT_INIT_WIDTH 300.0
+#define RECT_INIT_HEIGHT 300.0
+#define RECT_COLOR 0xC1BCFF
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+static void label_scale(lv_event_t * gesture_event);
+static void label_move(lv_event_t * event);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+static lv_obj_t * label;
+static lv_style_t label_style;
+static float label_width;
+static float label_height;
+static float label_x;
+static float label_y;
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**
+ * Entry point it creates the screen, and the label
+ * Set event callbacks on the label
+ */
+void lv_example_gestures(void)
+{
+ lv_obj_t * rectangle;
+ lv_obj_t * root_view;
+
+ label_width = RECT_INIT_WIDTH;
+ label_height = RECT_INIT_HEIGHT;
+ label_y = label_x = 300;
+
+ root_view = lv_screen_active();
+
+ lv_obj_set_style_bg_color(root_view, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
+ label = lv_label_create(root_view);
+ lv_obj_remove_flag(root_view, LV_OBJ_FLAG_SCROLLABLE);
+
+ lv_label_set_text(label, "Zoom or move");
+ lv_obj_add_flag(label, LV_OBJ_FLAG_CLICKABLE);
+
+ lv_style_init(&label_style);
+ lv_style_set_bg_color(&label_style, lv_color_hex(RECT_COLOR));
+ lv_style_set_bg_opa(&label_style, LV_OPA_COVER);
+
+ lv_style_set_width(&label_style, (int)label_width);
+ lv_style_set_height(&label_style, (int)label_height);
+
+ lv_style_set_x(&label_style, (int)label_x);
+ lv_style_set_y(&label_style, (int)label_y);
+
+ lv_obj_add_style(label, &label_style, LV_STATE_DEFAULT);
+
+ lv_obj_add_event_cb(label, label_scale, LV_EVENT_GESTURE, label);
+ lv_obj_add_event_cb(label, label_move, LV_EVENT_PRESSING, label);
+
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+/**
+ * Called when a pinch event occurs - scales the label
+ * @param gesture_event point to a LV_EVENT_PINCH event
+ */
+static void label_scale(lv_event_t * gesture_event)
+{
+
+ static int initial_w = -1;
+ static int initial_h = -1;
+ lv_indev_gesture_state_t state;
+ lv_point_t center_pnt;
+ float scale;
+
+ scale = lv_event_get_pinch_scale(gesture_event);
+ state = lv_event_get_gesture_state(gesture_event);
+
+ lv_indev_get_point(lv_indev_active(), ¢er_pnt);
+
+ if(state == LV_INDEV_GESTURE_STATE_ENDED) {
+ /* Pinch gesture has ended - reset the width/height for the next pinch gesture*/
+ initial_w = -1;
+ initial_h = -1;
+
+ LV_LOG_TRACE("label end scale: %g %d\n", scale, state);
+ return;
+ }
+
+ if(initial_h == -1 || initial_w == -1) {
+
+ LV_ASSERT(state == LV_INDEV_GESTURE_STATE_RECOGNIZED);
+
+ /* Pinch gesture has been recognized - this is the first event in a series of recognized events */
+ /* The scaling is applied relative to the original width/height of the rectangle */
+ initial_w = label_width;
+ initial_h = label_height;
+
+ LV_LOG_TRACE("label start scale: %g\n", scale);
+ }
+
+ /* The gesture has started or is on-going */
+
+ /* Avoids a situation where the rectangle becomes too small,
+ * do not perform the scaling - leave straight away */
+ if(scale < 0.4) {
+ return;
+ }
+
+ label_width = initial_w * scale;
+ label_height = initial_h * scale;
+ label_x = center_pnt.x - label_width / 2;
+ label_y = center_pnt.y - label_height / 2;
+
+ LV_LOG_TRACE("label scale: %g label x: %g label y: %g w: %g h: %g\n",
+ scale, label_x, label_y, label_width, label_height);
+
+ /* Update position and size */
+ lv_style_set_width(&label_style, (int)label_width);
+ lv_style_set_height(&label_style, (int)label_height);
+ lv_style_set_x(&label_style, (int)label_x);
+ lv_style_set_y(&label_style, (int)label_y);
+
+ lv_obj_add_style(label, &label_style, LV_STATE_DEFAULT);
+}
+
+/**
+ * Called when a LV_EVENT_PRESSING occurs on the rectangle - moves the label
+ * @param event pointer to the event
+ */
+static void label_move(lv_event_t * event)
+{
+ lv_point_t pnt;
+ lv_indev_gesture_state_t state;
+
+ state = lv_event_get_gesture_state(event);
+ lv_indev_get_point(lv_indev_active(), &pnt);
+
+ /* Do not move and when a pinch gesture is ongoing */
+ if(state == LV_INDEV_GESTURE_STATE_RECOGNIZED) {
+ return;
+ }
+
+ LV_LOG_TRACE("label move %p x: %d y: %d\n", event, pnt.x, pnt.y);
+
+ label_x = pnt.x - label_width / 2;
+ label_y = pnt.y - label_height / 2;
+
+ /* Update position */
+ lv_style_set_x(&label_style, (int)label_x);
+ lv_style_set_y(&label_style, (int)label_y);
+
+ lv_obj_add_style(label, &label_style, LV_STATE_DEFAULT);
+}
+
+#endif /* LV_USE_GESTURE_RECOGNITION && LV_USE_FLOAT */
diff --git a/examples/others/gestures/lv_example_gestures.h b/examples/others/gestures/lv_example_gestures.h
new file mode 100644
index 000000000..8faa67252
--- /dev/null
+++ b/examples/others/gestures/lv_example_gestures.h
@@ -0,0 +1,44 @@
+/*******************************************************************
+ * @file lv_example_gestures.h
+ *
+ * Copyright (c) 2024 EDGEMTech Ltd.
+ *
+ * Author: EDGEMTech Ltd, Erik Tagirov (erik.tagirov@edgemtech.ch)
+ *
+ ******************************************************************/
+
+#ifndef LV_EXAMPLE_GESTURES_H
+#define LV_EXAMPLE_GESTURES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/* Example entry point */
+void lv_example_gestures(void);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_EXAMPLE_GESTURES_H*/
diff --git a/examples/others/lv_example_others.h b/examples/others/lv_example_others.h
index 3c48d5bc1..dc6886763 100644
--- a/examples/others/lv_example_others.h
+++ b/examples/others/lv_example_others.h
@@ -22,6 +22,7 @@ extern "C" {
#include "monkey/lv_example_monkey.h"
#include "observer/lv_example_observer.h"
#include "snapshot/lv_example_snapshot.h"
+#include "gestures/lv_example_gestures.h"
/*********************
* DEFINES
diff --git a/lv_conf_template.h b/lv_conf_template.h
index 8cf8f4631..bdf512666 100644
--- a/lv_conf_template.h
+++ b/lv_conf_template.h
@@ -475,6 +475,10 @@
#define LV_VG_LITE_THORVG_THREAD_RENDER 0
#endif
+/* Enable the multi-touch gesture recognition feature */
+/* Gesture recognition requires the use of floats */
+#define LV_USE_GESTURE_RECOGNITION 0
+
/*=====================
* COMPILER SETTINGS
*====================*/
diff --git a/lvgl.h b/lvgl.h
index 5cea1e283..758e8be64 100644
--- a/lvgl.h
+++ b/lvgl.h
@@ -43,6 +43,7 @@ extern "C" {
#include "src/core/lv_obj.h"
#include "src/core/lv_group.h"
#include "src/indev/lv_indev.h"
+#include "src/indev/lv_indev_gesture.h"
#include "src/core/lv_refr.h"
#include "src/display/lv_display.h"
@@ -130,6 +131,7 @@ extern "C" {
#include "src/lvgl_private.h"
#endif
+
/*********************
* DEFINES
*********************/
diff --git a/src/drivers/wayland/lv_wayland.c b/src/drivers/wayland/lv_wayland.c
index dc71f6677..f2469d049 100644
--- a/src/drivers/wayland/lv_wayland.c
+++ b/src/drivers/wayland/lv_wayland.c
@@ -1,7 +1,6 @@
/*******************************************************************
*
* @file lv_wayland.c - The Wayland client for LVGL applications
- *
* Based on the original file from the repository.
*
* Porting to LVGL 9.1
@@ -43,7 +42,6 @@ typedef int dummy_t; /* Make GCC on windows happy, avoid empty translation un
#include "lvgl.h"
-
#if !LV_WAYLAND_WL_SHELL
#include "wayland_xdg_shell.h"
#define LV_WAYLAND_XDG_SHELL 1
@@ -101,6 +99,7 @@ enum object_type {
#define LAST_DECORATION (OBJECT_BORDER_RIGHT)
#define NUM_DECORATIONS (LAST_DECORATION-FIRST_DECORATION+1)
+
struct window;
struct input {
struct {
@@ -117,11 +116,12 @@ struct input {
lv_indev_state_t state;
} keyboard;
- struct {
- uint32_t x;
- uint32_t y;
- lv_indev_state_t state;
- } touch;
+#if LV_USE_GESTURE_RECOGNITION
+ lv_indev_touch_data_t touches[10];
+ uint8_t touch_event_cnt;
+ uint8_t primary_id;
+ lv_indev_gesture_recognizer_t recognizer;
+#endif
};
struct seat {
@@ -878,11 +878,14 @@ static const struct wl_keyboard_listener keyboard_listener = {
.modifiers = keyboard_handle_modifiers,
};
+#if LV_USE_GESTURE_RECOGNITION
+
static void touch_handle_down(void * data, struct wl_touch * wl_touch,
uint32_t serial, uint32_t time, struct wl_surface * surface,
int32_t id, wl_fixed_t x_w, wl_fixed_t y_w)
{
struct application * app = data;
+ uint8_t i;
LV_UNUSED(id);
LV_UNUSED(time);
@@ -894,11 +897,16 @@ static void touch_handle_down(void * data, struct wl_touch * wl_touch,
return;
}
+ /* Create the touch down event */
app->touch_obj = wl_surface_get_user_data(surface);
+ i = app->touch_obj->input.touch_event_cnt;
- app->touch_obj->input.touch.x = wl_fixed_to_int(x_w);
- app->touch_obj->input.touch.y = wl_fixed_to_int(y_w);
- app->touch_obj->input.touch.state = LV_INDEV_STATE_PRESSED;
+ app->touch_obj->input.touches[i].point.x = wl_fixed_to_int(x_w);
+ app->touch_obj->input.touches[i].point.y = wl_fixed_to_int(y_w);
+ app->touch_obj->input.touches[i].id = id;
+ app->touch_obj->input.touches[i].timestamp = time;
+ app->touch_obj->input.touches[i].state = LV_INDEV_STATE_PRESSED;
+ app->touch_obj->input.touch_event_cnt++;
#if LV_WAYLAND_WINDOW_DECORATIONS
struct window * window = app->touch_obj->window;
@@ -927,17 +935,25 @@ static void touch_handle_up(void * data, struct wl_touch * wl_touch,
uint32_t serial, uint32_t time, int32_t id)
{
struct application * app = data;
+ uint8_t i;
LV_UNUSED(serial);
LV_UNUSED(time);
LV_UNUSED(id);
LV_UNUSED(wl_touch);
- if(!app->touch_obj) {
- return;
- }
+#if LV_USE_GESTURE_RECOGNITION
+ /* Create a released event */
+ i = app->touch_obj->input.touch_event_cnt;
- app->touch_obj->input.touch.state = LV_INDEV_STATE_RELEASED;
+ app->touch_obj->input.touches[i].point.x = 0;
+ app->touch_obj->input.touches[i].point.y = 0;
+ app->touch_obj->input.touches[i].id = id;
+ app->touch_obj->input.touches[i].timestamp = time;
+ app->touch_obj->input.touches[i].state = LV_INDEV_STATE_RELEASED;
+
+ app->touch_obj->input.touch_event_cnt++;
+#endif
#if LV_WAYLAND_WINDOW_DECORATIONS
struct window * window = app->touch_obj->window;
@@ -962,30 +978,56 @@ static void touch_handle_up(void * data, struct wl_touch * wl_touch,
xdg_toplevel_set_minimized(window->xdg_toplevel);
window->flush_pending = true;
}
-#endif // LV_WAYLAND_XDG_SHELL
+#endif /* LV_WAYLAND_XDG_SHELL */
default:
break;
}
-#endif // LV_WAYLAND_WINDOW_DECORATIONS
+#endif /* LV_WAYLAND_WINDOW_DECORATIONS */
- app->touch_obj = NULL;
}
static void touch_handle_motion(void * data, struct wl_touch * wl_touch,
uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w)
{
struct application * app = data;
+ lv_indev_touch_data_t * touch;
+ lv_indev_touch_data_t * cur;
+ uint8_t i;
LV_UNUSED(time);
LV_UNUSED(id);
LV_UNUSED(wl_touch);
- if(!app->touch_obj) {
- return;
+ /* Update the contact point of the corresponding id with the latest coordinate */
+ touch = &app->touch_obj->input.touches[0];
+ cur = NULL;
+
+ for(i = 0; i < app->touch_obj->input.touch_event_cnt; i++) {
+ if(touch->id == id) {
+ cur = touch;
+ }
+ touch++;
+ }
+
+ if(cur == NULL) {
+
+ i = app->touch_obj->input.touch_event_cnt;
+ app->touch_obj->input.touches[i].point.x = wl_fixed_to_int(x_w);
+ app->touch_obj->input.touches[i].point.y = wl_fixed_to_int(y_w);
+ app->touch_obj->input.touches[i].id = id;
+ app->touch_obj->input.touches[i].timestamp = time;
+ app->touch_obj->input.touches[i].state = LV_INDEV_STATE_PRESSED;
+ app->touch_obj->input.touch_event_cnt++;
+
+ }
+ else {
+
+ cur->point.x = wl_fixed_to_int(x_w);
+ cur->point.y = wl_fixed_to_int(y_w);
+ cur->id = id;
+ cur->timestamp = time;
}
- app->touch_obj->input.touch.x = wl_fixed_to_int(x_w);
- app->touch_obj->input.touch.y = wl_fixed_to_int(y_w);
}
static void touch_handle_frame(void * data, struct wl_touch * wl_touch)
@@ -1009,6 +1051,8 @@ static const struct wl_touch_listener touch_listener = {
.cancel = touch_handle_cancel,
};
+#endif /* END LV_USE_GESTURE_RECOGNITION */
+
static void seat_handle_capabilities(void * data, struct wl_seat * wl_seat, enum wl_seat_capability caps)
{
struct application * app = data;
@@ -1039,10 +1083,12 @@ static void seat_handle_capabilities(void * data, struct wl_seat * wl_seat, enum
seat->wl_keyboard = NULL;
}
+#if LV_USE_GESTURE_RECOGNITION
if((caps & WL_SEAT_CAPABILITY_TOUCH) && !seat->wl_touch) {
seat->wl_touch = wl_seat_get_touch(wl_seat);
wl_touch_add_listener(seat->wl_touch, &touch_listener, app);
}
+#endif
else if(!(caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch) {
wl_touch_destroy(seat->wl_touch);
seat->wl_touch = NULL;
@@ -2223,6 +2269,7 @@ skip:
static void _lv_wayland_handle_input(void)
{
int prepare_read = wl_display_prepare_read(application.display);
+
while(prepare_read != 0) {
wl_display_dispatch_pending(application.display);
}
@@ -2249,13 +2296,6 @@ static void _lv_wayland_handle_output(void)
window->shall_close = false;
shall_flush = true;
- window->body->input.touch.x = 0;
- window->body->input.touch.y = 0;
- window->body->input.touch.state = LV_INDEV_STATE_RELEASED;
- if(window->application->touch_obj == window->body) {
- window->application->touch_obj = NULL;
- }
-
window->body->input.pointer.x = 0;
window->body->input.pointer.y = 0;
window->body->input.pointer.left_button = LV_INDEV_STATE_RELEASED;
@@ -2331,18 +2371,40 @@ static void _lv_wayland_keyboard_read(lv_indev_t * drv, lv_indev_data_t * data)
data->state = window->body->input.keyboard.state;
}
+#if LV_USE_GESTURE_RECOGNITION
+
static void _lv_wayland_touch_read(lv_indev_t * drv, lv_indev_data_t * data)
{
+
struct window * window = lv_display_get_user_data(lv_indev_get_display(drv));
+ lv_indev_touch_data_t * touch;
+ bool is_active;
+ lv_indev_gesture_recognizer_t * recognizer;
+ uint8_t touch_cnt;
+ uint8_t i;
+
if(!window || window->closed) {
return;
}
- data->point.x = window->body->input.touch.x;
- data->point.y = window->body->input.touch.y;
- data->state = window->body->input.touch.state;
+ /* Collect touches if there are any - send them to the gesture recognizer */
+ recognizer = &window->body->input.recognizer;
+ touch = &window->body->input.touches[0];
+
+ LV_LOG_TRACE("collected touch events: %d", window->body->input.touch_event_cnt);
+
+ lv_indev_gesture_detect_pinch(recognizer, &window->body->input.touches[0],
+ window->body->input.touch_event_cnt);
+
+ window->body->input.touch_event_cnt = 0;
+
+ /* Set the gesture information, before returning to LVGL */
+ lv_indev_set_gesture_data(data, recognizer);
+
}
+#endif /* END LV_USE_GESTURE_RECOGNITION */
+
/**********************
* GLOBAL FUNCTIONS
**********************/
@@ -2569,6 +2631,8 @@ lv_display_t * lv_wayland_window_create(uint32_t hor_res, uint32_t ver_res, char
LV_LOG_ERROR("failed to register pointeraxis indev");
}
+#if LV_USE_GESTURE_RECOGNITION
+
window->lv_indev_touch = lv_indev_create();
lv_indev_set_type(window->lv_indev_touch, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(window->lv_indev_touch, _lv_wayland_touch_read);
@@ -2578,6 +2642,8 @@ lv_display_t * lv_wayland_window_create(uint32_t hor_res, uint32_t ver_res, char
LV_LOG_ERROR("failed to register touch indev");
}
+#endif /* END LV_USE_GESTURE_RECOGNITION */
+
window->lv_indev_keyboard = lv_indev_create();
lv_indev_set_type(window->lv_indev_keyboard, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_read_cb(window->lv_indev_keyboard, _lv_wayland_keyboard_read);
diff --git a/src/drivers/wayland/lv_wayland.h b/src/drivers/wayland/lv_wayland.h
index f3d340b0d..2e30fb276 100644
--- a/src/drivers/wayland/lv_wayland.h
+++ b/src/drivers/wayland/lv_wayland.h
@@ -27,6 +27,7 @@ extern "C" {
#include "../../display/lv_display.h"
#include "../../indev/lv_indev.h"
+#include "../../indev/lv_indev_gesture.h"
#if LV_USE_WAYLAND
diff --git a/src/indev/lv_indev.c b/src/indev/lv_indev.c
index 6c34c1a58..da9789171 100644
--- a/src/indev/lv_indev.c
+++ b/src/indev/lv_indev.c
@@ -12,6 +12,7 @@
* INCLUDES
********************/
#include "lv_indev_scroll.h"
+#include "lv_indev_gesture.h"
#include "../display/lv_display_private.h"
#include "../core/lv_global.h"
#include "../core/lv_obj_private.h"
@@ -728,6 +729,9 @@ static void indev_pointer_proc(lv_indev_t * i, lv_indev_data_t * data)
i->pointer.act_point.y = data->point.y;
i->pointer.diff = data->enc_diff;
+ i->gesture_type = data->gesture_type;
+ i->gesture_data = data->gesture_data;
+
/*Process the diff first as scrolling will be processed in indev_proc_release*/
indev_proc_pointer_diff(i);
@@ -1277,13 +1281,24 @@ static void indev_proc_press(lv_indev_t * indev)
indev->pointer.press_moved = 1;
}
+ /* Send a gesture event to a potential indev cb callback, even if no object was found */
+ if(indev->gesture_type != LV_INDEV_GESTURE_NONE) {
+ lv_indev_send_event(indev, LV_EVENT_GESTURE, indev_act);
+ }
+
if(indev_obj_act) {
const bool is_enabled = !lv_obj_has_state(indev_obj_act, LV_STATE_DISABLED);
+ if(indev->gesture_type != LV_INDEV_GESTURE_NONE) {
+ /* NOTE: hardcoded to pinch for now */
+ if(send_event(LV_EVENT_GESTURE, indev_act) == LV_RESULT_INVALID) return;
+ }
+
if(is_enabled) {
if(send_event(LV_EVENT_PRESSING, indev_act) == LV_RESULT_INVALID) return;
}
+
if(indev_act->wait_until_release) return;
lv_indev_scroll_handler(indev);
@@ -1363,11 +1378,20 @@ static void indev_proc_release(lv_indev_t * indev)
lv_timer_pause(indev->read_timer);
}
+ /* Send a gesture event to a potential indev cb callback, even if no object was found */
+ if(indev->gesture_type != LV_INDEV_GESTURE_NONE) {
+ lv_indev_send_event(indev, LV_EVENT_GESTURE, indev_act);
+ }
+
if(indev_obj_act) {
LV_LOG_INFO("released");
const bool is_enabled = !lv_obj_has_state(indev_obj_act, LV_STATE_DISABLED);
+ if(is_enabled && indev->gesture_type != LV_INDEV_GESTURE_NONE) {
+ if(send_event(LV_EVENT_GESTURE, indev_act) == LV_RESULT_INVALID) return;
+ }
+
if(is_enabled) {
if(send_event(LV_EVENT_RELEASED, indev_act) == LV_RESULT_INVALID) return;
}
diff --git a/src/indev/lv_indev.h b/src/indev/lv_indev.h
index 7267c5034..6bdf9b1c5 100644
--- a/src/indev/lv_indev.h
+++ b/src/indev/lv_indev.h
@@ -47,6 +47,17 @@ typedef enum {
LV_INDEV_MODE_EVENT,
} lv_indev_mode_t;
+
+/* Supported types of gestures */
+typedef enum {
+ LV_INDEV_GESTURE_NONE = 0,
+ LV_INDEV_GESTURE_PINCH,
+ LV_INDEV_GESTURE_SWIPE,
+ LV_INDEV_GESTURE_ROTATE,
+ LV_INDEV_GESTURE_SCROLL, /* Used with scrollwheels */
+ LV_INDEV_GESTURE_CNT, /* Total number of gestures types */
+} lv_indev_gesture_type_t;
+
/** Data structure passed to an input driver to fill*/
typedef struct {
lv_point_t point; /**< For LV_INDEV_TYPE_POINTER the currently pressed point*/
@@ -56,6 +67,10 @@ typedef struct {
lv_indev_state_t state; /**< LV_INDEV_STATE_RELEASED or LV_INDEV_STATE_PRESSED*/
bool continue_reading; /**< If set to true, the read callback is invoked again, unless the device is in event-driven mode*/
+
+ lv_indev_gesture_type_t gesture_type;
+ void * gesture_data;
+
} lv_indev_data_t;
typedef void (*lv_indev_read_cb_t)(lv_indev_t * indev, lv_indev_data_t * data);
diff --git a/src/indev/lv_indev_gesture.c b/src/indev/lv_indev_gesture.c
new file mode 100644
index 000000000..9b71581c1
--- /dev/null
+++ b/src/indev/lv_indev_gesture.c
@@ -0,0 +1,615 @@
+/******************************************************************
+ * @file lv_indev_gesture.c
+ *
+ * Recognize gestures that consist of multiple touch events
+ *
+ * Copyright (c) 2024 EDGEMTech Ltd
+ *
+ * Author EDGEMTech Ltd. (erik.tagirov@edgemtech.ch)
+ *
+ ******************************************************************/
+
+/********************
+ * INCLUDES
+ ********************/
+
+#include "lv_indev_private.h"
+#include "../misc/lv_event_private.h"
+
+#if LV_USE_GESTURE_RECOGNITION
+
+#include
+#include "lv_indev_gesture.h"
+#include "lv_indev_gesture_private.h"
+
+/********************
+ * DEFINES
+ ********************/
+
+#define LV_GESTURE_PINCH_DOWN_THRESHOLD 0.75 /* Default value - start sending events when reached */
+#define LV_GESTURE_PINCH_UP_THRESHOLD 1.5 /* Default value - start sending events when reached */
+#define LV_GESTURE_PINCH_MAX_INITIAL_SCALE 2.5 /* Default value */
+
+
+/********************
+ * TYPEDEFS
+ ********************/
+
+/********************
+ * STATIC PROTOTYPES
+ ********************/
+
+static lv_indev_gesture_t * init_gesture_info(void);
+static void reset_gesture_info(lv_indev_gesture_t * info);
+static lv_indev_gesture_motion_t * get_motion(uint8_t id, lv_indev_gesture_t * info);
+static int8_t get_motion_idx(uint8_t id, lv_indev_gesture_t * info);
+static void process_touch_event(lv_indev_touch_data_t * touch, lv_indev_gesture_t * info);
+static void gesture_update_center_point(lv_indev_gesture_t * gesture, int touch_points_nb);
+static void gesture_calculate_factors(lv_indev_gesture_t * gesture, int touch_points_nb);
+static void reset_recognizer(lv_indev_gesture_recognizer_t * recognizer);
+static lv_indev_gesture_recognizer_t * lv_indev_get_gesture_recognizer(lv_event_t * gesture_event);
+
+/********************
+ * STATIC VARIABLES
+ ********************/
+
+/********************
+ * MACROS
+ ********************/
+
+/********************
+ * GLOBAL FUNCTIONS
+ ********************/
+
+void lv_indev_set_pinch_up_threshold(lv_indev_gesture_recognizer_t * recognizer, float threshold)
+{
+ /* A up threshold MUST always be bigger than 1 */
+ LV_ASSERT(threshold > 1.0);
+
+ if(recognizer->config == NULL) {
+
+ recognizer->config = lv_malloc(sizeof(lv_indev_gesture_configuration_t));
+ LV_ASSERT(recognizer->config != NULL);
+ recognizer->config->pinch_down_threshold = LV_GESTURE_PINCH_DOWN_THRESHOLD;
+ }
+
+ recognizer->config->pinch_up_threshold = threshold;
+}
+
+void lv_indev_set_pinch_down_threshold(lv_indev_gesture_recognizer_t * recognizer, float threshold)
+{
+ /* A down threshold MUST always be smaller than 1 */
+ LV_ASSERT(threshold < 1.0);
+
+ if(recognizer->config == NULL) {
+
+ recognizer->config = lv_malloc(sizeof(lv_indev_gesture_configuration_t));
+ LV_ASSERT(recognizer->config != NULL);
+ recognizer->config->pinch_up_threshold = LV_GESTURE_PINCH_UP_THRESHOLD;
+ }
+
+ recognizer->config->pinch_down_threshold = threshold;
+}
+
+void lv_indev_get_gesture_primary_point(lv_indev_gesture_recognizer_t * recognizer, lv_point_t * point)
+{
+ if(recognizer->info->motions[0].finger != -1) {
+ point->x = recognizer->info->motions[0].point.x;
+ point->y = recognizer->info->motions[0].point.y;
+ return;
+ }
+
+ /* There are currently no active contact points */
+ point->x = 0;
+ point->y = 0;
+}
+
+bool lv_indev_recognizer_is_active(lv_indev_gesture_recognizer_t * recognizer)
+{
+ if(recognizer->state == LV_INDEV_GESTURE_STATE_ENDED ||
+ recognizer->info->finger_cnt == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+float lv_event_get_pinch_scale(lv_event_t * gesture_event)
+{
+ lv_indev_gesture_recognizer_t * recognizer;
+
+ if((recognizer = lv_indev_get_gesture_recognizer(gesture_event)) == NULL) {
+ return 0.0f;
+ }
+
+ return recognizer->scale;
+}
+
+void lv_indev_get_gesture_center_point(lv_indev_gesture_recognizer_t * recognizer, lv_point_t * point)
+{
+ if(lv_indev_recognizer_is_active(recognizer) == false) {
+ point->x = 0;
+ point->y = 0;
+ return;
+ }
+
+ point->x = recognizer->info->center.x;
+ point->y = recognizer->info->center.y;
+
+}
+
+lv_indev_gesture_state_t lv_event_get_gesture_state(lv_event_t * gesture_event)
+{
+ lv_indev_gesture_recognizer_t * recognizer;
+
+ if((recognizer = lv_indev_get_gesture_recognizer(gesture_event)) == NULL) {
+ return LV_INDEV_GESTURE_STATE_NONE;
+ }
+
+ return recognizer->state;
+}
+
+
+void lv_indev_set_gesture_data(lv_indev_data_t * data, lv_indev_gesture_recognizer_t * recognizer)
+{
+ bool is_active;
+ lv_point_t cur_pnt;
+
+ if(recognizer == NULL) return;
+
+ /* If there is a single contact point use its coords,
+ * when there are no contact points it's set to 0,0
+ *
+ * Note: If a gesture was detected, the primary point is overwritten below
+ */
+
+ lv_indev_get_gesture_primary_point(recognizer, &cur_pnt);
+ data->point.x = cur_pnt.x;
+ data->point.y = cur_pnt.y;
+
+ data->gesture_type = LV_INDEV_GESTURE_NONE;
+ data->gesture_data = NULL;
+
+ /* The call below returns false if there are no active contact points */
+ /* - OR when the gesture has ended, false is considered as a RELEASED state */
+ is_active = lv_indev_recognizer_is_active(recognizer);
+
+ if(is_active == false) {
+ data->state = LV_INDEV_STATE_RELEASED;
+
+ }
+ else {
+ data->state = LV_INDEV_STATE_PRESSED;
+ }
+
+ switch(recognizer->state) {
+ case LV_INDEV_GESTURE_STATE_RECOGNIZED:
+ lv_indev_get_gesture_center_point(recognizer, &cur_pnt);
+ data->point.x = cur_pnt.x;
+ data->point.y = cur_pnt.y;
+ data->gesture_type = LV_INDEV_GESTURE_PINCH;
+ data->gesture_data = (void *) recognizer;
+ break;
+
+ case LV_INDEV_GESTURE_STATE_ENDED:
+ data->gesture_type = LV_INDEV_GESTURE_PINCH;
+ data->gesture_data = (void *) recognizer;
+ break;
+ }
+}
+
+
+void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, lv_indev_touch_data_t * touches,
+ uint16_t touch_cnt)
+{
+ lv_indev_touch_data_t * touch;
+ lv_indev_gesture_recognizer_t * r = recognizer;
+ uint8_t i;
+
+ if(r->info == NULL) {
+ LV_LOG_TRACE("init gesture info");
+ r->info = init_gesture_info();
+ }
+
+ if(r->config == NULL) {
+ LV_LOG_TRACE("init gesture configuration - set defaults");
+ r->config = lv_malloc(sizeof(lv_indev_gesture_configuration_t));
+
+ LV_ASSERT(r->config != NULL);
+
+ r->config->pinch_up_threshold = LV_GESTURE_PINCH_UP_THRESHOLD;
+ r->config->pinch_down_threshold = LV_GESTURE_PINCH_DOWN_THRESHOLD;
+ }
+
+ /* Process collected touch events */
+ for(i = 0; i < touch_cnt; i++) {
+
+ touch = touches;
+ process_touch_event(touch, r->info);
+ touches++;
+
+ LV_LOG_TRACE("processed touch ev: %d finger id: %d state: %d x: %d y: %d finger_cnt: %d",
+ i, touch->id, touch->state, touch->point.x, touch->point.y, r->info->finger_cnt);
+ }
+
+ LV_LOG_TRACE("Current finger count: %d state: %d", r->info->finger_cnt, r->state);
+
+
+ if(r->info->finger_cnt == 2) {
+
+ switch(r->state) {
+ case LV_INDEV_GESTURE_STATE_ENDED:
+ case LV_INDEV_GESTURE_STATE_CANCELED:
+ case LV_INDEV_GESTURE_STATE_NONE:
+
+ /* 2 fingers down - potential pinch or swipe */
+ reset_recognizer(recognizer);
+ gesture_update_center_point(r->info, 2);
+ r->state = LV_INDEV_GESTURE_STATE_ONGOING;
+ break;
+
+ case LV_INDEV_GESTURE_STATE_ONGOING:
+ case LV_INDEV_GESTURE_STATE_RECOGNIZED:
+
+ /* It's an ongoing pinch gesture - update the factors */
+ gesture_calculate_factors(r->info, 2);
+
+ if(r->info->scale > LV_GESTURE_PINCH_MAX_INITIAL_SCALE &&
+ r->state == LV_INDEV_GESTURE_STATE_ONGOING) {
+ r->state = LV_INDEV_GESTURE_STATE_CANCELED;
+ break;
+ }
+
+ LV_ASSERT(r->config != NULL);
+
+ if(r->info->scale > r->config->pinch_up_threshold ||
+ r->info->scale < r->config->pinch_down_threshold) {
+
+ if(r->info->scale > 1.0) {
+ r->scale = r->info->scale - (r->config->pinch_up_threshold - 1.0);
+
+ }
+ else if(r->info->scale < 1.0) {
+
+ r->scale = r->info->scale + (1.0 - r->config->pinch_down_threshold);
+ }
+
+ r->type = LV_INDEV_GESTURE_PINCH;
+ r->state = LV_INDEV_GESTURE_STATE_RECOGNIZED;
+ }
+ break;
+
+ default:
+ LV_ASSERT_MSG(true, "invalid gesture recognizer state");
+ }
+
+ }
+ else {
+
+ switch(r->state) {
+ case LV_INDEV_GESTURE_STATE_RECOGNIZED:
+ /* Gesture has ended */
+ r->state = LV_INDEV_GESTURE_STATE_ENDED;
+ r->type = LV_INDEV_GESTURE_PINCH;
+ break;
+
+ case LV_INDEV_GESTURE_STATE_ONGOING:
+ /* User lifted a finger before reaching threshold */
+ r->state = LV_INDEV_GESTURE_STATE_CANCELED;
+ reset_recognizer(r);
+ break;
+
+ case LV_INDEV_GESTURE_STATE_CANCELED:
+ case LV_INDEV_GESTURE_STATE_ENDED:
+ reset_recognizer(r);
+ break;
+
+ default:
+ LV_ASSERT_MSG(true, "invalid gesture recognizer state");
+ }
+ }
+}
+
+/********************
+ * STATIC FUNCTIONS
+ ********************/
+
+/**
+ * Get the gesture recognizer associated to the event
+ * @param gesture_event an LV_GESTURE_EVENT event
+ * @return A pointer to the gesture recognizer that emitted the event
+ */
+lv_indev_gesture_recognizer_t * lv_indev_get_gesture_recognizer(lv_event_t * gesture_event)
+{
+ lv_indev_t * indev;
+
+ if(gesture_event == NULL || gesture_event->param == NULL) return NULL;
+
+ indev = (lv_indev_t *) gesture_event->param;
+
+ if(indev == NULL || indev->gesture_data == NULL) return NULL;
+
+ return (lv_indev_gesture_recognizer_t *) indev->gesture_data;
+}
+
+/**
+ * Resets a gesture recognizer, motion descriptors are preserved
+ * @param recognizer a pointer to the recognizer to reset
+ */
+static void reset_recognizer(lv_indev_gesture_recognizer_t * recognizer)
+{
+ size_t motion_arr_sz;
+ lv_indev_gesture_t * info;
+ lv_indev_gesture_configuration_t * conf;
+
+ if(recognizer == NULL) return;
+
+ info = recognizer->info;
+ conf = recognizer->config;
+
+ /* Set everything to zero but preserve the motion descriptors,
+ * which are located at the start of the lv_indev_gesture_t struct */
+ motion_arr_sz = sizeof(lv_indev_gesture_motion_t) * LV_GESTURE_MAX_POINTS;
+ lv_memset(info + motion_arr_sz, 0, sizeof(lv_indev_gesture_t) - motion_arr_sz);
+ lv_memset(recognizer, 0, sizeof(lv_indev_gesture_recognizer_t));
+
+ recognizer->scale = info->scale = 1;
+ recognizer->info = info;
+ recognizer->config = conf;
+}
+
+/**
+ * Initializes a motion descriptors used with the recognizer(s)
+ * @return a pointer to gesture descriptor
+ */
+static lv_indev_gesture_t * init_gesture_info(void)
+{
+ lv_indev_gesture_t * info;
+ uint8_t i;
+
+ info = lv_malloc(sizeof(lv_indev_gesture_t));
+ LV_ASSERT_NULL(info);
+
+ lv_memset(info, 0, sizeof(lv_indev_gesture_t));
+ info->scale = 1;
+
+ for(i = 0; i < LV_GESTURE_MAX_POINTS; i++) {
+ info->motions[i].finger = -1;
+ }
+
+ return info;
+}
+
+/**
+ * Obtains the contact point motion descriptor with id
+ * @param id the id of the contact point
+ * @param info a pointer to the gesture descriptor that stores the motion of each contact point
+ * @return a pointer to the motion descriptor or NULL if not found
+ */
+static lv_indev_gesture_motion_t * get_motion(uint8_t id, lv_indev_gesture_t * info)
+{
+ uint8_t i;
+
+ for(i = 0; i < LV_GESTURE_MAX_POINTS; i++) {
+ if(info->motions[i].finger == id) {
+ return &info->motions[i];
+ }
+ }
+
+ return NULL;
+
+}
+
+/**
+ * Obtains the index of the contact point motion descriptor
+ * @param id the id of the contact point
+ * @param info a pointer to the gesture descriptor that stores the motion of each contact point
+ * @return the index of the motion descriptor or -1 if not found
+ */
+static int8_t get_motion_idx(uint8_t id, lv_indev_gesture_t * info)
+{
+ uint8_t i;
+
+ for(i = 0; i < LV_GESTURE_MAX_POINTS; i++) {
+ if(info->motions[i].finger == id) {
+ return i;
+ }
+ }
+
+ return -1;
+
+}
+
+/**
+ * Update the motion descriptors of a gesture
+ * @param touch a pointer to a touch data structure
+ * @param info a pointer to a gesture descriptor
+ */
+static void process_touch_event(lv_indev_touch_data_t * touch, lv_indev_gesture_t * info)
+{
+ lv_indev_gesture_t * g = info;
+ lv_indev_gesture_motion_t * motion;
+ int8_t motion_idx;
+ uint8_t len;
+
+ motion_idx = get_motion_idx(touch->id, g);
+
+ if(motion_idx == -1 && touch->state == LV_INDEV_STATE_PRESSED) {
+
+ if(g->finger_cnt == LV_GESTURE_MAX_POINTS) {
+ /* Skip touch */
+ return;
+ }
+
+ /* New touch point id */
+ motion = &g->motions[g->finger_cnt];
+ motion->start_point.x = touch->point.x;
+ motion->start_point.y = touch->point.y;
+ motion->point.x = touch->point.x;
+ motion->point.y = touch->point.y;
+ motion->finger = touch->id;
+ motion->state = touch->state;
+
+ g->finger_cnt++;
+
+ }
+ else if(motion_idx >= 0 && touch->state == LV_INDEV_STATE_RELEASED) {
+
+ if(motion_idx == g->finger_cnt - 1) {
+
+ /* Mark last item as un-used */
+ motion = get_motion(touch->id, g);
+ motion->finger = -1;
+ motion->state = touch->state;
+
+ }
+ else {
+
+ /* Move back by one */
+ len = (g->finger_cnt - 1) - motion_idx;
+ lv_memmove(g->motions + motion_idx,
+ g->motions + motion_idx + 1,
+ sizeof(lv_indev_gesture_motion_t) * len);
+
+ g->motions[g->finger_cnt - 1].finger = -1;
+
+ LV_ASSERT(g->motions[motion_idx + 1].finger == -1);
+
+ }
+ g->finger_cnt--;
+
+ }
+ else if(motion_idx >= 0) {
+
+ motion = get_motion(touch->id, g);
+ motion->point.x = touch->point.x;
+ motion->point.y = touch->point.y;
+ motion->state = touch->state;
+
+ }
+ else {
+ LV_LOG_TRACE("Ignore extra touch id: %d", touch->id);
+ }
+}
+
+/**
+ * Calculate the center point of a gesture, called when there
+ * is a probability for the gesture to occur
+ * @param touch a pointer to a touch data structure
+ * @param touch_points_nb The number of contact point to take into account
+ */
+static void gesture_update_center_point(lv_indev_gesture_t * gesture, int touch_points_nb)
+{
+ lv_indev_gesture_motion_t * motion;
+ lv_indev_gesture_t * g = gesture;
+ int32_t x = 0;
+ int32_t y = 0;
+ uint8_t i;
+ float scale_factor = 0.0f;
+ float delta_x[LV_GESTURE_MAX_POINTS] = {0.0f};
+ float delta_y[LV_GESTURE_MAX_POINTS] = {0.0f};
+ uint8_t touch_cnt = 0;
+ x = y = 0;
+
+ g->p_scale = g->scale;
+ g->p_delta_x = g->delta_x;
+ g->p_delta_y = g->delta_y;
+ g->p_rotation = g->rotation;
+
+ for(i = 0; i < touch_points_nb; i++) {
+ motion = &g->motions[i];
+
+ if(motion->finger >= 0) {
+ x += motion->point.x;
+ y += motion->point.y;
+ touch_cnt++;
+
+ }
+ else {
+ break;
+ }
+ }
+
+ g->center.x = x / touch_cnt;
+ g->center.y = y / touch_cnt;
+
+ for(i = 0; i < touch_points_nb; i++) {
+
+ motion = &g->motions[i];
+ if(motion->finger >= 0) {
+ delta_x[i] = motion->point.x - g->center.x;
+ delta_y[i] = motion->point.y - g->center.y;
+ scale_factor += (delta_x[i] * delta_x[i]) + (delta_y[i] * delta_y[i]);
+ }
+ }
+ for(i = 0; i < touch_points_nb; i++) {
+
+ motion = &g->motions[i];
+ if(motion->finger >= 0) {
+ g->scale_factors_x[i] = delta_x[i] / scale_factor;
+ g->scale_factors_y[i] = delta_y[i] / scale_factor;
+ }
+ }
+}
+
+/**
+ * Calculate the scale, translation and rotation of a gesture, called when
+ * the gesture has been recognized
+ * @param gesture a pointer to the gesture descriptor
+ * @param touch_points_nb the number of contact points to take into account
+ */
+static void gesture_calculate_factors(lv_indev_gesture_t * gesture, int touch_points_nb)
+{
+ lv_indev_gesture_motion_t * motion;
+ lv_indev_gesture_t * g = gesture;
+ float center_x = 0;
+ float center_y = 0;
+ float a = 0;
+ float b = 0;
+ float d_x;
+ float d_y;
+ int8_t i;
+ int8_t touch_cnt = 0;
+
+ for(i = 0; i < touch_points_nb; i++) {
+ motion = &g->motions[i];
+
+ if(motion->finger >= 0) {
+ center_x += motion->point.x;
+ center_y += motion->point.y;
+ touch_cnt++;
+
+ }
+ else {
+ break;
+ }
+ }
+
+ center_x = center_x / touch_cnt;
+ center_y = center_y / touch_cnt;
+
+ /* translation */
+ g->delta_x = g->p_delta_x + (center_x - g->center.x);
+ g->delta_y = g->p_delta_x + (center_y - g->center.y);
+
+ /* rotation & scaling */
+ for(i = 0; i < touch_points_nb; i++) {
+ motion = &g->motions[i];
+
+ if(motion->finger >= 0) {
+ d_x = (motion->point.x - center_x);
+ d_y = (motion->point.y - center_y);
+ a += g->scale_factors_x[i] * d_x + g->scale_factors_y[i] * d_y;
+ b += g->scale_factors_x[i] * d_y + g->scale_factors_y[i] * d_x;
+ }
+ }
+
+ g->rotation = g->p_rotation + atan2f(b, a);
+ g->scale = g->p_scale * sqrtf((a * a) + (b * b));
+
+ g->center.x = center_x;
+ g->center.y = center_y;
+
+}
+
+#endif /* LV_USE_GESTURE_RECOGNITION */
diff --git a/src/indev/lv_indev_gesture.h b/src/indev/lv_indev_gesture.h
new file mode 100644
index 000000000..9b5768999
--- /dev/null
+++ b/src/indev/lv_indev_gesture.h
@@ -0,0 +1,165 @@
+/*******************************************************************
+ *
+ * @file lv_indev_gesture.h
+ *
+ * Copyright (c) 2024 EDGEMTech Ltd.
+ *
+ * Author EDGEMTech Ltd, (erik.tagirov@edgemtech.ch)
+ *
+ ******************************************************************/
+
+#ifndef LV_INDEV_GESTURE_H
+#define LV_INDEV_GESTURE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../core/lv_obj.h"
+
+#if LV_USE_GESTURE_RECOGNITION
+
+#if LV_USE_FLOAT == 0
+#error "LV_USE_FLOAT is required for gesture detection."
+#endif
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/* Opaque types defined in the private header */
+struct lv_indev_gesture;
+struct lv_indev_gesture_configuration;
+
+typedef struct lv_indev_gesture lv_indev_gesture_t;
+typedef struct lv_indev_gesture_configuration lv_indev_gesture_configuration_t;
+
+/* The states of a gesture recognizer */
+typedef enum {
+ LV_INDEV_GESTURE_STATE_NONE = 0, /* Beginning & end */
+ LV_INDEV_GESTURE_STATE_ONGOING, /* Set when there is a probability */
+ LV_INDEV_GESTURE_STATE_RECOGNIZED, /* Recognized, the event will contain touch info */
+ LV_INDEV_GESTURE_STATE_ENDED, /* A recognized gesture has ended */
+ LV_INDEV_GESTURE_STATE_CANCELED, /* Canceled - usually a finger is lifted */
+} lv_indev_gesture_state_t;
+
+/* Data structures for touch events - used to repsensent a libinput event */
+/* Emitted by devices capable of tracking identifiable contacts (type B) */
+typedef struct {
+ lv_point_t point; /* Coordinates of the touch */
+ lv_indev_state_t state; /* The state i.e PRESSED or RELEASED */
+ uint8_t id; /* Identification/slot of the contact point */
+ uint32_t timestamp; /* Timestamp in milliseconds */
+} lv_indev_touch_data_t;
+
+/* Gesture recognizer */
+typedef struct {
+ lv_indev_gesture_type_t type; /* The detected gesture type */
+ lv_indev_gesture_state_t state; /* The gesture state ongoing, recognized */
+ lv_indev_gesture_t * info; /* Information on the motion of each touch point */
+ float scale; /* Relevant for the pinch gesture */
+ float rotation; /* Relevant for rotation */
+ float distance; /* Relevant for swipes */
+ float speed;
+
+ lv_indev_gesture_configuration_t * config;
+
+} lv_indev_gesture_recognizer_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+
+/* PINCH Gesture */
+
+/**
+ * Detects a pinch gesture
+ * @param recognizer pointer to a gesture recognizer
+ * @param touches pointer to the first element of the collected touch events
+ * @param touch_cnt length of passed touch event array.
+ */
+void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, lv_indev_touch_data_t * touches,
+ uint16_t touch_cnt);
+
+
+/**
+ * Set the threshold for the pinch gesture scale up, when the scale factor of gesture
+ * reaches the threshold events get sent
+ * @param recognizer pointer to a gesture recognizer
+ * @param touches pointer to the first element of the collected touch events
+ * @param touch_cnt length of passed touch event array.
+ */
+void lv_indev_set_pinch_up_threshold(lv_indev_gesture_recognizer_t * recognizer, float threshold);
+
+/**
+ * Set the threshold for the pinch gesture scale down, when the scale factor of gesture
+ * reaches the threshold events get sent
+ * @param recognizer pointer to a gesture recognizer
+ * @param touches pointer to the first element of the collected touch events
+ * @param touch_cnt length of passed touch event array.
+ */
+void lv_indev_set_pinch_down_threshold(lv_indev_gesture_recognizer_t * recognizer, float threshold);
+
+/**
+ * Obtains the current scale of a pinch gesture
+ * @param gesture_event pointer to a gesture recognizer event
+ * @return the scale of the current gesture
+ */
+float lv_event_get_pinch_scale(lv_event_t * gesture_event);
+
+/**
+ * Sets the state of the recognizer to a indev data structure,
+ * it is usually called from the indev read callback
+ * @param data the indev data
+ * @param recognizer pointer to a gesture recognizer
+ */
+void lv_indev_set_gesture_data(lv_indev_data_t * data, lv_indev_gesture_recognizer_t * recognizer);
+
+/**
+ * Obtains the center point of a gesture
+ * @param gesture_event pointer to a gesture recognizer event
+ * @param point pointer to a point
+ */
+void lv_indev_get_gesture_center_point(lv_indev_gesture_recognizer_t * recognizer, lv_point_t * point);
+
+/**
+ * Obtains the current state of the gesture recognizer attached to an event
+ * @param gesture_event pointer to a gesture recognizer event
+ * @return current state of the gesture recognizer
+ */
+lv_indev_gesture_state_t lv_event_get_gesture_state(lv_event_t * gesture_event);
+
+/**
+ * Obtains the coordinates of the current primary point
+ * @param recognizer pointer to a gesture recognizer
+ * @param point pointer to a point
+ */
+void lv_indev_get_gesture_primary_point(lv_indev_gesture_recognizer_t * recognizer, lv_point_t * point);
+
+/**
+ * Allows to determine if there is an are ongoing gesture
+ * @param recognizer pointer to a gesture recognizer
+ * @return false if there are no contact points, or the gesture has ended - true otherwise
+ */
+bool lv_indev_recognizer_is_active(lv_indev_gesture_recognizer_t * recognizer);
+
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /* END LV_USE_RECOGNITION */
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /* END LV_INDEV_GESTURE_H */
diff --git a/src/indev/lv_indev_gesture_private.h b/src/indev/lv_indev_gesture_private.h
new file mode 100644
index 000000000..77a51c712
--- /dev/null
+++ b/src/indev/lv_indev_gesture_private.h
@@ -0,0 +1,93 @@
+/*******************************************************************
+ *
+ * @file lv_indev_gesture_private.h
+ *
+ * Contains declarations and definition that are internal
+ * to the gesture detection logic
+ *
+ * Copyright (c) 2024 EDGEMTech Ltd.
+ *
+ * Author EDGEMTech Ltd, (erik.tagirov@edgemtech.ch)
+ *
+ ******************************************************************/
+
+#ifndef LV_INDEV_GESTURE_PRIVATE_H
+#define LV_INDEV_GESTURE_PRIVATE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../core/lv_obj.h"
+
+#if LV_USE_GESTURE_RECOGNITION
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define LV_GESTURE_MAX_POINTS 2
+
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/* Represent the motion of a finger */
+struct lv_indev_gesture_motion {
+ int8_t finger; /* The ID of the tracked finger */
+ lv_point_t start_point; /* The coordinates where the DOWN event occured */
+ lv_point_t point; /* The current coordinates */
+ lv_indev_state_t state; /* DEBUG: The state i.e PRESSED or RELEASED */
+};
+
+typedef struct lv_indev_gesture_motion lv_indev_gesture_motion_t;
+
+/* General descriptor for a gesture, used by recognizer state machines to track
+ * the scale, rotation, and translation NOTE: (this will likely become private) */
+struct lv_indev_gesture {
+
+ /* Motion descriptor, stores the coordinates and velocity of a contact point */
+ lv_indev_gesture_motion_t motions[LV_GESTURE_MAX_POINTS];
+
+ lv_point_t center; /* Center point */
+ float scale; /* Scale factor & previous scale factor */
+ float p_scale;
+ float scale_factors_x[LV_GESTURE_MAX_POINTS]; /* Scale factor relative to center for each point */
+ float scale_factors_y[LV_GESTURE_MAX_POINTS];
+
+ float delta_x; /* Translation & previous translation */
+ float delta_y;
+ float p_delta_x;
+ float p_delta_y;
+ float rotation; /* Rotation & previous rotation*/
+ float p_rotation;
+ uint8_t finger_cnt; /* Current number of contact points */
+
+};
+
+struct lv_indev_gesture_configuration {
+
+ float pinch_up_threshold; /* When the gesture reaches the threshold - start sending events */
+ float pinch_down_threshold; /* When the gesture reaches the threshold - start sending events */
+
+};
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /* END LV_USE_RECOGNITION */
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /* END LV_INDEV_GESTURE_PRIVATE_H */
diff --git a/src/indev/lv_indev_private.h b/src/indev/lv_indev_private.h
index 9bf980914..830fd4de9 100644
--- a/src/indev/lv_indev_private.h
+++ b/src/indev/lv_indev_private.h
@@ -112,6 +112,9 @@ struct _lv_indev_t {
here by the buttons*/
lv_event_list_t event_list;
lv_anim_t * scroll_throw_anim;
+
+ lv_indev_gesture_type_t gesture_type;
+ void * gesture_data;
};
/**********************
diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h
index 7da9d0a42..22c2c8b9f 100644
--- a/src/lv_conf_internal.h
+++ b/src/lv_conf_internal.h
@@ -1367,6 +1367,16 @@
#endif
#endif
+/* Enable the multi-touch gesture recognition feature */
+/* Gesture recognition requires the use of floats */
+#ifndef LV_USE_GESTURE_RECOGNITION
+ #ifdef CONFIG_LV_USE_GESTURE_RECOGNITION
+ #define LV_USE_GESTURE_RECOGNITION CONFIG_LV_USE_GESTURE_RECOGNITION
+ #else
+ #define LV_USE_GESTURE_RECOGNITION 0
+ #endif
+#endif
+
/*=====================
* COMPILER SETTINGS
*====================*/