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 *====================*/