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 LV_EVENT_GESTURE event. For example:
To prevent passing the gesture event to the parent from a widget, use 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 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:
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:
fast enough: the difference between the current and the previous point must be greater than
indev->gesture_min_velocitylarge enough: the difference between the first and the current point must be greater than
indev->gesture_limit
Multi-touch Gestures¶
LVGL has the ability to recognize multi-touch gestures. Currently, these multi-touch gestures are supported:
Two fingers pinch (up and down)
Two fingers rotation
Two fingers swipe (infinite)
To enable multi-touch gesture recognition, set the LV_USE_GESTURE_RECOGNITION
option in the lv_conf.h file.
Usage¶
The recognizers can be updated to recognize gestures by calling
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
lv_indev_gesture_recognizers_set_data(indev, data) will update the
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:
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.
Events¶
Once a gesture is recognized or ended, a LV_EVENT_GESTURE is sent. The user can
use these functions to gather more information about the gesture:
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.
lv_event_get_gesture_state(lv_event_t *e, lv_indev_gesture_type_t type): Get the state of the gesture as
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.
Thresholds¶
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.
libinput Example¶
In the case of libinput, touch events are received asynchronously. To handle it,
the touch array and touch count need to be global variables:
/* 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:
Save the touch events asynchronously in the
touchesarray (one event per finger)Process the touch data in the
read_cbForget the touches and wait for new touch events by setting
touch_cnt = 0at the end of theread_cb
/**
* @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;
}
}