diff --git a/docs/src/details/main-modules/indev.rst b/docs/src/details/main-modules/indev.rst index d07fa33da5..975ec6e72e 100644 --- a/docs/src/details/main-modules/indev.rst +++ b/docs/src/details/main-modules/indev.rst @@ -161,25 +161,44 @@ 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. +gesture occurred. Currently, these multi-touch gestures are supported: + +- Two fingers pinch (up and down) +- Two fingers rotation +- Two fingers swipe (infinite) To enable the multi-touch gesture recognition set the ``LV_USE_GESTURE_RECOGNITION`` option in the ``lv_conf.h`` file. -Touch event collection -~~~~~~~~~~~~~~~~~~~~~~ +Currently, the system sends the events if the gestures are in one of the following states: -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``. +- ``LV_INDEV_GESTURE_STATE_RECOGNIZED``: The gesture has been recognized and is now active. +- ``LV_INDEV_GESTURE_STATE_ENDED``: The gesture has ended. -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:: +Multi-touch gestures overview +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To recognize multi touch gestures, recognizers are used. The structure ``lv_indev_t`` contains +an array of recognizers, one per gesture type. These recognizers are initialized internally by ``lv_indev_create`` by calling +``lv_indev_gesture_init_recognizers`` after the indev device is created. The the recognizers can then be configured to +modify the gestures thresholds. These thresholds are used to be able to recognize the gesture only after the threshold +have been reached. They can be set-up like this: + +- ``lv_indev_set_pinch_up_threshold(lv_indev_t * indev, float threshold)``: Set the pinch up (zoom in) threshold in pixels. +- ``lv_indev_set_pinch_down_threshold(lv_indev_t * indev, float threshold)``: Set the pinch down (zoom out) threshold in pixels. +- ``lv_indev_set_rotation_rad_threshold(lv_indev_t * indev, float threshold)``: Set the rotation angle threshold in radians. + +The recognizers can then be updated to recognize the gestures by calling ``lv_indev_gesture_recognizers_update``. +This must be done in the user defined indev ``read_cb``. This will iterate over the recognizers and stop once it detects a +recognized or ended gesture. For now only one multi-touch gesture can be recognized/ended at a time. + +Once the recognizers are updated, calling ``lv_indev_gesture_recognizers_set_data`` will update the ``lv_indev_data_t`` structure. +It is meant to be done in the indev ``read_cb``. This allows the future ``lv_event_t`` to eb filled with multi-touch gesture info. + +Here is an example of the ``read_cb``: + +.. code-block:: c /* The recognizer keeps the state of the gesture */ static lv_indev_gesture_recognizer_t recognizer; @@ -194,34 +213,146 @@ and ``gesture_type`` fields of the structure ``lv_indev_data_t`` { lv_indev_touch_data_t * touch; - uint8_t i; - - touch = &touches[0]; - lv_indev_gesture_detect_pinch(recognizer, &touches[0], - touch_cnt); + lv_indev_update_recognizers(drv, &touches[0], touch_cnt); touch_cnt = 0; /* Set the gesture information, before returning to LVGL */ - lv_indev_set_gesture_data(data, recognizer); + lv_indev_gesture_recognizers_set_data(drv, data); } -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 +The user is in charge of collecting the necessary touches events from the driver until the indev ``read_cb`` is called. +It must then convert the specific driver input to ``lv_indev_touch_data_t`` to be processed by the ``read_cb`` at a later point. +Here is an example using ``libinput``: -Handling touch events -~~~~~~~~~~~~~~~~~~~~~ +.. code-block:: c -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. + /** + * @brief Convert the libinput to lvgl's representation of touch event + * @param ev a pointer to the lib input event + */ + static void touch_event_queue_add(struct libinput_event *ev) + { + struct libinput_event_touch *touch_ev; + lv_indev_touch_data_t *cur; + lv_indev_touch_data_t *t; + uint32_t time; + int i; + int id; + int type; -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. + type = libinput_event_get_type(ev); + touch_ev = libinput_event_get_touch_event(ev); + id = libinput_event_touch_get_slot(touch_ev); + time = libinput_event_touch_get_time(touch_ev); + + /* Get the last event for contact point */ + t = &touches[0]; + cur = NULL; + + for (i = 0; i < touch_cnt; i++) { + if (t->id == id) { + cur = t; + } + t++; + } + + if (cur != NULL && cur->timestamp == time) { + /* Previous event has the same timestamp - ignore duplicate event */ + return; + } + + if (cur == NULL || + type == LIBINPUT_EVENT_TOUCH_UP || + type == LIBINPUT_EVENT_TOUCH_DOWN) { + + /* create new event */ + cur = &touches[touch_cnt]; + touch_cnt++; + } + + switch (type) { + case LIBINPUT_EVENT_TOUCH_DOWN: + case LIBINPUT_EVENT_TOUCH_MOTION: + + cur->point.x = (int) libinput_event_touch_get_x_transformed(touch_ev, SCREEN_WIDTH); + cur->point.y = (int) libinput_event_touch_get_y_transformed(touch_ev, SCREEN_HEIGHT); + cur->state = LV_INDEV_STATE_PRESSED; + break; + + case LIBINPUT_EVENT_TOUCH_UP: + + cur->state = LV_INDEV_STATE_RELEASED; + cur->point.x = 0; + cur->point.y = 0; + break; + } + + cur->timestamp = time; + cur->id = id; + } + + /** + * @brief Filter out libinput events that are not related to touches + * @param ev a pointer to the lib input event + */ + static void process_libinput_event(struct libinput_event *ev) + { + int type; + + type = libinput_event_get_type(ev); + + switch (type) { + case LIBINPUT_EVENT_TOUCH_MOTION: + case LIBINPUT_EVENT_TOUCH_DOWN: + case LIBINPUT_EVENT_TOUCH_UP: + /* Filter only touch events */ + touch_event_queue_add(ev); + break; + default: + /* Skip an unrelated libinput event */ + return; + } + } + + +From this setup, the user can now register events callbacks to react to ``LV_EVENT_GESTURE``. + +.. note:: + 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 multi-touch gesture events +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once a gesture is recognized or ended, a ``LV_EVENT_GESTURE`` is sent. The user can the use these functions to +gather more information about the gesture: + +- ``lv_event_get_gesture_type(lv_event_t * gesture_event)``: Get the type of the gesture. To be +used to check which multi-touch gesture is currently reported. +- ``lv_indev_gesture_state_t lv_event_get_gesture_state(lv_event_t * gesture_event, lv_indev_gesture_type_t type)``: Get the +state of the gesture. It can be one of those: + + - ``LV_INDEV_GESTURE_STATE_NONE``: The gesture is not active. + - ``LV_INDEV_GESTURE_STATE_RECOGNIZED``: The gesture is recognized and can be used. + - ``LV_INDEV_GESTURE_STATE_ENDED``: The gesture ended. + +These functions allow the user to confirm the gesture is the expected one and that it is in a usable state. +The user can then request the gestures values with the following functions: + +- ``lv_event_get_pinch_scale(lv_event_t * gesture_event)``: Get the pinch scale. Only relevant for pinch gesture. +- ``lv_event_get_rotation(lv_event_t * gesture_event)``: Get the rotation in radians. Only relevant for rotation gesture. +- ``lv_event_get_two_fingers_swipe_distance(lv_event_t * gesture_event)``: Get the distance in pixels from the gesture staring center. + Only relevant for two fingers swipe gesture. +- ``lv_event_get_two_fingers_swipe_dir(lv_event_t * gesture_event)``: Get the direction from the starting center. Only relevant for + two fingers swipe gesture. + +This allow the user to react to the gestures and to use the gestures values. An example of such an application is available in +the source tree ``examples/others/gestures/lv_example_gestures.c``. -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 index 517142bacf..f035cd91b5 100644 --- a/examples/others/gestures/lv_example_gestures.c +++ b/examples/others/gestures/lv_example_gestures.c @@ -6,8 +6,10 @@ * 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, + * The application starts with a single rectangle. The user can then + * - Pinch the rectangle to scale it + * - Rotate the rectangle to rotate it + * - Two fingers swipe the rectangle to see the direction and the distance of the swipe * * Copyright (c) 2024 EDGEMTech Ltd * @@ -26,10 +28,14 @@ * DEFINES *********************/ -#define RECT_INIT_WIDTH 300.0 -#define RECT_INIT_HEIGHT 300.0 +#define RECT_INIT_WIDTH 300 +#define RECT_INIT_HEIGHT 300 #define RECT_COLOR 0xC1BCFF +#ifndef M_PI + #define M_PI 3.1415926f +#endif + /********************** * TYPEDEFS **********************/ @@ -39,7 +45,8 @@ **********************/ static void label_scale(lv_event_t * gesture_event); -static void label_move(lv_event_t * event); +static void label_rotate(lv_event_t * gesture_event); +static void label_swipe(lv_event_t * gesture_event); /********************** * STATIC VARIABLES @@ -47,10 +54,10 @@ static void label_move(lv_event_t * event); 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; +static uint32_t label_width; +static uint32_t label_height; +static uint32_t label_x; +static uint32_t label_y; /********************** * MACROS @@ -60,6 +67,7 @@ static float label_y; * GLOBAL FUNCTIONS **********************/ + /** * Entry point it creates the screen, and the label * Set event callbacks on the label @@ -70,15 +78,16 @@ void lv_example_gestures(void) label_width = RECT_INIT_WIDTH; label_height = RECT_INIT_HEIGHT; - label_y = label_x = 300; + label_x = LV_HOR_RES / 2 - (label_width / 2); + label_y = LV_VER_RES / 2 - (label_height / 2); root_view = lv_screen_active(); - lv_obj_set_style_bg_color(root_view, lv_color_hex(0xFFFFFF), LV_PART_MAIN); + 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_label_set_text(label, "Zoom, rotate or move"); lv_obj_add_flag(label, LV_OBJ_FLAG_CLICKABLE); lv_style_init(&label_style); @@ -93,9 +102,9 @@ void lv_example_gestures(void) lv_obj_add_style(label, &label_style, LV_STATE_DEFAULT); + lv_obj_add_event_cb(label, label_rotate, LV_EVENT_GESTURE, label); lv_obj_add_event_cb(label, label_scale, LV_EVENT_GESTURE, label); - lv_obj_add_event_cb(label, label_move, LV_EVENT_PRESSING, label); - + lv_obj_add_event_cb(label, label_swipe, LV_EVENT_GESTURE, label); } /********************** @@ -103,8 +112,56 @@ void lv_example_gestures(void) **********************/ /** - * Called when a pinch event occurs - scales the label - * @param gesture_event point to a LV_EVENT_PINCH event + * Called when a LV_EVENT_GESTURE event occurs - update the label if the gesture is a swipe + * @param gesture_event pointer to a LV_EVENT_GESTURE event + */ +static void label_swipe(lv_event_t * gesture_event) +{ + + lv_dir_t dir; + lv_indev_gesture_state_t state; + char * text; + + if(lv_event_get_gesture_type(gesture_event) != LV_INDEV_GESTURE_TWO_FINGERS_SWIPE) { + return; + } + + state = lv_event_get_gesture_state(gesture_event, LV_INDEV_GESTURE_TWO_FINGERS_SWIPE); + dir = lv_event_get_two_fingers_swipe_dir(gesture_event); + + if(state == LV_INDEV_GESTURE_STATE_ENDED) { + + text = "NONE"; + lv_label_set_text(label, text); + } + else if(state == LV_INDEV_GESTURE_STATE_RECOGNIZED) { + + switch(dir) { + case LV_DIR_LEFT: + text = "LEFT"; + break; + case LV_DIR_RIGHT: + text = "RIGHT"; + break; + case LV_DIR_TOP: + text = "TOP"; + break; + case LV_DIR_BOTTOM: + text = "BOTTOM"; + break; + default: + text = "???"; + break; + } + + lv_label_set_text_fmt(label, "%s - %f", text, lv_event_get_two_fingers_swipe_distance(gesture_event)); + } + +} + +/** + * Called when a LV_EVENT_GESTURE event occurs - scales the label if the gesture is a pinch + * @param gesture_event pointer to a LV_EVENT_GESTURE event */ static void label_scale(lv_event_t * gesture_event) { @@ -112,87 +169,115 @@ 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; + static lv_point_t center_pnt; + static float base_scale = 1.0; float scale; + float label_width_float; + float label_height_float; - scale = lv_event_get_pinch_scale(gesture_event); - state = lv_event_get_gesture_state(gesture_event); + /* Ensure the gesture is a pinch */ + if(lv_event_get_gesture_type(gesture_event) != LV_INDEV_GESTURE_PINCH) { + return; + } - lv_indev_get_point(lv_indev_active(), ¢er_pnt); + state = lv_event_get_gesture_state(gesture_event, LV_INDEV_GESTURE_PINCH); + scale = base_scale * lv_event_get_pinch_scale(gesture_event); + + /* Reset state when the gesture ended */ 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_USER("label end scale: %g, state: %d", scale, state); + base_scale = scale; + return; } - if(initial_h == -1 || initial_w == -1) { + /* The first time the gesture is recognized, save its center */ + if(state == LV_INDEV_GESTURE_STATE_RECOGNIZED) { - LV_ASSERT(state == LV_INDEV_GESTURE_STATE_RECOGNIZED); + if((initial_h == -1 || initial_w == -1)) { - /* 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 = (int)label_width; - initial_h = (int)label_height; + /* 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; + center_pnt.x = lv_obj_get_x(label) + label_width / 2; + center_pnt.y = lv_obj_get_y(label) + label_height / 2; + } - LV_LOG_USER("label start scale: %g", scale); + /* The gesture is recognized, we can now use the scale */ + + /* Avoids a situation where the rectangle becomes too small or too big, + * adding limits */ + if(scale < 0.4f) { + scale = 0.4f; + } + else if(scale > 2.0f) { + scale = 2.0f; + } + + label_x = center_pnt.x - label_width / 2; + label_y = center_pnt.y - label_height / 2; + + label_width_float = (float)RECT_INIT_WIDTH * scale; + label_height_float = (float)RECT_INIT_HEIGHT * scale; + + /* Update position and size */ + lv_style_set_width(&label_style, (int)label_width_float); + lv_style_set_height(&label_style, (int)label_height_float); + 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); + + label_width = (int)label_width_float; + label_height = (int)label_height_float; } - - /* 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_USER("label scale: %g label x: %g label y: %g w: %g h: %g", - 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 + * Called when a LV_EVENT_GESTURE event occurs - rotate the label if the gesture is a rotation + * @param gesture_event pointer to a LV_EVENT_GESTURE event */ -static void label_move(lv_event_t * event) +static void label_rotate(lv_event_t * gesture_event) { - lv_point_t pnt; + + float angle_degrees = 0.f; + static float start_angle = 0.f; 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) { + if(lv_event_get_gesture_type(gesture_event) != LV_INDEV_GESTURE_ROTATE) { return; } - LV_LOG_USER("label move x: %" LV_PRId32 ", y: %" LV_PRId32, pnt.x, pnt.y); + state = lv_event_get_gesture_state(gesture_event, LV_INDEV_GESTURE_ROTATE); - label_x = pnt.x - label_width / 2; - label_y = pnt.y - label_height / 2; + /* Calculate new angle. The x10 is due to lv_obj_set_style_transform_rotation using x10 angle in parameter */ + angle_degrees = start_angle + 10.0f * (lv_event_get_rotation(gesture_event) * 180.0f / M_PI); - /* Update position */ - lv_style_set_x(&label_style, (int)label_x); - lv_style_set_y(&label_style, (int)label_y); + /* Once the gesture ends, save the current angle to the start_angle */ + if(state == LV_INDEV_GESTURE_STATE_ENDED) { - lv_obj_add_style(label, &label_style, LV_STATE_DEFAULT); + start_angle = angle_degrees; + } + + /* If the gesture is recognized, rotate the label */ + if(state == LV_INDEV_GESTURE_STATE_RECOGNIZED) { + + /* Need to set the pivot to the center of the widget to not rotate + around the top-left corner */ + lv_obj_set_style_transform_pivot_x(label, lv_obj_get_width(label) / 2, 0); + lv_obj_set_style_transform_pivot_y(label, lv_obj_get_height(label) / 2, 0); + + lv_obj_set_style_transform_rotation(label, (int) angle_degrees, 0); + } + + return; } #endif /* LV_USE_GESTURE_RECOGNITION && LV_USE_FLOAT */ diff --git a/src/drivers/wayland/lv_wayland.c b/src/drivers/wayland/lv_wayland.c index 8b85ae5a04..b4afbd64f7 100644 --- a/src/drivers/wayland/lv_wayland.c +++ b/src/drivers/wayland/lv_wayland.c @@ -120,7 +120,6 @@ struct input { lv_indev_touch_data_t touches[10]; uint8_t touch_event_cnt; uint8_t primary_id; - lv_indev_gesture_recognizer_t recognizer; #endif }; @@ -2382,24 +2381,21 @@ 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_gesture_recognizer_t * recognizer; if(!window || window->closed) { return; } /* Collect touches if there are any - send them to the gesture recognizer */ - recognizer = &window->body->input.recognizer; + lv_indev_gesture_recognizers_update(drv, &window->body->input.touches[0], + window->body->input.touch_event_cnt); 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); + lv_indev_gesture_recognizers_set_data(drv, data); } diff --git a/src/indev/lv_indev.c b/src/indev/lv_indev.c index a9b09dad12..84ac23f748 100644 --- a/src/indev/lv_indev.c +++ b/src/indev/lv_indev.c @@ -81,6 +81,7 @@ static void indev_gesture(lv_indev_t * indev); static bool indev_reset_check(lv_indev_t * indev); static void indev_read_core(lv_indev_t * indev, lv_indev_data_t * data); static void indev_reset_core(lv_indev_t * indev, lv_obj_t * obj); +static void indev_init_gesture_recognizers(lv_indev_t * indev); static lv_result_t send_event(lv_event_code_t code, void * param); static void indev_scroll_throw_anim_start(lv_indev_t * indev); @@ -141,6 +142,9 @@ lv_indev_t * lv_indev_create(void) indev->gesture_limit = LV_INDEV_DEF_GESTURE_LIMIT; indev->gesture_min_velocity = LV_INDEV_DEF_GESTURE_MIN_VELOCITY; indev->rotary_sensitivity = LV_INDEV_DEF_ROTARY_SENSITIVITY; + + indev_init_gesture_recognizers(indev); + return indev; } @@ -729,8 +733,12 @@ 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; +#if LV_USE_GESTURE_RECOGNITION + for(int gest = 0; gest < LV_INDEV_GESTURE_CNT; gest++) { + i->gesture_type[gest] = data->gesture_type[gest]; + i->gesture_data[gest] = data->gesture_data[gest]; + } +#endif /*Process the diff first as scrolling will be processed in indev_proc_release*/ indev_proc_pointer_diff(i); @@ -1296,19 +1304,30 @@ 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 LV_USE_GESTURE_RECOGNITION + for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) { + /* Send a gesture event to a potential indev cb callback, even if no object was found */ + if(indev->gesture_type[i] != LV_INDEV_GESTURE_NONE) { + indev->cur_gesture = (lv_indev_gesture_type_t) i; + lv_indev_send_event(indev, LV_EVENT_GESTURE, indev_act); + break; + } } +#endif 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 LV_USE_GESTURE_RECOGNITION + for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) { + if(indev->gesture_type[i] != LV_INDEV_GESTURE_NONE) { + indev->cur_gesture = (lv_indev_gesture_type_t) i; + if(send_event(LV_EVENT_GESTURE, indev_act) == LV_RESULT_INVALID) return; + break; + } + } +#endif if(is_enabled) { if(send_event(LV_EVENT_PRESSING, indev_act) == LV_RESULT_INVALID) return; } @@ -1400,20 +1419,32 @@ static void indev_proc_release(lv_indev_t * indev) lv_timer_pause(indev->read_timer); } +#if LV_USE_GESTURE_RECOGNITION /* 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); + for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) { + + if(indev->gesture_type[i] != LV_INDEV_GESTURE_NONE) { + indev_act->cur_gesture = (lv_indev_gesture_type_t) i; + lv_indev_send_event(indev, LV_EVENT_GESTURE, indev_act); + break; + } } +#endif 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 LV_USE_GESTURE_RECOGNITION + for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) { + if(is_enabled && indev->gesture_type[i] != LV_INDEV_GESTURE_NONE) { + indev_act->cur_gesture = (lv_indev_gesture_type_t) i; + if(send_event(LV_EVENT_GESTURE, indev_act) == LV_RESULT_INVALID) return; + break; + } } - +#endif if(is_enabled) { if(send_event(LV_EVENT_RELEASED, indev_act) == LV_RESULT_INVALID) return; } @@ -1851,3 +1882,21 @@ static void indev_scroll_throw_anim_start(lv_indev_t * indev) indev->scroll_throw_anim = lv_anim_start(&a); } + +/** + * Initialize this indev's recognizers. It specify their recognizer function + * @param indev pointer to the indev containing the recognizers to initialize + */ +static void indev_init_gesture_recognizers(lv_indev_t * indev) +{ +#if LV_USE_GESTURE_RECOGNITION + indev->recognizers[LV_INDEV_GESTURE_NONE].recog_fn = NULL; + indev->recognizers[LV_INDEV_GESTURE_PINCH].recog_fn = lv_indev_gesture_detect_pinch; + indev->recognizers[LV_INDEV_GESTURE_ROTATE].recog_fn = lv_indev_gesture_detect_rotation; + indev->recognizers[LV_INDEV_GESTURE_TWO_FINGERS_SWIPE].recog_fn = lv_indev_gesture_detect_two_fingers_swipe; + indev->recognizers[LV_INDEV_GESTURE_SCROLL].recog_fn = NULL; + indev->recognizers[LV_INDEV_GESTURE_SWIPE].recog_fn = NULL; +#else + LV_UNUSED(indev); +#endif +} diff --git a/src/indev/lv_indev.h b/src/indev/lv_indev.h index 6bdf9b1c5a..d6c60c4049 100644 --- a/src/indev/lv_indev.h +++ b/src/indev/lv_indev.h @@ -54,6 +54,7 @@ typedef enum { LV_INDEV_GESTURE_PINCH, LV_INDEV_GESTURE_SWIPE, LV_INDEV_GESTURE_ROTATE, + LV_INDEV_GESTURE_TWO_FINGERS_SWIPE, LV_INDEV_GESTURE_SCROLL, /* Used with scrollwheels */ LV_INDEV_GESTURE_CNT, /* Total number of gestures types */ } lv_indev_gesture_type_t; @@ -68,8 +69,8 @@ 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_gesture_type_t gesture_type[LV_INDEV_GESTURE_CNT]; /* Current gesture types, per gesture */ + void * gesture_data[LV_INDEV_GESTURE_CNT]; /* Used to store data per gesture */ } lv_indev_data_t; diff --git a/src/indev/lv_indev_gesture.c b/src/indev/lv_indev_gesture.c index 3ea34ca8b6..d278873145 100644 --- a/src/indev/lv_indev_gesture.c +++ b/src/indev/lv_indev_gesture.c @@ -29,6 +29,7 @@ #define LV_GESTURE_PINCH_DOWN_THRESHOLD 0.75f /* Default value - start sending events when reached */ #define LV_GESTURE_PINCH_UP_THRESHOLD 1.5f /* Default value - start sending events when reached */ #define LV_GESTURE_PINCH_MAX_INITIAL_SCALE 2.5f /* Default value */ +#define LV_GESTURE_ROTATION_ANGLE_RAD_THRESHOLD 0.2f /* Default value - start sending events when reached */ /******************** @@ -46,7 +47,10 @@ static void process_touch_event(lv_indev_touch_data_t * touch, lv_indev_gesture_ 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 lv_indev_gesture_recognizer_t * lv_indev_get_gesture_recognizer(lv_event_t * gesture_event, + lv_indev_gesture_type_t type); +static lv_dir_t calculate_swipe_dir(lv_indev_gesture_recognizer_t * recognizer); +static lv_indev_gesture_type_t get_first_recognized_or_ended_gesture(lv_indev_t * indev); /******************** * STATIC VARIABLES @@ -55,16 +59,20 @@ static lv_indev_gesture_recognizer_t * lv_indev_get_gesture_recognizer(lv_event_ /******************** * MACROS ********************/ +#define SQUARE(x) ((x) * (x)) +#define SQUARE_SUM(x, y) (SQUARE(x) + SQUARE(y)) /******************** * GLOBAL FUNCTIONS ********************/ -void lv_indev_set_pinch_up_threshold(lv_indev_gesture_recognizer_t * recognizer, float threshold) +void lv_indev_set_pinch_up_threshold(lv_indev_t * indev, float threshold) { /* A up threshold MUST always be bigger than 1 */ LV_ASSERT(threshold > 1.0f); + lv_indev_gesture_recognizer_t * recognizer = &indev->recognizers[LV_INDEV_GESTURE_PINCH]; + if(recognizer->config == NULL) { recognizer->config = lv_malloc_zeroed(sizeof(lv_indev_gesture_configuration_t)); LV_ASSERT_MALLOC(recognizer->config); @@ -74,11 +82,13 @@ void lv_indev_set_pinch_up_threshold(lv_indev_gesture_recognizer_t * recognizer, recognizer->config->pinch_up_threshold = threshold; } -void lv_indev_set_pinch_down_threshold(lv_indev_gesture_recognizer_t * recognizer, float threshold) +void lv_indev_set_pinch_down_threshold(lv_indev_t * indev, float threshold) { /* A down threshold MUST always be smaller than 1 */ LV_ASSERT(threshold < 1.0f); + lv_indev_gesture_recognizer_t * recognizer = &indev->recognizers[LV_INDEV_GESTURE_PINCH]; + if(recognizer->config == NULL) { recognizer->config = lv_malloc_zeroed(sizeof(lv_indev_gesture_configuration_t)); LV_ASSERT_MALLOC(recognizer->config); @@ -88,6 +98,23 @@ void lv_indev_set_pinch_down_threshold(lv_indev_gesture_recognizer_t * recognize recognizer->config->pinch_down_threshold = threshold; } +void lv_indev_set_rotation_rad_threshold(lv_indev_t * indev, float threshold) +{ + /* A rotation threshold MUST always be a positive number */ + LV_ASSERT(threshold > 0.0f); + + lv_indev_gesture_recognizer_t * recognizer = &indev->recognizers[LV_INDEV_GESTURE_ROTATE]; + + if(recognizer->config == NULL) { + + recognizer->config = lv_malloc_zeroed(sizeof(lv_indev_gesture_configuration_t)); + LV_ASSERT(recognizer->config != NULL); + recognizer->config->rotation_angle_rad_threshold = LV_GESTURE_ROTATION_ANGLE_RAD_THRESHOLD; + } + + recognizer->config->rotation_angle_rad_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) { @@ -114,13 +141,48 @@ bool lv_indev_recognizer_is_active(lv_indev_gesture_recognizer_t * recognizer) 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) { + + if((recognizer = lv_indev_get_gesture_recognizer(gesture_event, LV_INDEV_GESTURE_PINCH)) == NULL) { return 0.0f; } return recognizer->scale; } +float lv_event_get_rotation(lv_event_t * gesture_event) +{ + lv_indev_gesture_recognizer_t * recognizer; + + if((recognizer = lv_indev_get_gesture_recognizer(gesture_event, LV_INDEV_GESTURE_ROTATE)) == NULL) { + return 0.0f; + } + + return recognizer->rotation; +} + +float lv_event_get_two_fingers_swipe_distance(lv_event_t * gesture_event) +{ + lv_indev_gesture_recognizer_t * recognizer; + + if((recognizer = lv_indev_get_gesture_recognizer(gesture_event, LV_INDEV_GESTURE_TWO_FINGERS_SWIPE)) == NULL) { + return 0.0f; + } + + return recognizer->distance; +} + +lv_dir_t lv_event_get_two_fingers_swipe_dir(lv_event_t * gesture_event) +{ + + lv_indev_gesture_recognizer_t * recognizer; + + if((recognizer = lv_indev_get_gesture_recognizer(gesture_event, LV_INDEV_GESTURE_TWO_FINGERS_SWIPE)) == NULL) { + return LV_DIR_NONE; + } + + return recognizer->two_fingers_swipe_dir; +} + 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) { @@ -133,18 +195,30 @@ void lv_indev_get_gesture_center_point(lv_indev_gesture_recognizer_t * recognize point->y = recognizer->info->center.y; } -lv_indev_gesture_state_t lv_event_get_gesture_state(lv_event_t * gesture_event) +lv_indev_gesture_state_t lv_event_get_gesture_state(lv_event_t * gesture_event, lv_indev_gesture_type_t type) { lv_indev_gesture_recognizer_t * recognizer; - if((recognizer = lv_indev_get_gesture_recognizer(gesture_event)) == NULL) { + + if((recognizer = lv_indev_get_gesture_recognizer(gesture_event, type)) == NULL) { return LV_INDEV_GESTURE_STATE_NONE; } return recognizer->state; } +lv_indev_gesture_type_t lv_event_get_gesture_type(lv_event_t * gesture_event) +{ + lv_indev_t * indev = (lv_indev_t *) gesture_event->param; -void lv_indev_set_gesture_data(lv_indev_data_t * data, lv_indev_gesture_recognizer_t * recognizer) + if(indev == NULL) { + return LV_INDEV_GESTURE_NONE; + } + + return indev->cur_gesture; +} + +void lv_indev_set_gesture_data(lv_indev_data_t * data, lv_indev_gesture_recognizer_t * recognizer, + lv_indev_gesture_type_t type) { bool is_active; lv_point_t cur_pnt; @@ -161,8 +235,8 @@ void lv_indev_set_gesture_data(lv_indev_data_t * data, lv_indev_gesture_recogniz data->point.x = cur_pnt.x; data->point.y = cur_pnt.y; - data->gesture_type = LV_INDEV_GESTURE_NONE; - data->gesture_data = NULL; + data->gesture_type[type] = LV_INDEV_GESTURE_NONE; + data->gesture_data[type] = 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 */ @@ -180,13 +254,13 @@ void lv_indev_set_gesture_data(lv_indev_data_t * data, lv_indev_gesture_recogniz 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; + data->gesture_type[type] = type; + data->gesture_data[type] = (void *) recognizer; break; case LV_INDEV_GESTURE_STATE_ENDED: - data->gesture_type = LV_INDEV_GESTURE_PINCH; - data->gesture_data = (void *) recognizer; + data->gesture_type[type] = type; + data->gesture_data[type] = (void *) recognizer; break; default: @@ -222,7 +296,7 @@ void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, l process_touch_event(touch, r->info); touches++; - LV_LOG_TRACE("processed touch ev: %d finger id: %d state: %d x: %" LV_PRId32 " y: %" LV_PRId32 " finger_cnt: %d", + LV_LOG_TRACE("processed touch ev: %d finger id: %d state: %d x: %" LV_PRId32 " y: %" LV_PRId32 " finger_cnt: %d", i, touch->id, touch->state, touch->point.x, touch->point.y, r->info->finger_cnt); } @@ -230,8 +304,6 @@ void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, l 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 */ @@ -239,38 +311,28 @@ void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, l 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) { + if(r->info->scale > LV_GESTURE_PINCH_MAX_INITIAL_SCALE) { r->state = LV_INDEV_GESTURE_STATE_CANCELED; break; } - - LV_ASSERT_NULL(r->config); - if(r->info->scale > r->config->pinch_up_threshold || r->info->scale < r->config->pinch_down_threshold) { - - if(r->info->scale > 1.0f) { - r->scale = r->info->scale - (r->config->pinch_up_threshold - 1.0f); - - } - else if(r->info->scale < 1.0f) { - - r->scale = r->info->scale + (1.0f - r->config->pinch_down_threshold); - } - - r->type = LV_INDEV_GESTURE_PINCH; r->state = LV_INDEV_GESTURE_STATE_RECOGNIZED; } break; - + case LV_INDEV_GESTURE_STATE_RECOGNIZED: + /* It's an ongoing pinch gesture - update the factors */ + gesture_calculate_factors(r->info, 2); + LV_ASSERT(r->info != NULL); + r->scale = r->info->scale; + r->type = LV_INDEV_GESTURE_PINCH; + break; + case LV_INDEV_GESTURE_STATE_ENDED: + case LV_INDEV_GESTURE_STATE_CANCELED: + reset_recognizer(r); + break; default: LV_ASSERT_MSG(true, "invalid gesture recognizer state"); } @@ -280,17 +342,13 @@ void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, l 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: + case LV_INDEV_GESTURE_STATE_CANCELED: reset_recognizer(r); break; @@ -300,16 +358,296 @@ void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, l } } +void lv_indev_gesture_detect_rotation(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_zeroed(sizeof(lv_indev_gesture_configuration_t)); + + LV_ASSERT(r->config != NULL); + } + + /* 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: %" LV_PRId32 " y: %" LV_PRId32 " 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_NONE: + /* 2 fingers down - potential rotation 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: + /* Update the rotation from the inputs */ + gesture_calculate_factors(r->info, 2); + if(fabs(r->info->rotation - r->info->p_rotation) > r->config->rotation_angle_rad_threshold) { + + gesture_update_center_point(r->info, 2); + r->state = LV_INDEV_GESTURE_STATE_RECOGNIZED; + } + break; + case LV_INDEV_GESTURE_STATE_RECOGNIZED: + /* It's a recognized rotation gesture - update the factors */ + gesture_calculate_factors(r->info, 2); + r->type = LV_INDEV_GESTURE_ROTATE; + r->rotation = r->info->rotation; + break; + case LV_INDEV_GESTURE_STATE_ENDED: + case LV_INDEV_GESTURE_STATE_CANCELED: + reset_recognizer(r); + r->type = LV_INDEV_GESTURE_NONE; + r->state = LV_INDEV_GESTURE_STATE_CANCELED; + break; + default: + LV_ASSERT_MSG(true, "invalid gesture recognizer state"); + } + } + else { + switch(r->state) { + case LV_INDEV_GESTURE_STATE_RECOGNIZED: + /* Gesture has ended */ + r->type = LV_INDEV_GESTURE_ROTATE; + r->state = LV_INDEV_GESTURE_STATE_ENDED; + break; + case LV_INDEV_GESTURE_STATE_ONGOING: + /* User lifted a finger before reaching threshold */ + 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"); + } + } +} + +void lv_indev_gesture_detect_two_fingers_swipe(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; + float dist; + + 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_zeroed(sizeof(lv_indev_gesture_configuration_t)); + + LV_ASSERT(r->config != NULL); + } + + /* 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: %" LV_PRId32 " y: %" LV_PRId32 " 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_NONE: + /* 2 fingers down - potential rotation 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: + /* The gesture is ongoing, now wait for the distance from the center + to be higher than the threshold to pass it as recognized */ + gesture_calculate_factors(r->info, 2); + dist = SQUARE_SUM(r->info->delta_x, r->info->delta_y); + if(dist > SQUARE(lv_indev_active()->gesture_limit)) { + r->state = LV_INDEV_GESTURE_STATE_RECOGNIZED; + } + break; + case LV_INDEV_GESTURE_STATE_RECOGNIZED: + /* The gesture is now recognized, and will stay recognized + until a finger is lifted */ + gesture_calculate_factors(r->info, 2); + r->distance = (float) sqrt(SQUARE_SUM(r->info->delta_x, r->info->delta_y)); + r->two_fingers_swipe_dir = calculate_swipe_dir(r); + r->type = LV_INDEV_GESTURE_TWO_FINGERS_SWIPE; + break; + case LV_INDEV_GESTURE_STATE_ENDED: + case LV_INDEV_GESTURE_STATE_CANCELED: + reset_recognizer(r); + 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_TWO_FINGERS_SWIPE; + break; + case LV_INDEV_GESTURE_STATE_ONGOING: + /* User lifted a finger before reaching threshold */ + reset_recognizer(r); + r->state = LV_INDEV_GESTURE_STATE_ENDED; + break; + case LV_INDEV_GESTURE_STATE_CANCELED: + case LV_INDEV_GESTURE_STATE_ENDED: + reset_recognizer(r); + r->state = LV_INDEV_GESTURE_STATE_NONE; + break; + default: + LV_ASSERT_MSG(true, "invalid gesture recognizer state"); + } + } +} + +void lv_indev_gesture_recognizers_update(lv_indev_t * indev, lv_indev_touch_data_t * touches, uint16_t touch_cnt) +{ + lv_indev_gesture_type_t type; + + /* First check if a recognizer state is RECOGNIZED or ENDED. * + * In that case, call its recongizer function and reset the other*/ + type = get_first_recognized_or_ended_gesture(indev); + if(type != LV_INDEV_GESTURE_NONE) { + + for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) { + + if(indev->recognizers[i].recog_fn != NULL) { + + /* Update all recognizers to let them process input */ + indev->recognizers[i].recog_fn(&indev->recognizers[i], &touches[0], touch_cnt); + + /* Then reset the recognizers which did not repport RECONIZED or ENDED */ + if(((lv_indev_gesture_type_t)i) != type) { + + reset_recognizer(&indev->recognizers[i]); + } + } + } + + } + else { + + /* Otherwise call all recognizer functions, and stop as soon as one recognizer * + * reports the state RECOGNIZED or ENDED */ + for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) { + + if(indev->recognizers[i].recog_fn != NULL) { + + indev->recognizers[i].recog_fn(&indev->recognizers[i], &touches[0], touch_cnt); + + /* If the new state is RECOGNIZED or ENDED */ + if(indev->recognizers[i].state == LV_INDEV_GESTURE_STATE_RECOGNIZED || + indev->recognizers[i].state == LV_INDEV_GESTURE_STATE_ENDED) { + + /* Reset the others registered recognizers */ + for(int j = 0; j < LV_INDEV_GESTURE_CNT; j++) { + + if(j != i && indev->recognizers[j].recog_fn != NULL) { + + reset_recognizer(&indev->recognizers[j]); + } + } + + break; + } + } + } + } +} + +void lv_indev_gesture_recognizers_set_data(lv_indev_t * indev, lv_indev_data_t * data) +{ + lv_indev_gesture_type_t type; + type = get_first_recognized_or_ended_gesture(indev); + + /* If a gesture is RECOGNIZED or ENDED, set only its data */ + if(type != LV_INDEV_GESTURE_NONE) { + lv_indev_set_gesture_data(data, &indev->recognizers[type], type); + } + else { + /* Otherwise, set data from all initialized recognizer */ + for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) { + + if(indev->recognizers[i].recog_fn != NULL) { + + lv_indev_set_gesture_data(data, &indev->recognizers[i], i); + } + } + } + +} + + /******************** * 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 + * Caluclate the direction from the starting center of a two fingers swipe gesture + * @param recognizer pointer to the recognizer handling the two fingers + * swipe gesture + * @return the direction of the swipe, from the starting center */ -lv_indev_gesture_recognizer_t * lv_indev_get_gesture_recognizer(lv_event_t * gesture_event) +static lv_dir_t calculate_swipe_dir(lv_indev_gesture_recognizer_t * recognizer) +{ + + float abs_x = LV_ABS(recognizer->info->delta_x); + float abs_y = LV_ABS(recognizer->info->delta_y); + + if(abs_x > abs_y) { + + return recognizer->info->delta_x > 0 ? LV_DIR_RIGHT : LV_DIR_LEFT; + } + else { + + return recognizer->info->delta_y > 0 ? LV_DIR_BOTTOM : LV_DIR_TOP; + } +} + +/** + * Get the gesture recognizer associated to the event + * @param gesture_event an LV_GESTURE_EVENT event + * @param type the type of the recognizer we want to get + * @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_gesture_type_t type) { lv_indev_t * indev; @@ -317,9 +655,9 @@ lv_indev_gesture_recognizer_t * lv_indev_get_gesture_recognizer(lv_event_t * ges indev = (lv_indev_t *) gesture_event->param; - if(indev == NULL || indev->gesture_data == NULL) return NULL; + if(indev == NULL || indev->gesture_data[type] == NULL) return NULL; - return (lv_indev_gesture_recognizer_t *) indev->gesture_data; + return (lv_indev_gesture_recognizer_t *) indev->gesture_data[type]; } /** @@ -331,22 +669,36 @@ static void reset_recognizer(lv_indev_gesture_recognizer_t * recognizer) uint8_t finger_cnt; lv_indev_gesture_t * info; lv_indev_gesture_configuration_t * conf; + lv_recognizer_func_t recog_fn; if(recognizer == NULL) return; finger_cnt = recognizer->info->finger_cnt; info = recognizer->info; conf = recognizer->config; + recog_fn = recognizer->recog_fn; /* Set everything to zero but preserve the motion descriptors, * which are located at the start of the lv_indev_gesture_t struct */ lv_memzero((uint8_t *)info + sizeof(info->motions), sizeof(lv_indev_gesture_t) - sizeof(info->motions)); lv_memzero(recognizer, sizeof(lv_indev_gesture_recognizer_t)); - info->finger_cnt = finger_cnt; - recognizer->scale = info->scale = 1; recognizer->info = info; recognizer->config = conf; + recognizer->recog_fn = recog_fn; + + recognizer->scale = 1; + recognizer->rotation = 0.0; + + recognizer->info->rotation = 0.0; + recognizer->info->p_rotation = 0.0; + recognizer->info->scale = 1.0; + recognizer->info->delta_x = 0.0; + recognizer->info->delta_y = 0.0; + recognizer->info->finger_cnt = finger_cnt; + + recognizer->state = LV_INDEV_GESTURE_STATE_NONE; + recognizer->type = LV_INDEV_GESTURE_NONE; } /** @@ -362,6 +714,7 @@ static lv_indev_gesture_t * init_gesture_info(void) LV_ASSERT_MALLOC(info); info->scale = 1; + info->rotation = 0.0; for(i = 0; i < LV_GESTURE_MAX_POINTS; i++) { info->motions[i].finger = -1; @@ -587,15 +940,33 @@ static void gesture_calculate_factors(lv_indev_gesture_t * gesture, int touch_po 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; + 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 = (int32_t)center_x; - g->center.y = (int32_t)center_y; } +/** + * Get the type of the first gesture which reports either a LV_INDEV_GESTURE_STATE_RECOGNIZED + * or LV_INDEV_GESTURE_STATE_ENDED state + * @param indev pointer to the indev device from which we want to check the gestures states + * @return the type of the gesture having the state LV_INDEV_GESTURE_STATE_RECOGNIZED or + * LV_INDEV_GESTURE_STATE_ENDED, if found + * LV_INDEV_GESTURE_NONE otherwise + */ +static lv_indev_gesture_type_t get_first_recognized_or_ended_gesture(lv_indev_t * indev) +{ + + for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) { + if(indev->recognizers[i].state == LV_INDEV_GESTURE_STATE_RECOGNIZED || + indev->recognizers[i].state == LV_INDEV_GESTURE_STATE_ENDED) + return (lv_indev_gesture_type_t) i; + } + + return LV_INDEV_GESTURE_NONE; +} + + #endif /* LV_USE_GESTURE_RECOGNITION */ diff --git a/src/indev/lv_indev_gesture.h b/src/indev/lv_indev_gesture.h index 9b57689995..08318b9157 100644 --- a/src/indev/lv_indev_gesture.h +++ b/src/indev/lv_indev_gesture.h @@ -38,9 +38,14 @@ extern "C" { struct lv_indev_gesture; struct lv_indev_gesture_configuration; +typedef struct lv_indev_gesture_recognizer lv_indev_gesture_recognizer_t; +typedef struct lv_indev_touch_data lv_indev_touch_data_t; + typedef struct lv_indev_gesture lv_indev_gesture_t; typedef struct lv_indev_gesture_configuration lv_indev_gesture_configuration_t; +typedef void (*lv_recognizer_func_t)(lv_indev_gesture_recognizer_t *, lv_indev_touch_data_t *, uint16_t); + /* The states of a gesture recognizer */ typedef enum { LV_INDEV_GESTURE_STATE_NONE = 0, /* Beginning & end */ @@ -52,15 +57,15 @@ typedef enum { /* Data structures for touch events - used to repsensent a libinput event */ /* Emitted by devices capable of tracking identifiable contacts (type B) */ -typedef struct { +struct lv_indev_touch_data { 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 { +struct lv_indev_gesture_recognizer { 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 */ @@ -68,10 +73,12 @@ typedef struct { float rotation; /* Relevant for rotation */ float distance; /* Relevant for swipes */ float speed; + lv_dir_t two_fingers_swipe_dir; /* Relevant for swipes */ - lv_indev_gesture_configuration_t * config; - -} lv_indev_gesture_recognizer_t; + lv_indev_gesture_configuration_t * config; /* The recognizer config, containing the gestures + thresholds */ + lv_recognizer_func_t recog_fn; /* The recognizer function that this recongnizer must execute */ +}; /********************** * GLOBAL PROTOTYPES @@ -81,7 +88,8 @@ typedef struct { /* PINCH Gesture */ /** - * Detects a pinch gesture + * Pinch gesture recognizer function + * Will update the recognizer data * @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. @@ -89,39 +97,86 @@ typedef struct { void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, lv_indev_touch_data_t * touches, uint16_t touch_cnt); +/** + * Rotation gesture recognizer function + * Will update the recognizer data + * @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_rotation(lv_indev_gesture_recognizer_t * recognizer, lv_indev_touch_data_t * touches, + uint16_t touch_cnt); + +/** + * Two finger swipe gesture recognizer function + * Will update the recognizer data + * @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_two_fingers_swipe(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. + * @param indev pointer to the indev device containing the pinch recognizer + * @param threshold threshold for a pinch up gesture to be recognized */ -void lv_indev_set_pinch_up_threshold(lv_indev_gesture_recognizer_t * recognizer, float threshold); +void lv_indev_set_pinch_up_threshold(lv_indev_t * indev, 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. + * @param indev pointer to the indev device containing the pinch recognizer + * @param threshold threshold for a pinch down gesture to be recognized */ -void lv_indev_set_pinch_down_threshold(lv_indev_gesture_recognizer_t * recognizer, float threshold); +void lv_indev_set_pinch_down_threshold(lv_indev_t * indev, float threshold); + +/** + * Set the rotation threshold in radian for the rotation gesture + * @param indev pointer to the indev device containing the rotation recognizer + * @param threshold threshold in radian for a rotation gesture to be recognized + */ +void lv_indev_set_rotation_rad_threshold(lv_indev_t * indev, float threshold); /** * Obtains the current scale of a pinch gesture - * @param gesture_event pointer to a gesture recognizer event + * @param gesture_event pointer to a gesture event * @return the scale of the current gesture */ float lv_event_get_pinch_scale(lv_event_t * gesture_event); +/** + * Obtains the current angle in radian of a rotation gesture + * @param gesture_event pointer to a gesture event + * @return the rotation angle in radian of the current gesture + */ +float lv_event_get_rotation(lv_event_t * gesture_event); + +/** + * Obtains the current distance in pixels of a two fingers swipe gesture, from the starting center + * @param gesture_event pointer to a gesture event + * @return the distance from the center, in pixels, of the current gesture + */ +float lv_event_get_two_fingers_swipe_distance(lv_event_t * gesture_event); + +/** + * Obtains the current direction from the center of a two finger swipe + * @param gesture_event pointer to a gesture event + * @return the rotation angle in radian of the current gesture + */ +lv_dir_t lv_event_get_two_fingers_swipe_dir(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); +void lv_indev_set_gesture_data(lv_indev_data_t * data, lv_indev_gesture_recognizer_t * recognizer, + lv_indev_gesture_type_t type); /** * Obtains the center point of a gesture @@ -135,7 +190,14 @@ void lv_indev_get_gesture_center_point(lv_indev_gesture_recognizer_t * recognize * @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); +lv_indev_gesture_state_t lv_event_get_gesture_state(lv_event_t * gesture_event, lv_indev_gesture_type_t type); + +/** + * Obtains the current event type of the gesture recognizer attached to an event + * @param gesture_event pointer to a gesture recognizer event + * @return current event type of the gesture recognizer + */ +lv_indev_gesture_type_t lv_event_get_gesture_type(lv_event_t * gesture_event); /** * Obtains the coordinates of the current primary point @@ -151,6 +213,23 @@ void lv_indev_get_gesture_primary_point(lv_indev_gesture_recognizer_t * recogniz */ bool lv_indev_recognizer_is_active(lv_indev_gesture_recognizer_t * recognizer); +/** + * Update the recognizers. It execute the recognizers functions and checks for + * LV_GESTURE_STATE_RECOGNIZED or LV_GESTURE_STATE_ENDED gestures. + * To be called in the indev read_cb. + * @param indev pointer to the indev containing from which the reconizer need an update + * @param touches indev touch data array, containing the last touch data from indev + * since the last recognizers update + * @param touch_cnt number of indev touch data in touches + */ +void lv_indev_gesture_recognizers_update(lv_indev_t * indev, lv_indev_touch_data_t * touches, uint16_t touch_cnt); + +/** + * Set the lv_indev_data_t struct from the recognizer data. + * To be called in the indev read_cb. + */ +void lv_indev_gesture_recognizers_set_data(lv_indev_t * indev, lv_indev_data_t * data); + /********************** * MACROS diff --git a/src/indev/lv_indev_gesture_private.h b/src/indev/lv_indev_gesture_private.h index 35dd4ed28f..752672dfcc 100644 --- a/src/indev/lv_indev_gesture_private.h +++ b/src/indev/lv_indev_gesture_private.h @@ -69,10 +69,14 @@ struct lv_indev_gesture { }; +/* Recognizer configuration. It stores the thresholds needed to detect the gestures and + * consider them as recognized. Once recognized, indev start sending LV_GESTURE event + */ 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 */ + float pinch_up_threshold; /* Threshold for the pinch up gesture to be recognized - in pixels */ + float pinch_down_threshold; /* Threshold for the pinch down gesture to be recognized - in pixels */ + float rotation_angle_rad_threshold; /* Threshold for the rotation gesture to be recognized - in radians */ }; diff --git a/src/indev/lv_indev_private.h b/src/indev/lv_indev_private.h index 1ea590e6af..83b65c77b8 100644 --- a/src/indev/lv_indev_private.h +++ b/src/indev/lv_indev_private.h @@ -16,6 +16,7 @@ extern "C" { #include "lv_indev.h" #include "../misc/lv_anim.h" #include "lv_indev_scroll.h" +#include "lv_indev_gesture.h" /********************* * DEFINES @@ -117,8 +118,12 @@ struct _lv_indev_t { lv_event_list_t event_list; lv_anim_t * scroll_throw_anim; - lv_indev_gesture_type_t gesture_type; - void * gesture_data; +#if LV_USE_GESTURE_RECOGNITION + lv_indev_gesture_recognizer_t recognizers[LV_INDEV_GESTURE_CNT]; + lv_indev_gesture_type_t cur_gesture; + void * gesture_data[LV_INDEV_GESTURE_CNT]; + lv_indev_gesture_type_t gesture_type[LV_INDEV_GESTURE_CNT]; +#endif }; /********************** @@ -133,6 +138,7 @@ struct _lv_indev_t { */ lv_obj_t * lv_indev_find_scroll_obj(lv_indev_t * indev); + /********************** * MACROS **********************/