docs/src/main-modules/indev/gestures.rst
.. _indev_gestures:
Overview
Pointer input devices can detect simple (up, down, left, right) and multi-touch (swipe, pinch, scroll) gestures.
By default, most widgets send gestures to their parents so they can be detected on the
Screen widget in the form of an :cpp:enumerator:LV_EVENT_GESTURE event. For example:
To prevent passing the gesture event to the parent from a widget, use
:cpp:expr:lv_obj_remove_flag(widget, LV_OBJ_FLAG_GESTURE_BUBBLE).
Note that gestures are not triggered if a widget is being scrolled.
If you performed some action on a gesture, you can call
:cpp:expr:lv_indev_wait_release(lv_indev_active()) in the event handler to prevent
LVGL from sending further input-device-related events.
Simple Gestures
Simple gestures are always enabled and very easy to use:
.. code-block:: c
void my_event(lv_event_t * e)
{
lv_obj_t * screen = lv_event_get_current_target(e);
lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_active());
switch(dir) {
case LV_DIR_LEFT:
...
break;
case LV_DIR_RIGHT:
...
break;
case LV_DIR_TOP:
...
break;
case LV_DIR_BOTTOM:
...
break;
}
}
...
lv_obj_add_event_cb(screen1, my_event, LV_EVENT_GESTURE, NULL);
To trigger a gesture, two things need to happen. The movement needs to be:
gesture_min_velocitygesture_min_distanceThese limits can be set via :cpp:func:lv_indev_set_gesture_min_velocity and
:cpp:func:lv_indev_set_gesture_min_distance accordingly.
Multi-touch Gestures
LVGL has the ability to recognize multi-touch gestures. Currently, these multi-touch gestures are supported:
To enable multi-touch gesture recognition, set the :c:macro:LV_USE_GESTURE_RECOGNITION
option in the lv_conf.h file.
The recognizers can be updated to recognize gestures by calling
:cpp:expr:lv_indev_gesture_recognizers_update(indev, touches, touch_cnt). 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. Currently, only one
multi-touch gesture can be recognized or ended at a time.
Once the recognizers are updated, calling
:cpp:expr:lv_indev_gesture_recognizers_set_data(indev, data) will update the
:cpp:expr:lv_indev_data_t structure. This should also be done in the indev
read_cb.
The way the touch points are collected varies based on the hardware and drivers used.
Here is a generic example of the read_cb:
.. code-block:: c
static void touch_read_callback(lv_indev_t * indev, lv_indev_data_t * data) { /* Stores the collected touch events */ lv_indev_touch_data_t touches[10];
/* Store the current touch (finger) count */
int32_t touch_cnt;
/* Get the touch points */
touch_cnt = my_read_touch_points(touches);
lv_indev_gesture_recognizers_update(indev, touches, touch_cnt);
lv_indev_gesture_recognizers_set_data(indev, data);
/* Also process normal touch */
if(touch_cnt > 0) {
data->point.x = touches[0].point.x;
data->point.y = touches[0].point.y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
LVGL sends events if the gestures are in one of the following states:
LV_INDEV_GESTURE_STATE_RECOGNIZED: The gesture has been recognized and is now
active.LV_INDEV_GESTURE_STATE_ENDED: The gesture has ended.Once a gesture is recognized or ended, a :cpp:enumerator:LV_EVENT_GESTURE is sent. The user can
use these functions to gather more information about the gesture:
:cpp:expr:lv_event_get_gesture_type(lv_event_t * e): Get the type of the gesture.
Use this to check which multi-touch gesture is currently reported.
:cpp:expr:lv_event_get_gesture_state(lv_event_t * e, lv_indev_gesture_type_t type):
Get the state of the gesture as :cpp:type:lv_indev_gesture_state_t. It can be one
of:
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.The user can then request the gesture values with the following functions:
lv_event_get_pinch_scale(lv_event_t * e): Get the pinch scale. Only
relevant for pinch gestures.lv_event_get_rotation(lv_event_t * e): Get the rotation in radians. Only
relevant for rotation gestures.lv_event_get_two_fingers_swipe_distance(lv_event_t * e): Get the
distance in pixels from the gesture starting center. Only relevant for two-finger
swipe gestures.lv_event_get_two_fingers_swipe_dir(lv_event_t * e): Get the direction
from the starting center. Only relevant for two-finger swipe gestures.This allows the user to react to the gestures and use the gesture values. An example
of such an application is available in the source tree:
examples/others/gestures/lv_example_gestures.c.
The gesture recognizers can be configured to modify the gesture thresholds:
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.In the case of libinput, touch events are received asynchronously. To handle it,
the touch array and touch count need to be global variables:
.. code-block:: c
/* Stores the collected touch events */ static lv_indev_touch_data_t touches[10];
/* Store the current touch (finger) count SINCE THE LAST READ */ static int32_t touch_cnt;
so the flow is:
touches array (one event per finger)read_cbtouch_cnt = 0 at
the end of the read_cb.. code-block:: c
/** * @brief Convert the libinput event to LVGL's representation of a touch event * @param ev a pointer to the libinput event */ static void touch_process(struct libinput_event *ev) { int type = libinput_event_get_type(ev); struct libinput_event_touch *touch_ev = libinput_event_get_touch_event(ev); int id = libinput_event_touch_get_slot(touch_ev); uint32_t time = libinput_event_touch_get_time(touch_ev);
/* Get the last event for the contact point */
lv_indev_touch_data_t *cur = NULL;
/* Find if the touch is already stored in the array by its ID.
* If there are 2 active touches it's enough to check those. */
for (int i = 0; i < touch_cnt; i++) {
if (touches[i].id == id) {
cur = &touches[i];
break;
}
}
if (cur && cur->timestamp == time) {
/* Previous event has the same timestamp - ignore duplicate event */
return;
}
/* Save the new touch */
if (cur == NULL ||
type == LIBINPUT_EVENT_TOUCH_UP ||
type == LIBINPUT_EVENT_TOUCH_DOWN) {
cur = &touches[touch_cnt];
cur->id = id; /* It will be needed to identify the touches in the next events */
touch_cnt++;
}
/* Process the event */
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;
}
/** * @brief Filter out libinput events that are not related to touches * @param ev a pointer to the libinput event */ static void process_libinput_event(struct libinput_event *ev) { int 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_process(ev);
break;
default:
/* Skip unrelated libinput events */
return;
}
}