Input Device (lv_indev)
Creating an Input Device
To create an input device on the Default Display:
/* Create and set up at least one display before you register any input devices. */
lv_indev_t * indev = lv_indev_create(); /* Create input device connected to Default Display. */
lv_indev_set_type(indev, LV_INDEV_TYPE_...); /* Touch pad is a pointer-like device. */
lv_indev_set_read_cb(indev, my_input_read); /* Set driver function. */
If you have multiple displays, you will need to ensure the Default Display is set to the display your input device is "connected to" before making the above calls.
The type
member can be:
LV_INDEV_TYPE_POINTER
: touchpad or mouseLV_INDEV_TYPE_KEYPAD
: keyboard or keypadLV_INDEV_TYPE_ENCODER
: encoder with left/right turn and push optionsLV_INDEV_TYPE_BUTTON
: external buttons virtually pressing the screen
my_input_read
is a function pointer which will be called periodically to
report the current state of an input device to LVGL.
Touchpad, Touch-Screen, Mouse or Any Pointer
Input devices that can click points on the display belong to the POINTER category. Here is an example of a simple input-device Read Callback function:
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
...
void my_input_read(lv_indev_t * indev, lv_indev_data_t * data)
{
if(touchpad_pressed) {
data->point.x = touchpad_x;
data->point.y = touchpad_y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
Mouse Cursor
Pointer input devices (like a mouse) can have a cursor.
...
lv_indev_t * mouse_indev = lv_indev_create();
...
LV_IMAGE_DECLARE(mouse_cursor_icon); /* Declare the image source. */
lv_obj_t * cursor_obj = lv_image_create(lv_screen_active()); /* Create image Widget for cursor. */
lv_image_set_src(cursor_obj, &mouse_cursor_icon); /* Set image source. */
lv_indev_set_cursor(mouse_indev, cursor_obj); /* Connect image to Input Device. */
Note that the cursor object should have lv_obj_remove_flag(cursor_obj, LV_OBJ_FLAG_CLICKABLE). For images, clicking is disabled by default.
Gestures
Pointer input devices can detect basic 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:
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 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 did some action on a gesture you can call lv_indev_wait_release(lv_indev_active()) in the event handler to prevent LVGL sending further input-device-related events.
Crown Behavior
A "Crown" is a rotary device typically found on smart watches.
When the user clicks somewhere and after that turns the rotary the last clicked widget will be either scrolled or it's value will be incremented/decremented (e.g. in case of a slider).
As this behavior is tightly related to the last clicked widget, the crown support is
an extension of the pointer input device. Just set data->diff
to the number of
turned steps and LVGL will automatically send the LV_EVENT_ROTARY
event or scroll the widget based on the editable
flag in the widget's class.
Non-editable widgets are scrolled and for editable widgets the event is sent.
To get the steps in an event callback use int32_t diff = lv_event_get_rotary_diff(e)
The rotary sensitivity can be adjusted on 2 levels:
in the input device by the
indev->rotary_sensitivity
element (1/256 unit), andby the
rotary_sensitivity
style property in the widget (1/256 unit).
The final diff is calculated like this:
diff_final = diff_in * (indev_sensitivity / 256) + (widget_sensitivity / 256);
For example, if both the indev and widget sensitivity is set to 128 (0.5), the input 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
/* 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 received */
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
Full keyboards with all the letters or simple keypads with a few navigation buttons belong in the keypad category.
You can fully control the user interface without a touchpad or mouse by using a keypad or encoder. It works similar to the TAB key on the PC to select an element in an application or web page.
To use a keyboard or keypad:
Register a Read Callback function for your device and set its type to
LV_INDEV_TYPE_KEYPAD
.Create a Widget Group (
lv_group_t * g = lv_group_create()
) and add Widgets to it with lv_group_add_obj(g, widget).Assign the group to an input device: lv_indev_set_group(indev, g).
Use
LV_KEY_...
to navigate among the Widgets in the group. Seelv_core/lv_group.h
for the available keys.
lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD);
...
void keyboard_read(lv_indev_t * indev, lv_indev_data_t * data){
data->key = last_key(); /* Get the last pressed or released key */
if(key_pressed()) data->state = LV_INDEV_STATE_PRESSED;
else data->state = LV_INDEV_STATE_RELEASED;
}
Encoder
A common example of an encoder is a device with a turning knob that tells the hosting device when the knob is being turned, and in which direction.
With an encoder your application can receive events from the following:
press of its button,
oong-press of its button,
turn left, and
turn right.
In short, the Encoder input devices work like this:
By turning the encoder you can focus on the next/previous object.
When you press the encoder on a simple object (like a button), it will be clicked.
If you press the encoder on a complex object (like a list, message box, etc.) the Widget will go to edit mode whereby you can navigate inside the object by turning the encoder.
To leave edit mode, long press the button.
To use an Encoder (similar to the Keypads) the Widgets should be added to a group.
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
...
void encoder_read(lv_indev_t * indev, lv_indev_data_t * data){
data->enc_diff = enc_get_new_moves();
if(enc_pressed()) data->state = LV_INDEV_STATE_PRESSED;
else data->state = LV_INDEV_STATE_RELEASED;
}
Widget Groups
When input focus needs to be managed among a set of Widgets (e.g. to capture user input from a keypad or encoder), that set of Widgets is placed in a group which thereafter manages how input focus moves from Widget to Widget.
In each group there is exactly one object with focus which receives the pressed keys or the encoder actions. For example, if a Text Area has focus and you press some letter on a keyboard, the keys will be sent and inserted into the text area. Similarly, if a Slider has focus and you press the left or right arrows, the slider's value will be changed.
You need to associate an input device with a group. An input device can send key events to only one group but a group can receive data from more than one input device.
To create a group use lv_group_t * g = lv_group_create() and to add a Widget to the group use lv_group_add_obj(g, widget).
Once a Widget has been added to a group, you can find out what group it is in using lv_obj_get_group(widget).
To find out if a Widget in a group has focus, call lv_obj_is_focused(widget).
If the Widget is not part of a group, this function will return false
.
To associate a group with an input device use lv_indev_set_group(indev, g).
Keys
There are some predefined keys which have special meaning:
LV_KEY_NEXT
: Move focus to next objectLV_KEY_PREV
: Move focus to previous objectLV_KEY_ENTER
: TriggersLV_EVENT_PRESSED
,LV_EVENT_CLICKED
, orLV_EVENT_LONG_PRESSED
etc. eventsLV_KEY_UP
: Increase value or move upLV_KEY_DOWN
: Decrease value or move downLV_KEY_RIGHT
: Increase value or move to the rightLV_KEY_LEFT
: Decrease value or move to the leftLV_KEY_ESC
: Close or exit (e.g. close a Drop-Down List)LV_KEY_DEL
: Delete (e.g. a character on the right in a Text Area)LV_KEY_BACKSPACE
: Delete (e.g. a character on the left in a Text Area)LV_KEY_HOME
: Go to the beginning/top (e.g. in a Text Area)LV_KEY_END
: Go to the end (e.g. in a Text Area)
The most important special keys in your read_cb()
function are:
You should translate some of your keys to these special keys to support navigation in a group and interact with selected Widgets.
Usually, it's enough to use only LV_KEY_LEFT
and LV_KEY_RIGHT
because most
Widgets can be fully controlled with them.
With an encoder you should use only LV_KEY_LEFT
, LV_KEY_RIGHT
,
and LV_KEY_ENTER
.
Default Group
Interactive widgets (such as Buttons, Checkboxes, Sliders, etc.) can be automatically added to a default group. Just create a group with lv_group_t * g = lv_group_create() and set the default group with lv_group_set_default(g)
Don't forget to assign one or more input devices to the default group with lv_indev_set_group(my_indev, g).
Styling
When a Widget receives focus either by clicking it via touchpad or by navigating to
it with an encoder or keypad, it goes to the LV_STATE_FOCUSED
state. Hence, focused styles will be applied to it.
If a Widget switches to edit mode it enters the LV_STATE_FOCUSED | LV_STATE_EDITED states so any style properties assigned to these states will be shown.
See Styles for more details.
Other Features
Parameters
The default value of the following parameters can be changed in lv_indev_t
:
scroll_limit
Number of pixels to slide before actually scrolling the Widgetscroll_throw
Scroll throw (momentum) slow-down in [%]. Greater value means faster slow-down.long_press_time
Press time to sendLV_EVENT_LONG_PRESSED
(in milliseconds)long_press_repeat_time
Interval of sendingLV_EVENT_LONG_PRESSED_REPEAT
(in milliseconds)read_timer
pointer to thelv_timer
which reads the input device. Its parameters can be changed by callinglv_timer_...()
functions.LV_DEF_REFR_PERIOD
inlv_conf.h
sets the default read period.
Feedback
Besides read_cb
a feedback_cb
callback can be also specified in
lv_indev_t
. feedback_cb
is called when any type of event is sent
by input devices (independently of their type). This allows generating
feedback for the user, e.g. to play a sound on LV_EVENT_CLICKED
.
Buffered Reading
By default, LVGL calls read_cb
periodically. Because of this
intermittent polling there is a chance that some user gestures are
missed.
To solve this you can write an event driven driver for your input device
that buffers measured data. In read_cb
you can report the buffered
data instead of directly reading the input device. Setting the
data->continue_reading
flag will tell LVGL there is more data to
read and it should call read_cb
again.
Switching the Input Device to Event-Driven Mode
Normally an Input Device is read every LV_DEF_REFR_PERIOD
milliseconds (set in lv_conf.h
). However, in some cases, you might
need more control over when to read the input device. For example, you
might need to read it by polling a file descriptor (fd).
You can do this by:
/* Update the input device's running mode to LV_INDEV_MODE_EVENT */
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
...
/* Call this anywhere you want to read the input device */
lv_indev_read(indev);
Note
lv_indev_read()
, lv_timer_handler()
and _lv_display_refr_timer()
cannot run at the same time.
Note
For devices in event-driven mode, data->continue_reading is ignored.
Further Reading
lv_port_indev_template.c for a template for your own Input-Device driver.