add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1,377 @@
.. _animation:
===================
Animation (lv_anim)
===================
Animations allow you to define the way something should move or change over time, and
let LVGL do the heavy lifting of making it happen. What makes it so powerful is that
the thing being changed can be virtually anything in your system. It is very
convenient to apply this to LVGL Widgets in your user interface (UI), to change their
appearance, size or location over time. But because it is --- at its core --- a
generic change-over-time manager, complete with a variety of optional event
callbacks, its application can be wider than just to UI components.
For each Animation you create, it accomplishes the above by providing a generic
method of varying a signed integer from a start value to an end value over a
specified time period. It allows you to specify what object it applies to (the
"variable"), which is available in the callback functions that are called as the
Animation is playing through.
This variation over time can be linear (default), it can be on a path (curve) that
you specify, and there is even a variety of commonly-used non-linear effects that can
be specified.
The main callback called during an Animation (when it is playing) is called an
*animator* function, which has the following prototype:
.. code-block:: c
void func(void *var , int32_t value);
This prototype makes it easy to use most of the LVGL *set* functions directly or via a trivial wrapper. It includes:
- most of the widget properties
- functions that set :ref:`local style properties <style_local>`) directly on objects (needs a wrapper to set set the *selector*)
- set properties on :cpp:type:`lv_style_t` objects (e.g. :ref:`shared styles <style_initialize>`) (``lv_obj_report_style_change`` needs to be called to notify the widgets having the style)
- ``lv_style_set_<property_name>(&style, <value>)``
- ``lv_obj_set_<property_name>(widget, <value>)``
Because of the former, an animation on a single :cpp:type:`lv_style_t` object shared
among several objects can simultaneously modify the appearance of all objects that
use it. See :ref:`styles` for more details.
Examples of the latter are: :cpp:expr:`lv_obj_set_x(widget, value)` or
:cpp:expr:`lv_obj_set_width(widget, value)`.
This makes it very convenient to apply to the appearance (and other attributes) of UI
components. But you can provide your own "set" functions, and so the application of
Animations is really limited only by your imagination.
The number of Animations that can be playing at the same time for a given object with
a given *animator* callback is one (1). However, the number of Animations that can
be playing at the same time is limited only by available RAM and CPU time for:
- a given object with different *animator* callbacks; and
- different objects.
Thus, you can have a Button's width being changed by one Animation while having its
height being changed by another Animation.
.. _animations_create:
Create an Animation
*******************
To create an Animation, start by creating an Animation *template* in an
:cpp:type:`lv_anim_t` variable. It has to be initialized and configured with
``lv_anim_set_...()`` functions.
.. code-block:: c
/* INITIALIZE AN ANIMATION
*-----------------------*/
static lv_anim_t anim_template;
static lv_anim_t * running_anim;
lv_anim_init(&anim_template);
/* MANDATORY SETTINGS
*------------------*/
/* Set the "animator" function */
lv_anim_set_exec_cb(&anim_template, (lv_anim_exec_xcb_t) lv_obj_set_x);
/* Set target of the Animation */
lv_anim_set_var(&anim_template, widget);
/* Length of the Animation [ms] */
lv_anim_set_duration(&anim_template, duration_in_ms);
/* Set start and end values. E.g. 0, 150 */
lv_anim_set_values(&anim_template, start, end);
/* OPTIONAL SETTINGS
*------------------*/
/* Time to wait before starting the Animation [ms] */
lv_anim_set_delay(&anim_template, delay);
/* Set path (curve). Default is linear */
lv_anim_set_path_cb(&anim_template, lv_anim_path_ease_in);
/* Set anim_template callback to indicate when the Animation is completed. */
lv_anim_set_completed_cb(&anim_template, completed_cb);
/* Set anim_template callback to indicate when the Animation is deleted (idle). */
lv_anim_set_deleted_cb(&anim_template, deleted_cb);
/* Set anim_template callback to indicate when the Animation is started (after delay). */
lv_anim_set_start_cb(&anim_template, start_cb);
/* When ready, play the Animation backward with this duration. Default is 0 (disabled) [ms] */
lv_anim_set_reverse_duration(&anim_template, time);
/* Delay before reverse play. Default is 0 (disabled) [ms] */
lv_anim_set_reverse_delay(&anim_template, delay);
/* Number of repetitions. Default is 1. LV_ANIM_REPEAT_INFINITE for infinite repetition */
lv_anim_set_repeat_count(&anim_template, cnt);
/* Delay before repeat. Default is 0 (disabled) [ms] */
lv_anim_set_repeat_delay(&anim_template, delay);
/* true (default): apply the start value immediately, false: apply start value after delay when the Anim. really starts. */
lv_anim_set_early_apply(&anim_template, true/false);
/* START THE ANIMATION
*------------------*/
running_anim = lv_anim_start(&anim_template); /* Start the Animation */
.. _animation_path:
Animation Path
**************
You can control the Path (curve) of an Animation. The simplest case is linear,
meaning the current value between *start* and *end* is changed at the same rate (i.e.
with fixed steps) over the duration of the Animation. A *Path* is a function which
calculates the next value to set based on the current state of the Animation.
There are a number of built-in *Paths* that can be used:
- :cpp:func:`lv_anim_path_linear`: linear Animation (default)
- :cpp:func:`lv_anim_path_step`: change in one step at the end
- :cpp:func:`lv_anim_path_ease_in`: slow at the beginning
- :cpp:func:`lv_anim_path_ease_out`: slow at the end
- :cpp:func:`lv_anim_path_ease_in_out`: slow at the beginning and end
- :cpp:func:`lv_anim_path_overshoot`: overshoot the end value
- :cpp:func:`lv_anim_path_bounce`: bounce back a little from the end value (like
hitting a wall)
Alternately, you can provide your own Path function.
:cpp:expr:`lv_anim_init(&my_anim)` sets the Path to :cpp:func:`lv_anim_path_linear`
by default. If you want to use a different Path (including a custom Path function
you provide), you set it using :cpp:expr:`lv_anim_set_path_cb(&anim_template, path_cb)`.
If you provide your own custom Path function, its prototype is:
.. code-block:: c
int32_t calculate_value(lv_anim_t * anim);
.. _animation_speed_vs_time:
Speed vs Time
*************
Normally, you set the Animation duration directly using
:cpp:expr:`lv_anim_set_duration(&anim_template, duration_in_ms)`. But in some cases
the *rate* is known but the duration is not known. Given an Animation's ``start``
and ``end`` values, *rate* here means the number of units of change per second, i.e.
how quickly (units per second) the Animation's value needs to change between the
``start`` and ``end`` value. For such cases there is a utility function
:cpp:func:`lv_anim_speed_to_time` you can use to compute the Animation's duration, so
you can set it like this:
.. code-block:: c
uint32_t change_per_sec = 20;
uint32_t duration_in_ms = lv_anim_speed_to_time(change_per_sec, 0, 100);
/* `duration_in_ms` will be 5000 */
lv_anim_set_duration(&anim_template, duration_in_ms);
.. _animation_direction:
Animating in Both Directions
****************************
Sometimes an Animation needs to play forward, and then play backwards, effectively
reversing course, animating from the ``end`` value back to the ``start`` value again.
To do this, pass a non-zero value to this function to set the duration for the
reverse portion of the Animation:
:cpp:expr:`lv_anim_set_reverse_duration(&anim_template, duration_in_ms)`.
Optionally, you can also introduce a delay between the forward and backward
directions using :cpp:expr:`lv_anim_set_reverse_delay(&anim_template, delay_in_ms)`
.. _animation_start:
Starting an Animation
*********************
After you have set up your :cpp:type:`lv_anim_t` object, it is important to realize
that what you have set up is a "template" for a live, running Animation that has
not been created yet. When you call :cpp:expr:`lv_anim_start(&anim_template)`
passing the *template* you have set up, it uses your template to dynamically allocate
an internal object that is a *live, running* Animation. This function returns a
pointer to that object.
.. code-block:: c
static lv_anim_t anim_template;
static lv_anim_t * running_anim;
/* Set up template... */
lv_anim_init(&anim_template);
/* ...and other set-up functions above. */
/* Later... */
running_anim = lv_anim_start(&anim_template);
.. note::
:cpp:expr:`lv_anim_start(&anim_template)` makes its own copy of the Animation
template, so if you do not need it later, its contents do not need to be
preserved after this call.
Once a *live running* Animation has been started, it runs until it has completed,
or until it is deleted (see below), whichever comes first. An Animation has
completed when:
- its "value" has reached the desginated ``end`` value;
- if the Animation has a non-zero *reverse* duration value, then its value
has run from the ``end`` value back to the ``start`` value again;
- if a non-zero repeat count has been set, it has repeated the Animation
that number of times.
Once the *live, running* Animation reaches completion, it is automatically deleted
from the list of running Animations. This does not impact your Animation template.
.. note::
If :cpp:expr:`lv_anim_set_repeat_count(&anim_template, cnt)` has been called
passing :c:macro:`LV_ANIM_REPEAT_INFINITE`, the animation never reaches a state
of being "completed". In this case, it must be deleted to terminate the
Animation.
.. _animation_delete:
Deleting Animations
*******************
You should delete an Animation using :cpp:expr:`lv_anim_delete(var, func)` if one of
these two conditions exists:
- the object (variable) being animated is deleted (and it is not a Widget) or
- a running animation needs to be stopped before it is completed.
.. note::
If the object (variable) being deleted is a type of Widget, the housekeeping code
involved in deleting it also deletes any running animations that are connected
with it. So :cpp:expr:`lv_anim_delete(var, func)` only needs to be called if the
object being deleted is *not* one of the Widgets.
If you kept a copy of the pointer returned by :cpp:func:`lv_anim_start` as
``running_anim``, you can delete the running animation like this:
.. code-block:: c
lv_anim_delete(running_anim->var, running_anim->exec_cb);
In the event that the Animation completes *after* you have determined it needs to be
deleted, and before the call to :cpp:func:`lv_anim_delete` is made, it does no harm
to call it a second time --- no damage will occur.
This function returns a Boolean value indicating whether any *live, running*
Animations were deleted.
.. _animation_pause:
Pausing Animations
******************
If you kept a copy of the pointer returned by :cpp:func:`lv_anim_start`,
you can pause the running animation using :cpp:expr:`lv_anim_pause(animation)` and then resume it
using :cpp:expr:`lv_anim_resume(animation)`.
:cpp:expr:`lv_anim_pause_for(animation, milliseconds)`
is also available if you wish for the animation to resume automatically after.
.. _animations_timeline:
Timeline
********
You can create a series of related animations that are linked together using an
Animation Timeline. A Timeline is a collection of multiple Animations which makes it
easy to create complex composite Animations. To create and use an Animation Timeline:
- Create an Animation template but do not call :cpp:func:`lv_anim_start` on it.
- Create an Animation Timeline object by calling :cpp:func:`lv_anim_timeline_create`.
- Add Animation templates to the Timeline by calling
:cpp:expr:`lv_anim_timeline_add(timeline, start_time, &anim_template)`.
``start_time`` is the start time of the Animation on the Timeline. Note that
``start_time`` will override any value given to
:cpp:expr:`lv_anim_set_delay(&anim_template, delay)`.
- Call :cpp:expr:`lv_anim_timeline_start(timeline)` to start the Animation Timeline.
.. note::
:cpp:expr:`lv_anim_timeline_add(timeline, start_time, &anim_template)` makes its
own copy of the contents of the Animation template, so if you do not need it
later, its contents do not need to be preserved after this call.
It supports forward and reverse play of the entire Animation group, using
:cpp:expr:`lv_anim_timeline_set_reverse(timeline, reverse)`. Note that if you want to
play in reverse from the end of the Timeline, you need to call
:cpp:expr:`lv_anim_timeline_set_progress(timeline, LV_ANIM_TIMELINE_PROGRESS_MAX)`
after adding all Animations and before telling it to start playing.
Call :cpp:expr:`lv_anim_timeline_pause(timeline)` to pause the Animation Timeline.
Note: this does not preserve its state. The only way to start it again is to call
:cpp:expr:`lv_anim_timeline_start(timeline)`, which starts the Timeline from the
beginning or at the point set by
:cpp:expr:`lv_anim_timeline_set_progress(timeline, progress)`.
Call :cpp:expr:`lv_anim_timeline_set_progress(timeline, progress)` function to set the
state of the Animation Timeline according to the ``progress`` value. ``progress`` is
a value between ``0`` and ``32767`` (:c:macro:`LV_ANIM_TIMELINE_PROGRESS_MAX`) to indicate the
proportion of the Timeline that has "played". Example: a ``progress`` value of
:cpp:expr:`LV_ANIM_TIMELINE_PROGRESS_MAX / 2` would set the Timeline play to its
half-way point.
Call :cpp:expr:`lv_anim_timeline_get_playtime(timeline)` function to get the total
duration (in milliseconds) of the entire Animation Timeline.
Call :cpp:expr:`lv_anim_timeline_get_reverse(timeline)` function to get whether the
Animation Timeline is also played in reverse after its forward play completes.
Call :cpp:expr:`lv_anim_timeline_delete(timeline)` function to delete the Animation Timeline.
**Note**: If you need to delete a Widget during Animation, be sure to delete the
Animation Timeline before deleting the Widget. Otherwise, the program may crash or behave abnormally.
.. image:: /_static/images/anim-timeline.png
.. _animations_example:
Examples
********
.. include:: ../../examples/anim/index.rst
.. _animations_api:
API
***

View File

@@ -0,0 +1,154 @@
.. _color:
================
Color (lv_color)
================
The color module handles all color-related functions like changing color
depth, creating colors from hex code, converting between color depths,
mixing colors, etc.
The type :cpp:type:`lv_color_t` is used to store a color in RGB888 format.
This type and format is used in almost all APIs regardless of :cpp:expr:`LV_COLOR_DEPTH`.
.. _color_create:
Creating Colors
***************
RGB
---
Create colors from Red, Green and Blue channel values:
.. code-block:: c
/* All channels are 0-255 */
lv_color_t c = lv_color_make(red, green, blue);
/* Same but can be used for const initialization as well */
lv_color_t c = LV_COLOR_MAKE(red, green, blue);
/* From hex code 0x000000..0xFFFFFF interpreted as RED + GREEN + BLUE */
lv_color_t c = lv_color_hex(0x123456);
/* From 3 digits. Same as lv_color_hex(0x112233) */
lv_color_t c = lv_color_hex3(0x123);
HSV
---
Create colors from Hue, Saturation and Value values:
.. code-block:: c
//h = 0..359, s = 0..100, v = 0..100
lv_color_t c = lv_color_hsv_to_rgb(h, s, v);
//All channels are 0-255
lv_color_hsv_t c_hsv = lv_color_rgb_to_hsv(r, g, b);
//From lv_color_t variable
lv_color_hsv_t c_hsv = lv_color_to_hsv(color);
.. _color_palette:
Palette
-------
LVGL includes `Material Design's palette <https://vuetifyjs.com/en/styles/colors/#material-colors>`__ of
colors. In this system all named colors have a nominal main color as
well as four darker and five lighter variants.
The names of the colors are as follows:
- :c:macro:`LV_PALETTE_RED`
- :c:macro:`LV_PALETTE_PINK`
- :c:macro:`LV_PALETTE_PURPLE`
- :c:macro:`LV_PALETTE_DEEP_PURPLE`
- :c:macro:`LV_PALETTE_INDIGO`
- :c:macro:`LV_PALETTE_BLUE`
- :c:macro:`LV_PALETTE_LIGHT_BLUE`
- :c:macro:`LV_PALETTE_CYAN`
- :c:macro:`LV_PALETTE_TEAL`
- :c:macro:`LV_PALETTE_GREEN`
- :c:macro:`LV_PALETTE_LIGHT_GREEN`
- :c:macro:`LV_PALETTE_LIME`
- :c:macro:`LV_PALETTE_YELLOW`
- :c:macro:`LV_PALETTE_AMBER`
- :c:macro:`LV_PALETTE_ORANGE`
- :c:macro:`LV_PALETTE_DEEP_ORANGE`
- :c:macro:`LV_PALETTE_BROWN`
- :c:macro:`LV_PALETTE_BLUE_GREY`
- :c:macro:`LV_PALETTE_GREY`
To get the main color use
:cpp:expr:`lv_color_t` ``c =`` :cpp:expr:`lv_palette_main(LV_PALETTE_...)`.
For the lighter variants of a palette color use
:cpp:expr:`lv_color_t` ``c =`` :cpp:expr:`lv_palette_lighten(LV_PALETTE_..., v)`. ``v`` can be
1..5. For the darker variants of a palette color use
:cpp:expr:`lv_color_t` ``c =`` :cpp:expr:`lv_palette_darken(LV_PALETTE_..., v)`. ``v`` can be
1..4.
.. _color_modify_and_mix:
Modify and mix colors
---------------------
The following functions can modify a color:
.. code-block:: c
// Lighten a color. 0: no change, 255: white
lv_color_t c = lv_color_lighten(c, lvl);
// Darken a color. 0: no change, 255: black
lv_color_t c = lv_color_darken(lv_color_t c, lv_opa_t lvl);
// Lighten or darken a color. 0: black, 128: no change 255: white
lv_color_t c = lv_color_change_lightness(lv_color_t c, lv_opa_t lvl);
// Mix two colors with a given ratio 0: full c2, 255: full c1, 128: half c1 and half c2
lv_color_t c = lv_color_mix(c1, c2, ratio);
.. _color_builtin:
Built-in colors
---------------
:cpp:func:`lv_color_white` and :cpp:func:`lv_color_black` return ``0xFFFFFF`` and
``0x000000`` respectively.
.. _color_opacity:
Opacity
*******
To describe opacity the :cpp:type:`lv_opa_t` type is created from ``uint8_t``.
Some special purpose defines are also introduced:
- :cpp:enumerator:`LV_OPA_TRANSP` Value: 0, means no opacity making the color
completely transparent
- :cpp:enumerator:`LV_OPA_10` Value: 25, means the color covers only a little
- ``LV_OPA_20 ... OPA_80`` follow logically
- :cpp:enumerator:`LV_OPA_90` Value: 229, means the color nearly completely covers
- :cpp:enumerator:`LV_OPA_COVER` Value: 255, means the color completely covers (full
opacity)
You can also use the ``LV_OPA_*`` defines in :cpp:func:`lv_color_mix` as a
mixing *ratio*.
.. _color_api:
API
***

View File

@@ -0,0 +1,168 @@
.. _display_color_format:
============
Color Format
============
The default color format of the display is set according to :c:macro:`LV_COLOR_DEPTH`
(see ``lv_conf.h``)
- :c:macro:`LV_COLOR_DEPTH` ``32``: XRGB8888 (4 bytes/pixel)
- :c:macro:`LV_COLOR_DEPTH` ``24``: RGB888 (3 bytes/pixel)
- :c:macro:`LV_COLOR_DEPTH` ``16``: RGB565 (2 bytes/pixel)
- :c:macro:`LV_COLOR_DEPTH` ``8``: L8 (1 bytes/pixel)
- :c:macro:`LV_COLOR_DEPTH` ``1``: I1 (1 bit/pixel) Only support for horizontal mapped buffers. See :ref:`display_monochrome` for more details:
The ``color_format`` can be changed with
:cpp:expr:`lv_display_set_color_depth(display, LV_COLOR_FORMAT_...)`.
Besides the default value :c:macro:`LV_COLOR_FORMAT_ARGB8888` can be
used as a well.
It's very important that draw buffer(s) should be large enough for the
selected color format.
.. _display_endianness:
Swapping Endian-ness
********************
In case of RGB565 color format it might be required to swap the 2 bytes
because the SPI, I2C or 8 bit parallel port periphery sends them in the wrong order.
The ideal solution is configure the hardware to handle the 16 bit data with different byte order,
however if this is not possible :cpp:expr:`lv_draw_sw_rgb565_swap(buf, buf_size_in_px)`
can be called in the :ref:`flush_callback` to swap the bytes.
If you wish you can also write your own function, or use assembly instructions for
the fastest possible byte swapping.
Note that this is not about swapping the Red and Blue channel but converting
``RRRRR GGG | GGG BBBBB``
to
``GGG BBBBB | RRRRR GGG``.
.. _display_monochrome:
Monochrome Displays
*******************
LVGL supports rendering directly in a 1-bit format for monochrome displays.
To enable it, set ``LV_COLOR_DEPTH 1`` or use :cpp:expr:`lv_display_set_color_format(display, LV_COLOR_FORMAT_I1)`.
The :cpp:expr:`LV_COLOR_FORMAT_I1` format assumes that bytes are mapped to rows (i.e., the bits of a byte are written next to each other).
The order of bits is MSB first, which means:
.. code-block::
MSB LSB
bits 7 6 5 4 3 2 1 0
are represented on the display as:
.. code-block::
pixels 0 1 2 3 4 5 6 7
Left Right
Ensure that the LCD controller is configured accordingly.
Internally, LVGL rounds the redrawn areas to byte boundaries. Therefore, updated areas will:
- start on an ``Nx8`` coordinate, and
- end on an ``Nx8 - 1`` coordinate.
When setting up the buffers for rendering (:cpp:func:`lv_display_set_buffers`), make the buffer 8 bytes larger.
This is necessary because LVGL reserves 2 x 4 bytes in the buffer, as these are assumed to be used as a palette.
To skip the palette, include the following line in your :ref:`flush_callback` function: ``px_map += 8``.
As usual, monochrome displays support partial, full, and direct rendering modes as well.
In full and direct modes, the buffer size should be large enough for the whole screen,
meaning ``(horizontal_resolution x vertical_resolution / 8) + 8`` bytes.
As LVGL can not handle fractional width make sure to round the horizontal resolution
to 8 bits (for example 90 to 96).
The :cpp:func:`lv_draw_sw_i1_convert_to_vtiled` function is used to convert a draw
buffer in I1 color format from a row-wise (htiled) to a column-wise (vtiled) buffer
layout. This conversion is necessary for certain display controllers that require a
different draw buffer mapping. The function assumes that the buffer width and height
are rounded to a multiple of 8. The bit order of the resulting vtiled buffer can be
specified using the `bit_order_lsb` parameter.
For more details, refer to the implementation in
:cpp:func:`lv_draw_sw_i1_convert_to_vtiled` in :file:`src/draw/sw/lv_draw_sw.c`.
To ensure that the redrawn areas start and end on byte boundaries, you can add a
rounder callback to your display driver. This callback will round the width and
height to the nearest multiple of 8.
Here is an example of how to implement and set a rounder callback:
.. code:: c
static void my_rounder_cb(lv_event_t *e)
{
lv_area_t *area = lv_event_get_param(e);
/* Round the height to the nearest multiple of 8 */
area->y1 = (area->y1 & ~0x7);
area->y2 = (area->y2 | 0x7);
}
lv_display_add_event_cb(display, my_rounder_cb, LV_EVENT_INVALIDATE_AREA, display);
In this example, the `my_rounder_cb` function rounds the coordinates of the redrawn
area to the nearest multiple of 8. The `x1` and `y1` coordinates are rounded down,
while the `x2` and `y2` coordinates are rounded up. This ensures that the width and
height of the redrawn area are always multiples of 8.
.. _transparent_screens:
Transparent Screens
*******************
Usually, the opacity of the Screen is :cpp:enumerator:`LV_OPA_COVER` to provide a
solid background for its children. If this is not the case (opacity <
100%) the display's ``bottom_layer`` will be visible. If the bottom layer's
opacity is also not :cpp:enumerator:`LV_OPA_COVER` LVGL will have no solid background
to draw.
This configuration (transparent Screen) could be useful to create, for example,
on-screen display (OSD) menus where a video is played on a different hardware layer
of the display panel, and a menu is overlaid on a higher layer.
To properly render a UI on a transparent Screen the Display's color format needs to
be set to one with an alpha channel (for example LV_COLOR_FORMAT_ARGB8888).
In summary, to enable transparent screens and displays for OSD menu-like UIs:
- Set the screen's ``bg_opa`` to transparent:
:cpp:expr:`lv_obj_set_style_bg_opa(lv_screen_active(), LV_OPA_TRANSP, LV_PART_MAIN)`
- Set the bottom layer's ``bg_opa`` to transparent:
:cpp:expr:`lv_obj_set_style_bg_opa(lv_layer_bottom(), LV_OPA_TRANSP, LV_PART_MAIN)`
- Set a color format with alpha channel. E.g.
:cpp:expr:`lv_display_set_color_format(disp, LV_COLOR_FORMAT_ARGB8888)`
API
***
.. API equals:
LV_COLOR_DEPTH
LV_COLOR_FORMAT_ARGB8888
lv_display_set_buffers
lv_display_set_color_depth
lv_display_set_color_format
lv_draw_sw_i1_convert_to_vtiled
lv_draw_sw_rgb565_swap
lv_obj_set_style_bg_opa

View File

@@ -0,0 +1,27 @@
.. _display_events:
Events
******
:cpp:expr:`lv_display_add_event_cb(disp, event_cb, LV_EVENT_..., user_data)` adds
an event handler to a display.
If you added ``user_data`` to the Display, you can retrieve it in an event like this:
.. code-block:: c
lv_display_t * display1;
my_type_t * my_user_data;
display1 = (lv_display_t *)lv_event_get_current_target(e);
my_user_data = lv_display_get_user_data(display1);
The following events are sent for Display (lv_display_t) objects:
.. include:: display_events_list.txt
API
***
.. API equals: lv_display_add_event_cb

View File

@@ -0,0 +1,32 @@
..
display_events_list.txt
Note: this is used in full in 2 places currently. Thus, it is placed
here so its source is only in one place and will be fully duplicated
where it is used:
- display_events.rst
- event.rst
- :cpp:enumerator:`LV_EVENT_INVALIDATE_AREA` An area is invalidated (marked for redraw).
:cpp:expr:`lv_event_get_param(e)` returns a pointer to an :cpp:struct:`lv_area_t`
object with the coordinates of the area to be invalidated. The area can
be freely modified if needed to adapt it a special requirement of the
display. Usually needed with monochrome displays to invalidate ``N x 8``
rows or columns in one pass.
- :cpp:enumerator:`LV_EVENT_RESOLUTION_CHANGED`: Sent when the resolution changes due
to :cpp:func:`lv_display_set_resolution` or :cpp:func:`lv_display_set_rotation`.
- :cpp:enumerator:`LV_EVENT_COLOR_FORMAT_CHANGED`: Sent as a result of any call to `lv_display_set_color_format()`.
- :cpp:enumerator:`LV_EVENT_REFR_REQUEST`: Sent when something happened that requires redraw.
- :cpp:enumerator:`LV_EVENT_REFR_START`: Sent before a refreshing cycle starts. Sent even
if there is nothing to redraw.
- :cpp:enumerator:`LV_EVENT_REFR_READY`: Sent when refreshing has been completed (after
rendering and calling :ref:`flush_callback`). Sent even if no redraw happened.
- :cpp:enumerator:`LV_EVENT_RENDER_START`: Sent just before rendering begins.
- :cpp:enumerator:`LV_EVENT_RENDER_READY`: Sent after rendering has been completed (before
calling :ref:`flush_callback`)
- :cpp:enumerator:`LV_EVENT_FLUSH_START`: Sent before :ref:`flush_callback` is called.
- :cpp:enumerator:`LV_EVENT_FLUSH_FINISH`: Sent after :ref:`flush_callback` call has returned.
- :cpp:enumerator:`LV_EVENT_FLUSH_WAIT_START`: Sent at the beginning of internal call to
`wait_for_flushing()` -- happens whether or not any waiting will actually occur.
Call returns immediately if `disp->flushing == 0`.
- :cpp:enumerator:`LV_EVENT_FLUSH_WAIT_FINISH`: Sent when the call to `wait_for_flushing()`
is about to return, regardless whether any actual waiting occurred.

View File

@@ -0,0 +1,28 @@
.. _extending_combining_displays:
============================
Extending/Combining Displays
============================
.. _display_mirroring:
Mirroring a Display
*******************
To mirror the image of a display to another display, you don't need to use
multi-display support. Just transfer the buffer received in the first display's
:ref:`flush_callback` to the other display as well.
.. _display_split_image:
Splitting an Image
******************
You can create a larger virtual display from an array of smaller ones.
You can create it by:
1. setting the resolution of the displays to the large display's resolution;
2. in :ref:`flush_callback`, truncate and modify the ``area`` parameter for each display; and
3. send the buffer's content to each real display with the truncated area.

View File

@@ -0,0 +1,30 @@
.. _display_inactivity:
======================
Inactivity Measurement
======================
A user's inactivity time is measured and stored with each ``lv_display_t`` object.
Every use of an :ref:`Input Device <indev>` (if :ref:`associated with the display
<indev_other_features>`) counts as an activity. To get time elapsed since the last
activity, use :cpp:expr:`lv_display_get_inactive_time(display1)`. If ``NULL`` is
passed, the lowest inactivity time among all displays will be returned (in this case
NULL does *not* mean the :ref:`default_display`).
You can manually trigger an activity using
:cpp:expr:`lv_display_trigger_activity(display1)`. If ``display1`` is ``NULL``, the
:ref:`default_display` will be used (**not all displays**).
.. admonition:: Further Reading
- `lv_port_disp_template.c <https://github.com/lvgl/lvgl/blob/master/examples/porting/lv_port_disp_template.c>`__
for a template for your own driver.
- :ref:`Drawing <draw>` to learn more about how rendering works in LVGL.
API
***
.. API equals: lv_display_get_inactive_time

View File

@@ -0,0 +1,29 @@
.. _display:
====================
Display (lv_display)
====================
.. toctree::
:maxdepth: 2
overview
setup
screen_layers
color_format
refreshing
display_events
resolution
inactivity
rotation
redraw_area
tiling
extending_combining
.. _display_api:
API
***
:ref:`lv_display_h`

View File

@@ -0,0 +1,76 @@
.. _display_overview:
========
Overview
========
What is a Display?
******************
In LVGL, an *lv_display_t* (not to be confused with a :ref:`Screen <screens>`) is a
data type that represents a single display panel --- the hardware that displays
LVGL-rendered pixels on your device.
.. _multiple_displays:
How Many Displays Can LVGL Use?
*******************************
LVGL can use any number of displays. It is only limited by available RAM and MCU time.
Why would you want multi-display support? Here are some examples:
- Have a "normal" TFT display with local UI and create "virtual" screens on VNC
on demand. (You need to add your VNC driver.)
- Have a large TFT display and a small monochrome display.
- Have some smaller and simple displays in a large instrument or technology.
- Have two large TFT displays: one for a customer and one for the shop assistant.
If you set up LVGL to use more than one display, be aware that some functions use the
:ref:`default_display` during their execution, such as creating :ref:`screens`.
.. _display_attributes:
Attributes
**********
Once created, a Display object remembers the characteristics of the display hardware
it is representing, as well as other things relevant to its lifetime:
- Resolution (width and height in pixels)
- Color Depth (bits per pixel)
- Color Format (how colors in pixels are laid out)
- DPI (default is configured :c:macro:`LV_DPI_DEF` in ``lv_conf.h``, but can be
modified with :cpp:expr:`lv_display_set_dpi(disp, new_dpi)`).
- 4 :ref:`display_screen_layers` automatically created with each display
- All :ref:`screens` created in association with this display (and not yet deleted---only
one is displayed at any given time)
- The :ref:`draw_buffers` assigned to it
- The :ref:`flush_callback` function that moves pixels from :ref:`draw_buffers` to Display hardware
- What areas of the display have been updated (made "dirty") so rendering logic can
compute what to render during a :ref:`display refresh <basic_data_flow>`
- Optional custom pointer as :ref:`display_user_data`
.. _display_user_data:
User Data
*********
With :cpp:expr:`lv_display_set_user_data(display1, p)` a custom pointer can be stored
with ``lv_display_t`` object. This pointer can be used later, e.g. in
:ref:`display_events`. See code example for how to do this in :ref:`display_events`.
API
***
.. API equals:
lv_display_set_dpi
lv_display_set_user_data

View File

@@ -0,0 +1,36 @@
.. _display_redraw_area:
===========================
Constraints on Redrawn Area
===========================
Some display controllers have specific requirements for the window area where the
rendered image can be sent (e.g., `x1` must be even, and `x2` must be odd).
In the case of monochrome displays, `x1` must be `Nx8`, and `x2` must be `Nx8 - 1`.
(If the display uses `LV_COLOR_FORMAT_I1`, LVGL automatically applies this rounding.
See :ref:`display_monochrome`.)
The size of the invalidated (redrawn) area can be controlled as follows:
.. code-block:: c
void rounder_event_cb(lv_event_t * e)
{
lv_area_t * a = lv_event_get_invalidated_area(e);
a->x1 = a->x1 & (~0x1); /* Ensure x1 is even */
a->x2 = a->x2 | 0x1; /* Ensure x2 is odd */
}
...
lv_display_add_event_cb(disp, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
API
***
.. API equals:
lv_event_get_invalidated_area

View File

@@ -0,0 +1,77 @@
.. _display_refreshing:
==========
Refreshing
==========
Default Refresh Behavior
************************
Normally the dirty (a.k.a invalid) areas are checked and redrawn in
every :c:macro:`LV_DEF_REFR_PERIOD` milliseconds (set in ``lv_conf.h``).
This happens as a result of a refresh :ref:`timer` created that gets created when
the display is created, and is executed at that interval.
.. _display_decoupling_refresh_timer:
Decoupling the Display Refresh Timer
************************************
However, in some cases you might need more control on when display
refreshing happens, for example:
- to synchronize rendering with VSYNC or the TE signal;
- to time display refreshes immediately after a single screen update of all widgets
that needed to have their display data updated (i.e. only updated once immediately
before display refresh to reduce CPU overhead).
You can do this in the following way:
.. code-block:: c
/* Delete original display refresh timer */
lv_display_delete_refr_timer(display1);
/* Call this to refresh dirty (changed) areas of the display. */
lv_display_refr_timer(NULL);
If you have multiple displays call :cpp:expr:`lv_display_set_default(display1)` to
select the display to refresh before :cpp:expr:`lv_display_refr_timer(NULL)`.
.. note:: :cpp:func:`lv_timer_handler` and :cpp:func:`lv_display_refr_timer` must not run at the same time.
If the performance monitor is enabled, the value of :c:macro:`LV_DEF_REFR_PERIOD` needs to be set to be
consistent with the refresh period of the display to ensure that the statistical results are correct.
.. _display_force_refresh:
Forcing a Refresh
*****************
Normally the invalidated areas (marked for redrawing) are rendered in
:cpp:func:`lv_timer_handler` in every :c:macro:`LV_DEF_REFR_PERIOD` milliseconds.
However, by using :cpp:expr:`lv_refr_now(display)` you can tell LVGL to redraw the
invalid areas immediately. The refreshing will happen in :cpp:func:`lv_refr_now`
which might take longer.
The parameter of :cpp:func:`lv_refr_now` is a pointer to the display to refresh. If
``NULL`` is passed, all displays that have active refresh timers will be refreshed.
API
***
.. API equals:
LV_DEF_REFR_PERIOD
lv_display_refr_timer
lv_display_set_default
lv_refr_now
lv_timer_handler

View File

@@ -0,0 +1,27 @@
.. _display_resolution:
===================
Changing Resolution
===================
To set the resolution of the display after creation use
:cpp:expr:`lv_display_set_resolution(display, hor_res, ver_res)`
It's not mandatory to use the whole display for LVGL, however in some
cases the physical resolution is important. For example the touchpad
still sees the whole resolution and the values needs to be converted to
the active LVGL display area. So the physical resolution and the offset
of the active area can be set with
:cpp:expr:`lv_display_set_physical_resolution(disp, hor_res, ver_res)` and
:cpp:expr:`lv_display_set_offset(disp, x, y)`
API
***
.. API equals:
lv_display_set_resolution,
lv_display_set_physical_resolution,
lv_display_set_offset

View File

@@ -0,0 +1,106 @@
.. _display_rotation:
========
Rotation
========
LVGL supports rotation of the display in 90 degree increments.
The orientation of the display can be changed with
:cpp:expr:`lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_xxx)` where ``xxx`` is
0, 90, 180 or 270. This will swap the horizontal and vertical resolutions internally
according to the set degree, however it will not perform the actual rotation.
When changing the rotation, the :cpp:enumerator:`LV_EVENT_SIZE_CHANGED` event is
emitted to allow for hardware reconfiguration. If your display panel and/or its
driver chip(s) do not support rotation, :cpp:func:`lv_draw_sw_rotate` can be used to
rotate the buffer in the :ref:`flush_callback` function.
:cpp:expr:`lv_display_rotate_area(display, &area)` rotates the rendered area
according to the current rotation settings of the display.
Note that in :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT` the small changed areas
are rendered directly in the frame buffer so they cannot be
rotated later. Therefore in direct mode only the whole frame buffer can be rotated.
In the case of :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` the small rendered areas
can be rotated on their own before flushing to the frame buffer.
:cpp:enumerator:`LV_DISPLAY_RENDER_MODE_FULL` can work with rotation if the buffer(s)
being rendered to are different than the buffer(s) being rotated to in the flush callback
and the buffers being rendered to do not have a stride requirement.
Below is an example for rotating when the rendering mode is
:cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` and the rotated image should be sent to a
**display controller**.
.. code-block:: c
/*Rotate a partially rendered area to another buffer and send it*/
void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
{
lv_display_rotation_t rotation = lv_display_get_rotation(disp);
lv_area_t rotated_area;
if(rotation != LV_DISPLAY_ROTATION_0) {
lv_color_format_t cf = lv_display_get_color_format(disp);
/*Calculate the position of the rotated area*/
rotated_area = *area;
lv_display_rotate_area(disp, &rotated_area);
/*Calculate the source stride (bytes in a line) from the width of the area*/
uint32_t src_stride = lv_draw_buf_width_to_stride(lv_area_get_width(area), cf);
/*Calculate the stride of the destination (rotated) area too*/
uint32_t dest_stride = lv_draw_buf_width_to_stride(lv_area_get_width(&rotated_area), cf);
/*Have a buffer to store the rotated area and perform the rotation*/
static uint8_t rotated_buf[500*1014];
int32_t src_w = lv_area_get_width(area);
int32_t src_h = lv_area_get_height(area);
lv_draw_sw_rotate(px_map, rotated_buf, src_w, src_h, src_stride, dest_stride, rotation, cf);
/*Use the rotated area and rotated buffer from now on*/
area = &rotated_area;
px_map = rotated_buf;
}
my_set_window(area->x1, area->y1, area->x2, area->y2);
my_send_colors(px_map);
}
Below is an example for rotating when the rendering mode is
:cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` and the image can be rotated directly
into a **frame buffer of the LCD peripheral**.
.. code-block:: c
/*Rotate a partially rendered area to the frame buffer*/
void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
{
lv_color_format_t cf = lv_display_get_color_format(disp);
uint32_t px_size = lv_color_format_get_size(cf);
/*Calculate the position of the rotated area*/
lv_area_t rotated_area = *area;
lv_display_rotate_area(disp, &rotated_area);
/*Calculate the properties of the source buffer*/
int32_t src_w = lv_area_get_width(area);
int32_t src_h = lv_area_get_height(area);
uint32_t src_stride = lv_draw_buf_width_to_stride(src_w, cf);
/*Calculate the properties of the frame buffer*/
int32_t fb_stride = lv_draw_buf_width_to_stride(disp->hor_res, cf);
uint8_t * fb_start = my_fb_address;
fb_start += rotated_area.y1 * fb_stride + rotated_area.x1 * px_size;
lv_display_rotation_t rotation = lv_display_get_rotation(disp);
if(rotation == LV_DISPLAY_ROTATION_0) {
int32_t y;
for(y = area->y1; y <= area->y2; y++) {
lv_memcpy(fb_start, px_map, src_stride);
px_map += src_stride;
fb_start += fb_stride;
}
}
else {
lv_draw_sw_rotate(px_map, fb_start, src_w, src_h, src_stride, fb_stride, rotation, cf);
}
}
API
***
.. API equals: lv_display_set_rotation

View File

@@ -0,0 +1,103 @@
.. _display_screen_layers:
=============
Screen Layers
=============
When an ``lv_display_t`` object is created, 4 permanent :ref:`screens` that
facilitate layering are created and attached to it.
1. Bottom Layer (below Active Screen, transparent, not scroll-able, but click-able)
2. :ref:`active_screen`
3. Top Layer (above Active Screen, transparent and neither scroll-able nor click-able)
4. System Layer (above Top Layer, transparent and neither scroll-able nor click-able)
1, 3 and 4 are independent of the :ref:`active_screen` and they will be shown (if
they contain anything that is visible) regardless of which screen is the
:ref:`active_screen`.
.. note::
For the bottom layer to be visible, the Active Screen's background has to be
at least partially, if not fully, transparent.
You can get pointers to each of these screens on the :ref:`default_display` by using
(respectively):
- :cpp:func:`lv_screen_active`,
- :cpp:func:`lv_layer_top`,
- :cpp:func:`lv_layer_sys`, and
- :cpp:func:`lv_layer_bottom`.
You can get pointers to each of these screens on a specified display by using
(respectively):
- :cpp:expr:`lv_display_get_screen_active(disp)`,
- :cpp:expr:`lv_display_get_layer_top(disp)`,
- :cpp:expr:`lv_display_get_layer_sys(disp)`, and
- :cpp:expr:`lv_display_get_layer_bottom(disp)`.
To set a Screen you create to be the :ref:`active_screen`, call
:cpp:func:`lv_screen_load` or :cpp:func:`lv_screen_load_anim`.
.. _layers_top_and_sys:
Top and System Layers
*********************
LVGL uses the Top Layer and System Layer two empower you to ensure that certain
:ref:`widgets` are *always* on top of other layers.
You can add "pop-up windows" to the *Top Layer* freely. The Top Layer was meant to
be used to create Widgets that are visible on all Screens shown on a Display. But,
the *System Layer* is intended for system-level things (e.g. mouse cursor will be
placed there with :cpp:func:`lv_indev_set_cursor`).
These layers work like any other Widget, meaning they have styles, and any kind of
Widgets can be created in them.
.. note::
While the Top Layer and System Layer are created by their owning :ref:`display`
as not scroll-able and not click-able, these behaviors can be overridden the same
as any other Widget by using :cpp:expr:`lv_obj_set_scrollbar_mode(scr1, LV_SCROLLBAR_MODE_xxx)`
and :cpp:expr:`lv_obj_add_flag(scr1, LV_OBJ_FLAG_CLICKABLE)` respectively.
If the :cpp:enumerator:`LV_OBJ_FLAG_CLICKABLE` flag is set on the Top Layer, then it will
absorb all user clicks and acts as a modal Widget.
.. code-block:: c
lv_obj_add_flag(lv_layer_top(), LV_OBJ_FLAG_CLICKABLE);
.. _layers_bottom:
Bottom Layer
************
Similar to the Top- and System Layers, the Bottom Layer is also the full size of the
Display, but it is located below the :ref:`active_screen`. It's visible only if the
Active Screen's background opacity is < 255.
.. admonition:: Further Reading
:ref:`transparent_screens`.
API
***
.. API equals:
lv_screen_active,
lv_layer_top,
lv_layer_sys,
lv_layer_bottom,
lv_display_get_screen_active,
lv_display_get_layer_top,
lv_display_get_layer_sys,
lv_display_get_layer_bottom

View File

@@ -0,0 +1,212 @@
.. _display_setup:
==========================
Setting Up Your Display(s)
==========================
During system initialization, you must do the following for each physical display
panel you want LVGL to use:
- :ref:`create an lv_display_t <creating_a_display>` object for it,
- assign its :ref:`draw_buffers`, and
- assign a :ref:`flush_callback` for it.
.. _creating_a_display:
Creating a Display
******************
To create a display for LVGL:
.. code-block:: c
lv_display_t * display1 = lv_display_create(hor_res, ver_res)
You can create :ref:`multiple displays <multiple_displays>` with a different driver for
each (see below).
When an ``lv_display_t`` object is created, with it are created 4 Screens set up
to help you manage layering of displayed Widgets. See :ref:`transparent_screens` and
:ref:`display_screen_layers` for more information.
.. _default_display:
Default Display
---------------
When the first :ref:`display` object is created, it becomes the Default Display. If
other Display Objects are created (to service additional Display Panels), the Default
Display remains the first one created.
To set another :ref:`display` as the Default Display, call :cpp:func:`lv_display_set_default`.
See :ref:`multiple_displays` for more information about using multiple displays.
For many ``lv_display_...()`` functions, passing NULL for the ``disp`` argument will
cause the function to target the Default Display. Check the API documentation for
the function you are calling to be sure.
.. _draw_buffers:
Draw Buffer(s)
**************
During system initialization, you must set drawing buffers for LVGL to use for each
display. Do so by calling:
.. code-block:: c
lv_display_set_buffers(display1, buf1, buf2, buf_size_in_bytes, render_mode)
- ``buf1`` a buffer to which LVGL can render pixels
- ``buf2`` a second optional buffer (see below)
- ``buf_size_in_bytes`` size of buffer(s) in bytes
- ``render_mode`` is one of the following:
- :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` Use the buffer(s) to render
to the display using buffers smaller than the size of the display. Use of
buffers at least 1/10 display size is recommended. In :ref:`flush_callback` the rendered
images needs to be copied to the given area of the display. In this mode if a
button is pressed only the button's area will be redrawn.
- :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT` The buffer size(es) must match
the size of the display. LVGL will render into the correct location of the
buffer. Using this method the buffer(s) always contain the whole display image.
If two buffer are used, the rendered areas are automatically copied to the
other buffer after flushing. Due to this in :ref:`flush_callback` typically
only a frame buffer address needs to be changed. If a button is pressed
only the button's area will be redrawn.
- :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_FULL` The buffer size(es) must match
the size of the display. LVGL will always redraw the whole screen even if only
1 pixel has been changed. If two display-sized draw buffers are provided,
LVGL's display handling works like "traditional" double buffering. This means
the :ref:`flush_callback` callback only has to update the address of the frame buffer to
the ``px_map`` parameter.
Simple Example
--------------
.. code-block:: c
/* Declare buffer for 1/10 screen size; BYTES_PER_PIXEL will be 2 for RGB565. */
#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))
static uint8_t buf1[MY_DISP_HOR_RES * MY_DISP_VER_RES / 10 * BYTES_PER_PIXEL];
/* Set display buffer for display `display1`. */
lv_display_set_buffers(display1, buf1, NULL, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL);
One Buffer
----------
If only one buffer is used, LVGL draws the content of the screen into
that draw buffer and sends it to the display via the :ref:`flush_callback`. LVGL
then waits until :cpp:func:`lv_display_flush_ready` is called
(that is, the content of the buffer has been sent to the
display) before drawing something new into it.
Two Buffers
-----------
If two buffers are used LVGL can draw into one buffer while the content
of the other buffer is sent to the display in the background. DMA or
other hardware should be used to transfer data to the display so the MCU
can continue drawing. Doing so allows *rendering* and *refreshing* the
display to become parallel operations.
Three Buffers
-------------
Triple buffering enhances parallelism between rendering and data transfer compared
to double buffering. When one buffer has completed rendering and another is actively
undergoing DMA transfer, the third buffer enables immediate rendering of the next frame,
eliminating CPU/GPU idle time caused by waiting for DMA completion.
The third buffer is configured using the :cpp:func:`lv_display_set_3rd_draw_buffer` function.
.. _flush_callback:
Flush Callback
**************
Draw buffer(s) are simple array(s) that LVGL uses to render the display's
content. Once rendering is has been completed, the content of the draw buffer is
sent to the display using a Flush Callback function.
An example looks like this:
.. code-block:: c
void my_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map)
{
/* The most simple case (also the slowest) to send all rendered pixels to the
* screen one-by-one. `put_px` is just an example. It needs to be implemented by you. */
uint16_t * buf16 = (uint16_t *)px_map; /* Let's say it's a 16 bit (RGB565) display */
int32_t x, y;
for(y = area->y1; y <= area->y2; y++) {
for(x = area->x1; x <= area->x2; x++) {
put_px(x, y, *buf16);
buf16++;
}
}
/* IMPORTANT!!!
* Inform LVGL that flushing is complete so buffer can be modified again. */
lv_display_flush_ready(display);
}
During system initialization, tell LVGL you want that function to copy pixels from
rendered pixel-buffers to a particular display by doing the following:
.. code-block:: c
lv_display_set_flush_cb(display1, my_flush_cb)
Note that which display is targeted is passed to the function, so you can use the
same function for multiple displays, or use different functions for multiple
displays. It's up to you.
.. note::
:cpp:expr:`lv_display_flush_ready(display1)` needs to be called when flushing is
complete to inform LVGL that the buffer is available again to render new content
into it.
LVGL might render the screen in multiple chunks and therefore call your Flush
Callback multiple times. To see whether the current call is for the last chunk being
rendered, use :cpp:expr:`lv_display_flush_is_last(display1)`.
.. _display_flush-wait_callback:
Flush-Wait Callback
*******************
By using :cpp:func:`lv_display_flush_ready` LVGL will normally spin in a loop
while waiting for flushing.
However with the help of :cpp:func:`lv_display_set_flush_wait_cb` a custom
wait callback be set for flushing. This callback can use a semaphore, mutex,
or anything else to optimize waiting for the flush to be completed. The callback
need not call :cpp:func:`lv_display_flush_ready` since the caller takes care of
that (clearing the display's ``flushing`` flag) when your callback returns.
However, if a Flush-Wait Callback is not set, LVGL assumes that
:cpp:func:`lv_display_flush_ready` is called after the flush has completed.
API
***
.. API equals:
lv_display_create,
lv_display_flush_is_last,
lv_display_flush_ready,
lv_display_set_buffers,
lv_display_set_default,
lv_display_set_flush_cb,
lv_display_set_flush_wait_cb
lv_display_t,

View File

@@ -0,0 +1,64 @@
.. _display_tiling:
===============
Tiled Rendering
===============
When multiple CPU cores are available and a large area needs to be redrawn, LVGL must
identify independent areas that can be rendered in parallel.
For example, if there are 4 CPU cores, one core can draw the screen's background
while the other 3 must wait until it is finished. If there are 2 buttons on the
screen, those 2 buttons can be rendered in parallel, but 2 cores will still remain
idle.
Due to dependencies among different areas, CPU cores cannot always be fully utilized.
To address this, LVGL can divide large areas that need to be updated into smaller
tiles. These tiles are independent, making it easier to find areas that can be
rendered concurrently.
Specifically, if there are 4 tiles and 4 cores, there will always be an independent
area for each core within one of the tiles.
The maximum number of tiles can be set using the function
:cpp:expr:`lv_display_set_tile_cnt(disp, cnt)`. The default value is
:cpp:expr:`LV_DRAW_SW_DRAW_UNIT_CNT` (or 1 if software rendering is not enabled).
Small areas are not further divided into smaller tiles because the overhead of
spinning up 4 cores would outweigh the benefits.
The ideal tile size is calculated as ``ideal_tile_size = draw_buf_size / tile_cnt``.
For example, in :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT` mode on an 800x480
screen, the display buffer is 800x480 = 375k pixels. If there are 4 tiles, the ideal
tile size is approximately 93k pixels. Based on this, core utilization is as follows:
- 30k pixels: 1 core
- 90k pixels: 1 core
- 95k pixels: 2 cores (above 93k pixels, 2 cores are used)
- 150k pixels: 2 cores
- 200k pixels: 3 cores (above 186k pixels, 3 cores are used)
- 300k pixels: 4 cores (above 279k pixels, 4 cores are used)
- 375k pixels: 4 cores
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT`, the screen-sized draw buffer is
divided by the tile count to determine the ideal tile sizes. If smaller areas are
refreshed, it may result in fewer cores being used.
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_FULL`, the maximum number of tiles is
always created when the entire screen is refreshed.
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL`, the partial buffer is divided
into tiles. For example, if the draw buffer is 1/10th the size of the screen and
there are 2 tiles, then 1/20th + 1/20th of the screen area will be rendered at once.
Tiled rendering only affects the rendering process, and the :ref:`flush_callback` is
called once for each invalidated area. Therefore, tiling is not visible from the
flushing point of view.
API
***
.. API equals: lv_display_set_tile_cnt, LV_DISPLAY_RENDER_MODE_FULL

View File

@@ -0,0 +1,90 @@
.. _draw_api:
========
Draw API
========
Where to Use the Drawing API
****************************
In most cases you use LVGL's Drawing API through the API of Widgets: by creating
buttons, labels, etc., and setting the their styles, positions, and other properties.
In these cases rendering (drawing) is handled internally and you doen't see the
:ref:`Drawing Pipeline <draw_pipeline>` at all.
However there are three places where you can use LVGL's Drawing API directly.
1. **In the draw events of the Widgets**:
There are event codes which are sent when the Widget needs to render itself:
- :cpp:enumerator:`LV_EVENT_DRAW_MAIN_BEGIN`, :cpp:enumerator:`LV_EVENT_DRAW_MAIN`,
:cpp:enumerator:`LV_EVENT_DRAW_MAIN_END`:
Triggered before, during, and after a Widget is drawn, respectively. Widget
rendering typically occurs in :cpp:enumerator:`LV_EVENT_DRAW_MAIN`.
- :cpp:enumerator:`LV_EVENT_DRAW_POST_BEGIN`, :cpp:enumerator:`LV_EVENT_DRAW_POST`,
:cpp:enumerator:`LV_EVENT_DRAW_POST_END`:
Triggered before, during, and after all child Widgets are rendered, respectively.
This can be useful for overlay-like drawings, such as scrollbars which should be
rendered on top of any children.
These are relevant if a new Widget is implemented and it uses custom drawing.
2. **Modifying the created draw tasks**:
The when a draw task is created for a Widget :cpp:enumerator:`LV_EVENT_DRAW_TASK_ADDED`
is sent. In this event the created draw task can be modified or new draw tasks
can be added. Typical use cases for this are modifying each bar of a bar chart,
or cells of a table.
For performance reasons, this event is disabled by default. Enable it by setting
the :cpp:enumerator:`LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS` flag on the Widget(s) you
wish to emit this event.
3. **Draw to the Canvas Widget**:
The drawing functions can be used directly to draw to a Canvas Widget. Doing so
renders custom drawing to a buffer which can be used later as an image or a mask.
For more information see :ref:`lv_canvas`.
Drawing API
***********
The main components of LVGL's Drawing API are the :cpp:func:`lv_draw_rect`,
:cpp:func:`lv_draw_label`, :cpp:func:`lv_draw_image`, and similar functions.
When they are called :cpp:type:`lv_draw_task_t` objects are created internally.
These functions have the following parameters:
- **Layer**: This is the target of the drawing. See details at :ref:`draw_layers`.
- **Draw Descriptor**: This is a large ``struct`` containing all the information
about the drawing. See details of the draw descriptors at :ref:`draw_descriptors`.
- **Area** (in some cases): Specifies where to draw.
Coordinate System
*****************
Some functions and draw descriptors require area or point parameters. These are
always **absolute coordinates** on the display. For example, if the target layer is
on a 800x480 display and the layer's area is (100,100) (200,200), to render a 10x10
object in the middle, the coordinates (145,145) (154,154) should be used
(not (40,40) (49,49)).
Exception: for the Canvas Widget the layer is always assumed to be at the (0,0)
coordinate, regardless of the Canvas Widget's position.
API
***
.. API equals:
LV_EVENT_DRAW_MAIN_BEGIN
lv_draw_arc
lv_draw_image
lv_draw_label
lv_draw_line
lv_draw_mask_rect
lv_draw_triangle

View File

@@ -0,0 +1,705 @@
.. _draw_descriptors:
================
Draw Descriptors
================
Overview
********
Each :ref:`Draw Task <draw tasks>` type has its own draw descriptor type. For
example, :cpp:type:`lv_draw_label_dsc_t` is used for label drawing,
:cpp:type:`lv_draw_image_dsc_t` is used for image drawing.
When an ``lv_draw_...`` function is called, it creates a Draw Task, copies the draw
descriptor into a ``malloc``\ ed memory block, and frees it automatically when
needed. Therefore, local draw descriptor variables can be safely used.
Relation to Styles
******************
In most cases, style properties map 1-to-1 to draw descriptor fields. For example:
- ``label_dsc.color`` corresponds to the ``text_color`` style property.
- ``shadow_dsc.width``, ``line_dsc.opa``, and ``arc_dsc.width`` map to
``shadow_width``, ``line_opa``, and ``arc_width`` in styles.
See :ref:`style_properties` to see the list of style properties and what they mean.
Base Draw Descriptor
********************
In each draw descriptor there is a generic "base descriptor" with
:cpp:type:`lv_draw_dsc_base_t` type and with ``base`` in its name. For example
``label_dsc.base``. This ``struct`` stores useful information about which Widget
and part created the draw descriptor. See all the fields in
:cpp:type:`lv_draw_dsc_base_t`.
In an :cpp:enumerator:`LV_EVENT_DRAW_TASK_ADDED` event, the elements of the base draw
descriptor are very useful to identify the Draw Task. For example:
.. code-block:: c
/* In LV_EVENT_DRAW_TASK_ADDED */
lv_draw_task_t * t = lv_event_get_draw_task(e);
lv_draw_base_dsc_t * draw_dsc = lv_draw_task_get_draw_dsc(t);
draw_dsc.obj; /* The Widget for which the draw descriptor was created */
draw_dsc.part; /* The Widget part for which the draw descriptor was created
E.g. LV_PART_INDICATOR */
draw_dsc.id1; /* A Widget type specific ID (e.g. table row index).
See the docs of the given Widget. */
draw_dsc.id2;
draw_dsc.layer; /* The target layer.
Required when a new Draw Tasks are also created */
Simple Initilialzation
----------------------
Before using a draw descriptor it needs to be initialized with
the related function. For example, :cpp:expr:`lv_draw_label_dsc_init(&my_label_draw_dsc)`.
After initialization, each field of the draw descriptor can be set. The default
values are quite sane and reasonable, so usually only a few fields need modification.
For example:
.. code-block:: c
/* In LV_EVENT_DRAW_MAIN */
lv_draw_label_dsc_t my_label_draw_dsc;
lv_draw_label_dsc_init(&my_label_draw_dsc);
my_label_draw_dsc.font = &my_font;
my_label_draw_dsc.color = lv_color_hex(0xff0000);
my_label_draw_dsc.text = "Hello";
lv_area_t a = {10, 10, 200, 50}; /* Draw label here */
lv_draw_label(lv_event_get_layer(e), &my_label_draw_dsc, &a);
Initilialzation for Widgets
---------------------------
When rendering a part of a Widget, helper functions can initialize draw
descriptors based on the styles, and a specific Widget part in the current state.
For example:
.. code-block:: c
/* In LV_EVENT_DRAW_MAIN */
lv_draw_rect_dsc_t cur_dsc;
lv_draw_rect_dsc_init(&cur_dsc);
lv_obj_init_draw_rect_dsc(obj, LV_PART_CURSOR, &cur_dsc);
cur_dsc.fill_color = lv_color_hex(0xff0000); /* Modify if needed */
lv_draw_rect(layer, &cur_dsc, &area);
The ``lv_obj_init_draw_...`` functions automatically initialize the fields of
the base descriptor.
Modify the draw descriptors
---------------------------
In :cpp:enumerator:`LV_EVENT_DRAW_TASK_ADDED`, the draw descriptor of the ``draw_task`` can be
accessed (using :cpp:type:`lv_draw_task_get_label_dsc()` and similar functions)
and modified (to change color, text, font, etc.). This means that in
:cpp:enumerator:`LV_EVENT_DRAW_TASK_ADDED`, the ``draw_task``\ s and draw descriptors
are already initialized and it's enough to change only a few specific values.
For example:
.. code-block:: c
/* In LV_EVENT_DRAW_TASK_ADDED */
lv_draw_task_t * t = lv_event_get_draw_task(e);
lv_draw_label_dsc_t * draw_dsc = lv_draw_task_get_label_dsc(t);
/* Check a few things in `draw_dsc->base` */
/* Make the color lighter for longer texts */
draw_dsc->color = lv_color_lighten(draw_dsc->color,
LV_MIN(lv_strlen(draw_dsc->text) * 5, 255));
/* Create new Draw Tasks if needed by calling
* `lv_draw_...(draw_dsc->base.layer, ...)` functions */
Rectangle Draw Descriptor
*************************
:cpp:type:`lv_draw_rect_dsc_t` is a helper descriptor that combines:
- Fill
- Border
- Outline (a border with its own styles)
- Shadow
- Background image (an image with its own styles)
into a single call.
``lv_obj_init_draw_rect_dsc(obj, part, &dsc);`` initializes a draw descriptor
from a Widget, and ``lv_draw_rect(layer, &dsc, area)`` draws the rectangle in a
specified area.
.. lv_example:: widgets/canvas/lv_example_canvas_3
:language: c
Fill Draw Descriptor
********************
The main fields of :cpp:type:`lv_draw_fill_dsc_t` are straightforward. It has a
radius, opacity, and color to draw a rectangle. If opacity is 0, no draw task will
be created.
- :cpp:expr:`lv_draw_fill_dsc_init(&dsc)` initializes a fill Draw Task.
- :cpp:expr:`lv_draw_sw_fill(layer, &dsc, area)` creates a Draw Task to fill an area.
- :cpp:expr:`lv_draw_task_get_fill_dsc(draw_task)` retrieves the fill descriptor from
a Draw Task.
Gradients
*********
The ``grad`` field of the fill descriptor (or :cpp:type:`lv_grad_dsc_t` in general)
supports:
- Horizontal
- Vertical
- Skew
- Radial
- Conical
gradient types.
The following show some example gradients.
.. lv_example:: styles/lv_example_style_2
:language: c
.. lv_example:: styles/lv_example_style_16
:language: c
.. lv_example:: styles/lv_example_style_17
:language: c
.. lv_example:: styles/lv_example_style_18
:language: c
For each gradient type, multiple color and opacity values can be assigned. These are
called "stops". The maximum number of stops is limited to
:c:macro:`LV_GRADIENT_MAX_STOPS`.
A gradient is basically a transition of colors and opacities between stops.
Besides just setting the color and opacity of each stop, it is also possible to set
where they start relative to the whole gradient area.
For example with 3 stops it can be set like this:
- 10% red: 0--10% fully red
- 60% green: 10--60% transition from red to green, 60% is fully green
- 65% blue: fast transition from green to blue between 60%--65%. After 65% fully blue.
The position of the stops are called fractions or offsets and are 8 bit values where
0 is 0% and 255 is 100% of the whole gradient area.
:cpp:expr:`lv_grad_init_stops(grad_dsc, colors, opas, fracs, cnt)` initializes
a gradient descriptor with stops containing the color, opacity and fraction of each
stop.
.. code-block:: c
static const lv_color_t colors[2] = {
LV_COLOR_MAKE(0xe8, 0xe8, 0xe8),
LV_COLOR_MAKE(0x79, 0x79, 0x79),
};
static const lv_opa_t opas[2] = {
170,
255,
};
static const uint8_t fracs[2] = {
170,
255,
};
lv_grad_init_stops(&grad, colors, opas, fracs, sizeof(colors) / sizeof(lv_color_t));
If the opacity array is ``NULL`` 255 will be used for each stop. If the fractions
array is ``NULL`` the colors will be distributed evenly. For example with 3 colors:
0%, 50%, 100%
Padding
-------
Linear, radial, and conic gradients are defined between two points or angles. You
can define how to pad the areas outside of the start and end points or angles:
- :cpp:enumerator:`LV_GRAD_EXTEND_PAD`: Repeat the same color
- :cpp:enumerator:`LV_GRAD_EXTEND_REPEAT`: Repeat the pattern
- :cpp:enumerator:`LV_GRAD_EXTEND_REFLECT`: Repeat the pattern normally and mirrored alternately
Horizontal and Vertical Gradients
---------------------------------
The simplest and usually fastest gradient types are horizontal and vertical gradients.
After initializing the stops with :cpp:expr:`lv_grad_init_stops` call either
:cpp:expr:`lv_grad_horizontal_init(&grad_dsc)` or
:cpp:expr:`lv_grad_vertical_init(&grad_dsc)` to get a horizontal or vertical gradient
descriptor.
.. lv_example:: grad/lv_example_grad_1
:language: c
Linear Gradients
----------------
The liniear (or skew) gradinet are similar to horizontal or vertical gradient but the
angle of the gradient can be controlled.
The linear gradient will be rendered along a line defined by 2 points.
After initializing the stops with :cpp:func:`lv_grad_init_stops` call
:cpp:expr:`lv_grad_linear_init(&grad_dsc, from_x, from_y, to_x, to_y, LV_GRAD_EXTEND_...)`
with your point values and extend pattern strategy to get a linear gradient descriptor.
.. lv_example:: grad/lv_example_grad_2
:language: c
Radial Gradients
----------------
The radial gradient is described by two circles: an outer circle and an inner circle
(also called the focal point). The gradient will be calculated between the focal
point's circle and the edge of the outer circle.
If the center of the focal point and the center of the main circle are the same, the
gradient will spread evenly in all directions. If the center points are not the
same, the gradient will have an egg shape.
The focal point's circle should be inside the main circle.
After initializing the stops with :cpp:func:`lv_grad_init_stops`, the outer
circle can be set by:
:cpp:expr:`lv_grad_radial_init(&grad_dsc, center_x, center_y, edge_x, edge_y, LV_GRAD_EXTEND_...)`
For both the center and edge coordinates, ``px`` or ``lv_pct()`` values can be used.
The inner circle (focal point) can be set with:
:cpp:expr:`lv_grad_radial_set_focal(&grad_dsc, center_x, center_y, radius)`
.. lv_example:: grad/lv_example_grad_3
:language: c
Conic Gradients
---------------
The conic gradient is defined between the angles of a circle, and colors are mapped
to each angle.
After initializing the stops with :cpp:func:`lv_grad_init_stops`, the conic gradient
can be set up with:
:cpp:expr:`lv_grad_conical_init(&grad, center_x, center_y, angle_start, angle_end, LV_GRAD_EXTEND_...)`
For both the center and edge coordinates, ``px`` or ``lv_pct()`` values can be used.
The zero angle is on the right-hand side, and 90 degrees is at the bottom.
.. lv_example:: grad/lv_example_grad_4
:language: c
Border Draw Descriptor
**********************
The :cpp:type:`lv_draw_border_dsc_t` border descriptor has radius, opacity,
width, color, and side fields. If the opacity or width is 0, no Draw Task will
be created.
``side`` can contain ORed values of :cpp:type:`lv_border_side_t`, such as
:cpp:enumerator:`LV_BORDER_SIDE_BOTTOM`. :cpp:enumerator:`LV_BORDER_SIDE_ALL`
applies to all sides, while :cpp:enumerator:`LV_BORDER_SIDE_INTERNAL` is used by
higher layers (e.g. a table Widget) to calculate border sides. However, the drawing
routine receives only simpler values.
The following functions are used for border drawing:
- :cpp:expr:`lv_draw_border_dsc_init(&dsc)` initializes a border Draw Task.
- :cpp:expr:`lv_draw_sw_border(layer, &dsc, area)` creates a Draw Task to draw a border
inward from its area.
- :cpp:expr:`lv_draw_task_get_border_dsc(draw_task)` retrieves the border descriptor
from a Draw Task.
.. lv_example:: styles/lv_example_style_3
:language: c
Outlines
********
The outline is similar to the border but is drawn outside the object's draw area.
In practice, there is no dedicated outline descriptor like
``lv_draw_outline_dsc_t``, because from the rendering perspective, the
outline is simply another border rendered outside the object's bounds.
The outline is used only in :cpp:type:`lv_draw_rect_dsc_t` for convenience. The two
differences compared to borders in :cpp:type:`lv_draw_rect_dsc_t` are:
- There is an ``outline_pad`` property to specify the gap between the target area and
the inner side of the outline. It can be negative. For example, if ``outline_pad =
-width``, the outline will resemble a border.
- There is no ``border_side`` property for the outline. It's always rendered as a
full rectangle.
.. lv_example:: styles/lv_example_style_4
:language: c
Box Shadow Draw Descriptor
**************************
The :cpp:type:`lv_draw_box_shadow_dsc_t` box shadow descriptor describes a **rounded
rectangle-shaped shadow**. It cannot generate shadows for arbitrary shapes, text, or
images. It includes the following fields:
:radius: Radius, :cpp:expr:`LV_RADIUS_CIRCLE`.
:color: Shadow color.
:width: Shadow width (blur radius).
:spread: Expands the rectangle in all directions; can be negative.
:ofs_x: Horizontal offset.
:ofs_y: Vertical offset.
:opa: Opacity (0--255 range). Values like ``LV_OPA_TRANSP``, ``LV_OPA_10``,
etc., can also be used.
:bg_cover: Set to 1 if the background will cover the shadow (a hint for the
renderer to skip masking).
Note: Rendering large shadows may be slow or memory-intensive.
The following functions are used for box shadow drawing:
- :cpp:expr:`lv_draw_box_shadow_dsc_init(&dsc)` initializes a box shadow Draw Task.
- :cpp:expr:`lv_draw_sw_box_shadow(layer, &dsc, area)` creates a Draw Task for a rectangle's
shadow. The shadow's size and position depend on the width, spread, and offset.
- :cpp:expr:`lv_draw_task_get_box_shadow_dsc(draw_task)` retrieves the box shadow
descriptor from a Draw Task.
.. lv_example:: styles/lv_example_style_5
:language: c
.. |deg| unicode:: U+000B0 .. DEGREE SIGN
Image Draw Descriptor
*********************
The :cpp:type:`lv_draw_image_dsc_t` image descriptor defines the parameters for
image drawing. It is a complex descriptor with the following options:
:src: The image source, either a pointer to `lv_image_dsc_t` or a file path.
:opa: Opacity in the 0--255 range. Options like
``LV_OPA_TRANSP``, ``LV_OPA_10``, etc., can also be used.
:clip_radius: Clips the corners of the image with this radius. Use
`LV_RADIUS_CIRCLE` for the maximum radius.
:rotation: Image rotation in 0.1-degree units (e.g., 234 means 23.4\ |deg|\ ).
:scale_x: Horizontal scaling (zoom) of the image.
256 (LV_SCALE_NONE) means no zoom, 512 doubles the size, and 128 halves it.
:scale_y: Same as ``scale_x`` but for vertical scaling.
:skew_x: Horizontal skew (parallelogram-like transformation) in 0.1-degree
units (e.g., 456 means 45.6\ |deg|\ ).
:skew_y: Vertical skew, similar to ``skew_x``.
:pivot: The pivot point for transformations (scaling and rotation).
(0,0) is the top-left corner of the image and can be set outside the image.
:bitmap_mask_src: Pointer to an A8 or L8 image descriptor used to mask the
image. The mask is always center-aligned.
:recolor: Mixes this color with the image. For :cpp:enumerator:`LV_COLOR_FORMAT_A8`,
this will be the visible pixels' color.
:recolor_opa: Intensity of recoloring (0 means no recoloring, 255 means full cover).
:blend_mode: Defines how to blend image pixels with the background.
See :cpp:type:`lv_blend_mode_t` for more details.
:antialias: Set to 1 to enable anti-aliasing for transformations.
:tile: Tiles the image (repeats it both horizontally and vertically) if the
image is smaller than the `image_area` field in `lv_draw_image_dsc_t`.
:image_area: Indicates the original, non-clipped area where the image
is drawn. This is essential for:
1. Layer rendering, where only part of a layer may be rendered and
``clip_radius`` needs the original image dimensions.
2. Tiling, where the draw area is larger than the image.
:sup: Internal field to store information about the palette or color of A8 images.
Functions for image drawing:
- :cpp:expr:`lv_draw_image_dsc_init(&dsc)` initializes an image draw descriptor.
- :cpp:expr:`lv_draw_image(layer, &dsc, area)` creates a task to draw an image in a given area.
- :cpp:expr:`lv_draw_task_get_image_dsc(draw_task)` retrieves the image descriptor from a task.
.. lv_example:: widgets/canvas/lv_example_canvas_6
:language: c
.. lv_example:: styles/lv_example_style_6
:language: c
Layers - Special Images
-----------------------
Layers are treated as images, so an :cpp:type:`lv_draw_image_dsc_t` can describe
how layers are blended into their parent layers. All image features apply to
layers as well.
``lv_draw_layer(layer, &dsc, area)`` initializes the blending of a layer back to
its parent layer. Additionally, image-drawing-related functions can be used for
layers.
For more details, see :ref:`layers`.
Label Draw Descriptor
*********************
The :cpp:type:`lv_draw_label_dsc_t` label descriptor provides extensive options
for controlling text rendering:
:text: The text to render.
:font: Font to use, with support for fallback fonts.
:color: Text color.
:opa: Text opacity.
:line_space: Additional space between lines.
:letter_space: Additional space between characters.
:ofs_x: Horizontal text offset.
:ofs_y: Vertical text offset.
:sel_start: Index of the first character for selection (not byte index).
``LV_DRAW_LABEL_NO_TXT_SEL`` means no selection.
:sel_end: Index of the last character for selection.
:sel_color: Color of selected characters.
:sel_bg_color: Background color for selected characters.
:align: Text alignment. See :cpp:type:`lv_text_align_t`.
:bidi_dir: Base direction for right-to-left text rendering (e.g., Arabic).
See :cpp:type:`lv_base_dir_t`.
:decor: Text decoration, e.g., underline. See :cpp:type:`lv_text_decor_t`.
:flag: Flags for text rendering. See :cpp:type:`lv_text_flag_t`.
:text_length: Number of characters to render (0 means render until `\0`).
:text_local: Set to 1 to allocate a buffer and copy the text.
:text_static: Indicates ``text`` is constant and its pointer can be cached.
:hint: Pointer to externally stored data to speed up rendering.
See :cpp:type:`lv_draw_label_hint_t`.
Functions for text drawing:
- :cpp:expr:`lv_draw_label_dsc_init(&dsc)` initializes a label draw descriptor.
- :cpp:expr:`lv_draw_label(layer, &dsc, area)` creates a task to render text in an area.
- :cpp:expr:`lv_draw_character(layer, &dsc, point, unicode_letter)` creates a task to
draw a character at a specific point.
- :cpp:expr:`lv_draw_task_get_label_dsc(draw_task)` retrieves the label descriptor from a task.
For character-specific drawing in draw units, use
:cpp:expr:`lv_draw_label_iterate_characters(draw_unit, draw_dsc, area, callback)`.
This iterates through all characters, calculates their positions, and calls the
callback for rendering each character. For callback details, see
:cpp:type:`lv_draw_glyph_cb_t`.
.. lv_example:: widgets/canvas/lv_example_canvas_4
:language: c
.. lv_example:: styles/lv_example_style_8
:language: c
Arc Draw Descriptor
*******************
The :cpp:type:`lv_draw_arc_dsc_t` arc descriptor defines arc rendering with
these fields:
:color: Arc color.
:img_src: Image source for the arc, or `NULL` if unused.
:width: Arc thickness.
:start_angle: Starting angle in degrees (e.g., 0° is 3 o'clock, 90° is 6 o'clock).
:end_angle: Ending angle.
:center: Arc center point.
:radius: Arc radius.
:opa: Arc opacity (0--255).
:rounded: Rounds the arc ends.
Functions for arc drawing:
- :cpp:expr:`lv_draw_arc_dsc_init(&dsc)` initializes an arc descriptor.
- :cpp:expr:`lv_draw_arc(layer, &dsc)` creates a task to render an arc.
- :cpp:expr:`lv_draw_task_get_arc_dsc(draw_task)` retrieves arc descriptor from task.
.. lv_example:: widgets/canvas/lv_example_canvas_5
:language: c
.. lv_example:: styles/lv_example_style_7
:language: c
Line Draw Descriptor
********************
The :cpp:type:`lv_draw_line_dsc_t` line descriptor defines line rendering with
these fields:
:p1: First point of line (supports floating-point coordinates).
:p2: Second point of line (supports floating-point coordinates).
:color: Line color.
:width: Line thickness.
:opa: Line opacity (0--255).
:dash_width: Length of dashes (0 means no dashes).
:dash_gap: Length of gaps between dashes (0 means no dashes).
:round_start: Rounds the line start.
:round_end: Rounds the line end.
:raw_end: Set to 1 to skip end calculations if they are unnecessary.
Functions for line drawing:
- :cpp:expr:`lv_draw_line_dsc_init(&dsc)` initializes a line descriptor.
- :cpp:expr:`lv_draw_line(layer, &dsc)` creates a task to draw a line.
- :cpp:expr:`lv_draw_task_get_line_dsc(draw_task)` retrieves line descriptor.
.. lv_example:: widgets/canvas/lv_example_canvas_7
:language: c
.. lv_example:: styles/lv_example_style_9
:language: c
Triangle Draw Descriptor
************************
Triangles are defined by :cpp:type:`lv_draw_triangle_dsc_t`, which includes:
:p[3]: 3 points for the triangle's vertices.
:color: Triangle color.
:opa: Triangle opacity.
:grad: Gradient options. If ``grad.dir`` is not ``LV_GRAD_DIR_NONE``, the
``color`` field is ignored. The ``opa`` field adjusts overall opacity.
Functions for triangle drawing:
- :cpp:expr:`lv_draw_triangle_dsc_init(&dsc)` initializes a triangle descriptor.
- :cpp:expr:`lv_draw_triangle(layer, &dsc)` creates a task to draw a triangle.
- :cpp:expr:`lv_draw_task_get_triangle_dsc(draw_task)` retrieves triangle descriptor.
.. lv_example:: widgets/canvas/lv_example_canvas_9
:language: c
Vector Draw Descriptor
**********************
TODO
Masking Operation
*****************
There are several options to mask parts of a layer, Widget, or drawing:
1. **Radius of Rectangles**:
Set the `radius` style property or the ``radius`` in the draw descriptors. This
creates rounded rectangles, borders, outlines, etc.. However, the content of
subsequent renderings will not be masked out in the corners.
2. **Clip Radius of Images**:
Similar to rectangles, images can also be rendered with a ``radius``. Since
layer drawing and image drawing are handled the same way, this works for
layers as well.
You can draw various content on a layer and then render the layer with a
``clip_radius``, masking out all the content on the corners.
3. **Rectangle Mask Draw Task**:
A special Draw Task can mask out a rectangle from a layer by setting the alpha
channel of certain pixels to 0. To achieve this:
- Create an :cpp:type:`lv_draw_mask_rect_dsc_t` descriptor.
- Set ``area``, ``radius``, and ``keep_outside`` parameters. If
``keep_outside`` is set to 1, areas outside of ``area`` remain unchanged.
Otherwise, they are cleared.
- Call :cpp:expr:`lv_draw_mask_rect(layer, &dsc)`.
Note: The layer must have a color format with an alpha channel, typically
:cpp:expr:`LV_COLOR_FORMAT_ARGB8888`.
In most cases, the *"Clip Radius of Images"* method is better because it
blends the layer with a radius mask on the fly, avoiding a dedicated masking
step. However, the *"Rectangle Mask Draw Task"* is useful when multiple areas
need clearing or when the area to be masked differs from the layer area.
4. **Clip Corner Style Property**:
Enabling ``..._style_clip_corner`` in a local or global style allows LVGL to
create a layer for the top and bottom corner areas of a Widget. It renders the
children there and blends it by setting ``clip_radius`` to the layer.
5. **Bitmap Masking for Images**:
Using ``..._style_bitmap_mask`` or ``bitmap_mask`` in
:cpp:type:`lv_draw_image_dsc_t` allows setting an A8 or L8 image as a mask
for an image/layer during blending.
- Limitation: The mask always aligns to the center, and only one bitmap mask can
be used for an image/layer.
- When ``..._style_bitmap_mask`` is used, LVGL automatically creates a layer,
renders the Widgets there, and applies the bitmap mask during blending.
- Alternatively, the ``bitmap_mask`` property in the draw descriptor can be
used directly for image drawing.
By using the Canvas Widget with an :cpp:enumerator:`LV_COLOR_FORMAT_L8` buffer,
bitmap masks can be rendered dynamically.
.. lv_example:: widgets/canvas/lv_example_label_4
:language: c
.. lv_example:: widgets/canvas/lv_example_roller_3
:language: c
API
***
.. API equals:
lv_draw_arc
lv_draw_dsc_base_t
lv_draw_image
lv_draw_label
lv_draw_label_dsc_init
lv_draw_line
lv_draw_mask_rect
lv_draw_rect_dsc_t
lv_draw_task_get_label_dsc
lv_draw_triangle
LV_EVENT_DRAW_TASK_ADDED
lv_grad_dsc_t
lv_grad_horizontal_init

View File

@@ -0,0 +1,199 @@
.. _draw_layers:
===========
Draw Layers
===========
Not to be confused with a :ref:`Display's main 4 layers <display_screen_layers>`, a
:dfn:`Draw Layer` is a buffer created during rendering, necessitated by certain style
properties, so different sets of pixels are correctly combined. Factors requiring
such layers are:
- partial opacity
- bit-mask being applied
- blend mode
- clipped corners (a bit-mask application)
- transformations
- scale
- skew
- rotation
Later that layer will be merged to the screen or its parent layer at the correct
point in the rendering sequence.
Layer Types
***********
Simple Layer
------------
The following style properties trigger the creation of a "Simple Layer":
- ``opa_layered``
- ``bitmap_mask_src``
- ``blend_mode``
In this case the Widget will be sliced into ``LV_DRAW_SW_LAYER_SIMPLE_BUF_SIZE``
sized chunks.
If there is no memory for a new chunk, LVGL will try allocating the layer after
another chunk is rendered and freed.
Transform Layer
---------------
The following style properties trigger the creation of a "Transform Layer":
- ``transform_scale_x``
- ``transform_scale_y``
- ``transform_skew_x``
- ``transform_skew_y``
- ``transform_rotate``
Due to the nature of transformations, the Widget being transformed (and its children)
must be rendered first, followed by the transformation step. This necessitates a
temporary drawing area (layer), often larger than the Widget proper, to provide an
area of adequate size for the transformation. LVGL tries to render as small area of
the widget as possible, but due to the nature of transformations no slicing is
possible in this case.
Clip Corner
-----------
The ``clip_corner`` style property also causes LVGL to create a 2 layers with radius
height for the top and bottom parts of the Widget.
Getting the Current Layer
*************************
The first parameter of the ``lv_draw_rect/label/etc`` functions is a layer.
In most cases a layer is not created, but an existing layer is used to draw there.
The draw API can be used in these cases and the current layer can be used differently
in each case:
1. **In draw events**:
In ``LV_EVENT_DRAW_MAIN/POST_BEGIN/...`` events the Widget is being rendered to a
layer of the display or another temporary layer created earlier during rendering.
The current target layer can be retrieved using :cpp:expr:`lv_event_get_layer(e)`.
It also possible to create new layers in these events, but the previous layer is
also required since it will be the parent layer in :cpp:func:`lv_draw_layer`.
2. **Modifying the created Draw Tasks**:
In :cpp:enumerator:`LV_EVENT_DRAW_TASK_ADDED` the draw tasks created by
``lv_draw_rect/label/etc`` can be modified. It's not required to know the current
layer to modify a draw task. However, if something new also needs to be drawn with
``lv_draw_rect/label/etc`` the current layer is also required.
The current layer can be read from the ``base`` draw descriptor. For example:
.. code-block:: c
/* In LV_EVENT_DRAW_TASK_ADDED */
lv_draw_task_t * t = lv_event_get_draw_task(e);
lv_draw_base_dsc_t * draw_dsc = lv_draw_task_get_draw_dsc(t);
lv_layer_t * current_layer = draw_dsc.layer;
3. **Draw to the Canvas Widget**:
The canvas itself doesn't store a layer, but one can be easily created and used
like this:
.. code-block:: c
/* Initialize a layer */
lv_layer_t layer;
lv_canvas_init_layer(canvas, &layer);
/* Draw something here */
/* Wait until the rendering is ready */
lv_canvas_finish_layer(canvas, &layer);
Creating a New Layer
********************
To create a new layer, use :cpp:func:`lv_draw_layer_create`:
.. code-block:: c
lv_area_t layer_area = {10, 10, 80, 50}; /* Area of the new layer */
lv_layer_t * new_layer = lv_draw_layer_create(parent_layer, LV_COLOR_FORMAT_RGB565, &layer_area);
Once the layer is created, draw tasks can be added to it
by using the :ref:`Draw API <draw_api>` and :ref:`Draw descriptors <draw_descriptors>`.
In most cases this means calling the ``lv_draw_rect/label/etc`` functions.
Finally, the layer must be rendered to its parent layer. Since a layer behaves
similarly to an image, it can be rendered the same way as images:
.. code-block:: c
lv_draw_image_dsc_t image_draw_dsc;
lv_draw_image_dsc_init(&image_draw_dsc);
image_draw_dsc.src = new_layer; /* Source image is the new layer. */
/* Draw new layer to parent layer. */
lv_draw_layer(parent_layer, &image_draw_dsc, &layer_area);
Memory Considerations
*********************
Layer Buffers
-------------
The buffer for a layer (where rendering occurs) is not allocated at creation.
Instead, it is allocated by :ref:`Draw Units` when the first :ref:`Draw Task <draw
tasks>` is dispatched.
Layer buffers can be large, so ensure there is sufficient heap memory or increase
:c:macro:`LV_MEM_SIZE` in ``lv_conf.h``.
Layer Type Memory Requirements
------------------------------
To save memory, LVGL can render certain types of layers in smaller chunks:
1. **Simple Layers**:
Simple layers can be rendered in chunks. For example, with
``opa_layered = 140``, only 10 lines of the layer can be rendered at a time,
then the next 10 lines, and so on.
This avoids allocating a large buffer for the entire layer. The buffer size for a
chunk is set using :c:macro:`LV_DRAW_LAYER_SIMPLE_BUF_SIZE` in ``lv_conf.h``.
2. **Transform Layers**:
Transform Widgets cannot be rendered in chunks because transformations
often affect pixels outside the given area. For such layers, LVGL allocates
a buffer large enough to render the entire transformed area without limits.
Memory Limit for Layers
-----------------------
The total memory available for layers at once is controlled by
:c:macro:`LV_DRAW_LAYER_MAX_MEMORY` in ``lv_conf.h``. If set to ``0``, there is no
limit.
API
***
.. API equals:
lv_draw_layer_create
LV_EVENT_DRAW_TASK_ADDED
lv_event_get_layer

View File

@@ -0,0 +1,212 @@
.. _draw_pipeline:
=============
Draw Pipeline
=============
What is Drawing?
****************
Drawing (also known as :dfn:`rendering`) is writing pixel colors into a buffer where
they will be delivered to a display panel as pixels. Sometimes this is done by
copying colors from places like background- and foreground-color properties. Other
times it involves computing those colors before they are written (e.g. combining them
with other colors when an object higher on the Z axis has partial opacity).
The following sections cover the LVGL drawing logic and how to use it and optionally
tune it to fit your particular project (e.g. if you have a GPU or other resources
that you would like to get involved).
Draw-Pipeline Overview
**********************
On modern computing hardware meant to be used with larger display panels, there are
sometimes options for different ways drawing can be accomplished. For example, some
MCUs come with hardware that is very good (and fast) at certain types of drawing
tasks. Alternatively, you might have access to a drawing library that performs
certain types of drawing tasks with great efficiency. To make it possible to utilize
such facilities in the most efficient fashion, LVGL v9 and onwards implements a
:dfn:`Drawing Pipeline`, like an assembly line, where decisions are made as to which
drawing tasks (:ref:`Draw Tasks`) are given to which "logic entities"
(:ref:`Draw Units`) in order to be carried out.
This Pipeline is designed so that it is both flexible and extendable. You can use it
to perform custom rendering with a GPU, or replace the parts of the built-in software
rendering logic to any extent desired.
Using events, it's also possible to modify :ref:`draw tasks` or insert new ones as
LVGL renders Widgets.
The following sections describe the basic terminology and concepts of rendering.
.. _draw tasks:
Draw Tasks
**********
A "Draw Task" (:cpp:type:`lv_draw_task_t`) is a package of information that is
created at the beginning of the Drawing Pipeline when a request to draw is made.
Functions such as :cpp:expr:`lv_draw_rect()` and :cpp:expr:`lv_draw_label()` create
one or more Draw Tasks and pass them down the Drawing Pipeline. Each Draw Task
carries all the information required to:
- compute which :ref:`Draw Unit <draw units>` should receive this task, and
- give the Draw Unit all the information required to accomplish the drawing task.
A Draw Task carries the following information:
:type: defines the drawing algorithm involved (e.g. line, fill,
border, image, label, arc, triangle, etc.)
:area: defines the rectangle in which drawing will occur
:transformation matrix: if :c:macro:`LV_DRAW_TRANSFORM_USE_MATRIX` is configured to '1'
:state: waiting, queued, in progress, completed
:drawing descriptor: carries details of the drawing to be performed
:preferred Draw Unit ID: identifier of the Draw Unit that should carry out this task
:preference score: value describing the speed of the specified Draw Unit relative
to software rendering (more on this below)
:next: a link to the next Draw Task in the list.
Draw Tasks are collected in a list and periodically dispatched to Draw Units.
.. _draw units:
Draw Units
**********
A "Draw Unit" (based on :cpp:type:`lv_draw_unit_t`) is any "logic entity" that can
generate the output required by a :ref:`Draw Task <draw tasks>`. This can be a CPU
core, a GPU, a custom rendering library for specific Draw Tasks, or any entity
capable of performing rendering.
For a reference implementation of a draw unit, see
`lv_draw_sw.c <https://github.com/lvgl/lvgl/blob/master/src/draw/sw/lv_draw_sw.c>`__.
During LVGL's initialization (:cpp:func:`lv_init`), a list of Draw Units is created.
If :c:macro:`LV_USE_DRAW_SW` is set to ``1`` in ``lv_conf.h`` (it is by default), the
Software Drawing Unit enters itself at the head of that list. If your platform has
other drawing units available, if they are configured to be used in ``lv_conf.h``,
they are added to this list during LVGL's initialization. If you are adding your own
Draw Unit(s), you add each available drawing unit to that list by calling
:cpp:expr:`lv_draw_create_unit(sizeof(your_draw_unit_t))`. With each call to that
function, the newly-created draw unit is added to the head of that list, pushing
already-existing draw units further back in the list, pushing the Draw Units created
earlier farther back in the list. The order of this list (and thus the order in which
:ref:`Draw Task Evaluation` is performed) is governed by the order in which each Draw
Unit is created.
Building this list (and initializing the Draw Units) is normally handled automatically
by configuring the available Draw Units in ``lv_conf.h``, such as setting
:c:macro:`LV_USE_DRAW_OPENGLES` or
:c:macro:`LV_USE_PXP` or
:c:macro:`LV_USE_DRAW_SDL` or
:c:macro:`LV_USE_DRAW_VG_LITE`
to ``1``. However, if you are introducing your own Draw Unit(s), you will need to
create and initialize it (after :cpp:func:`lv_init`) as above. This will include
several things, but setting its ``evaluate_cb`` and ``dispatch_cb`` callbacks
(mentioned later) are two of them.
For an example of how draw-unit cration and initialization is done, see
:cpp:func:`lv_draw_sw_init` in lv_draw_sw.c_ or the other draw units whose ``init``
functions are optionally called in :cpp:func:`lv_init`.
Thread Priority
---------------
All draw units operate with a configurable thread priority which can be set using the
:c:macro:`LV_DRAW_THREAD_PRIO` configuration option in ``lv_conf.h``. This allows you
to fine-tune the priority level across all drawing units, which is especially useful for
systems with limited priority levels.
By default, draw units use :c:macro:`LV_THREAD_PRIO_HIGH` as their thread priority.
This consistent approach ensures that all drawing units (software rendering, hardware
accelerators like STM32 DMA2D, NXP VGLite, etc.) use the same priority level unless
explicitly configured otherwise.
.. _lv_draw_sw.c: https://github.com/lvgl/lvgl/blob/master/src/draw/sw/lv_draw_sw.c
.. _draw task evaluation:
Draw Task Evaluation
********************
When each :ref:`Draw Task <draw tasks>` is created, each existing Draw Unit is
"consulted" as to its "appropriateness" for the task. It does this through
an "evaluation callback" function pointer (a.k.a. ``evaluate_cb``), which each Draw
Unit sets (for itself) during its initialization. Normally, that evaluation:
- optionally examines the existing "preference score" for the task mentioned above,
- if it can accomplish that type of task (e.g. line drawing) faster than other
Draw Units that have already reported, it writes its own "preference score" and
"preferred Draw Unit ID" to the respective fields in the task.
In this way, by the time the evaluation sequence is complete, the task will contain
the score and the ID of the Drawing Unit that will be used to perform that task when
it is :ref:`dispatched <draw task dispatching>`.
This logic, of course, can be overridden or redefined, depending on system design.
As a side effect, this also ensures that the same Draw Unit will be selected
consistently, depending on the type (and nature) of the drawing task, avoiding any
possible screen jitter in case more than one Draw Unit is capable of performing a
given task type.
The sequence of the Draw Unit list (with the Software Draw Unit at the end) also
ensures that the Software Draw Unit is the "buck-stops-here" Draw Unit: if no other
Draw Unit reported it was better at a given drawing task, then the Software Draw Unit
will handle it.
.. _draw task dispatching:
Dispatching
***********
While collecting Draw Tasks LVGL frequently dispatches the collected Draw Tasks to
their assigned Draw Units. This is handled via the ``dispatch_cb`` of the Draw Units.
If a Draw Unit is busy with another Draw Task, it just returns. However, if it is
available it can take a Draw Task.
:cpp:expr:`lv_draw_get_next_available_task(layer, previous_task, draw_unit_id)` is a
useful helper function which is used by the ``dispatch_cb`` to get the next Draw Task
it should act on. If it handled the task, it sets the Draw Task's ``state`` field to
:cpp:enumerator:`LV_DRAW_TASK_STATE_READY` (meaning "completed"). "Available" in
this context means that has been queued and assigned to a given Draw Unit and is
ready to be carried out. The ramifications of having multiple drawing threads are
taken into account for this.
Run-Time Object Hierarchy
*************************
All of the above have this relationship at run time:
- LVGL (global)
- list of :ref:`Draw Units`
- list of :ref:`Display(s) <display_overview>`
- Layer(s): Each :ref:`Display object <display_overview>` has its own list of :ref:`draw_layers`
- Draw Tasks: Each Layer has its own list of :ref:`Draw Tasks`
API
***
.. API equals:
lv_draw_create_unit
lv_draw_get_next_available_task
lv_draw_label
lv_draw_rect
lv_draw_sw_init
lv_draw_task_t
LV_DRAW_TRANSFORM_USE_MATRIX
lv_draw_unit_t
LV_USE_DRAW_OPENGLES

View File

@@ -0,0 +1,13 @@
.. _draw:
=======
Drawing
=======
.. toctree::
:maxdepth: 2
draw_pipeline
draw_api
draw_layers
draw_descriptors

View File

@@ -0,0 +1,556 @@
.. |check| unicode:: U+02713 .. CHECK MARK
.. |Aacute| unicode:: U+000C1 .. LATIN CAPITAL LETTER A WITH ACUTE
.. |eacute| unicode:: U+000E9 .. LATIN SMALL LETTER E WITH ACUTE
.. |otilde| unicode:: U+000F5 .. LATIN SMALL LETTER O WITH TILDE
.. |Utilde| unicode:: U+00168 .. LATIN CAPITAL LETTER U WITH TILDE
.. |uuml| unicode:: U+000FC .. LATIN SMALL LETTER U WITH DIAERESIS
.. |uml| unicode:: U+000A8 .. DIAERESIS
.. _font:
==============
Font (lv_font)
==============
In LVGL fonts are collections of bitmaps and other information required
to render images of individual letters (glyph). A font is stored in a
:cpp:type:`lv_font_t` variable and can be set in a style's *text_font* field.
For example:
.. code-block:: c
lv_style_set_text_font(&my_style, &lv_font_montserrat_28); /* Set a larger font */
Fonts have a **format** property. It describes how the glyph data is stored.
At this writing there are 12 possible values that this field can take, and those
values fall into 2 categories:
:Legacy simple: 1, 2, 4 or 8-bpp (aligned or unaligned) and image format, and
:Advanced: vector, SVG, and custom formats; for the latter, the user provides
the rendering logic.
For simple formats:
- the font is stored as an array of bitmaps, one bitmap per glyph;
- the value stored for each pixel determines the pixel's opacity, enabling edges
to be smoother --- higher bpp values result in smoother edges.
For advanced formats, the font information is stored in its respective format.
The **format** property also affects the amount of memory needed to store a
font. For example, ``format = LV_FONT_GLYPH_FORMAT_A4`` makes a font nearly four
times larger compared to ``format = LV_FONT_GLYPH_FORMAT_A1``.
Unicode Support
***************
LVGL supports **UTF-8** encoded Unicode characters. Your editor needs to
be configured to save your code/text as UTF-8 (usually this the default)
and be sure that :c:macro:`LV_TXT_ENC` is set to :c:macro:`LV_TXT_ENC_UTF8` in
``lv_conf.h``. (This is the default value.)
To test it try
.. code-block:: c
lv_obj_t * label1 = lv_label_create(lv_screen_active(), NULL);
lv_label_set_text(label1, LV_SYMBOL_OK);
If all works well, a '\ |check|\ ' character should be displayed.
Typesetting
***********
Although LVGL can decode and display any Unicode characters
(assuming the font supports them), LVGL cannot correctly render
all complex languages.
The standard Latin-based languages (e.g., English, Spanish, German)
and East Asian languages such as Chinese, Japanese, and Korean (CJK)
are relatively straightforward, as their characters are simply
written from left to right.
Languages like Arabic, Persian, and Hebrew, which use Right-to-Left
(RTL) or mixed writing directions, are also supported in LVGL.
Learn more :ref:`here <bidi>`.
For characters such as '|eacute|', '|uuml|', '|otilde|', '|Aacute|', and '|Utilde|',
it is recommended to use the single Unicode format (NFC) rather than decomposing them
into a base letter and diacritics (e.g. ``u`` + |uml|).
Complex languages where subsequent characters combine into a single glyph
and where the resulting glyph has no individual Unicode representation
(e.g., Devanagari), have limited support in LVGL.
Built-In Fonts
**************
There are several built-in fonts in different sizes, which can be
enabled in ``lv_conf.h`` with *LV_FONT_...* defines.
Normal Fonts
------------
The following fonts contain all ASCII characters, the degree symbol (U+00B0), the
bullet symbol (U+2022) and the built-in symbols (see below).
- :c:macro:`LV_FONT_MONTSERRAT_12`: 12 px font
- :c:macro:`LV_FONT_MONTSERRAT_14`: 14 px font
- :c:macro:`LV_FONT_MONTSERRAT_16`: 16 px font
- :c:macro:`LV_FONT_MONTSERRAT_18`: 18 px font
- :c:macro:`LV_FONT_MONTSERRAT_20`: 20 px font
- :c:macro:`LV_FONT_MONTSERRAT_22`: 22 px font
- :c:macro:`LV_FONT_MONTSERRAT_24`: 24 px font
- :c:macro:`LV_FONT_MONTSERRAT_26`: 26 px font
- :c:macro:`LV_FONT_MONTSERRAT_28`: 28 px font
- :c:macro:`LV_FONT_MONTSERRAT_30`: 30 px font
- :c:macro:`LV_FONT_MONTSERRAT_32`: 32 px font
- :c:macro:`LV_FONT_MONTSERRAT_34`: 34 px font
- :c:macro:`LV_FONT_MONTSERRAT_36`: 36 px font
- :c:macro:`LV_FONT_MONTSERRAT_38`: 38 px font
- :c:macro:`LV_FONT_MONTSERRAT_40`: 40 px font
- :c:macro:`LV_FONT_MONTSERRAT_42`: 42 px font
- :c:macro:`LV_FONT_MONTSERRAT_44`: 44 px font
- :c:macro:`LV_FONT_MONTSERRAT_46`: 46 px font
- :c:macro:`LV_FONT_MONTSERRAT_48`: 48 px font
Special fonts
-------------
- :c:macro:`LV_FONT_MONTSERRAT_28_COMPRESSED`: Same as normal 28 px font but stored as a :ref:`fonts_compressed` with 3 bpp
- :c:macro:`LV_FONT_DEJAVU_16_PERSIAN_HEBREW`: 16 px font with normal range + Hebrew, Arabic, Persian letters and all their forms
- :c:macro:`LV_FONT_SOURCE_HAN_SANS_SC_16_CJK`: 16 px font with normal range plus 1000 of the most common CJK radicals
- :c:macro:`LV_FONT_UNSCII_8`: 8 px pixel perfect font with only ASCII characters
- :c:macro:`LV_FONT_UNSCII_16`: 16 px pixel perfect font with only ASCII characters
The built-in fonts are **global variables** with names like
:cpp:var:`lv_font_montserrat_16` for a 16 px height font. To use them in a
style, just add a pointer to a font variable like this:
.. code-block:: c
lv_style_set_text_font(&my_style, &lv_font_montserrat_28);
The built-in fonts with ``bpp = 4`` contain the ASCII characters and use
the `Montserrat <https://fonts.google.com/specimen/Montserrat>`__ font.
In addition to the ASCII range, the following symbols are also added to
the built-in fonts from the `FontAwesome <https://fontawesome.com/>`__
font.
.. _fonts_symbols:
.. image:: /_static/images/symbols.png
The symbols can be used singly as:
.. code-block:: c
lv_label_set_text(my_label, LV_SYMBOL_OK);
Or together with strings (compile time string concatenation):
.. code-block:: c
lv_label_set_text(my_label, LV_SYMBOL_OK "Apply");
Or more symbols together:
.. code-block:: c
lv_label_set_text(my_label, LV_SYMBOL_OK LV_SYMBOL_WIFI LV_SYMBOL_PLAY);
Special Features
****************
.. _bidi:
Bidirectional support
---------------------
Most languages use a Left-to-Right (LTR for short) writing direction,
however some languages (such as Hebrew, Persian or Arabic) use
Right-to-Left (RTL for short) direction.
LVGL not only supports RTL text but supports mixed (a.k.a.
bidirectional, BiDi) text rendering as well. Some examples:
.. image:: /_static/images/bidi.png
BiDi support is enabled by setting :c:macro:`LV_USE_BIDI` to a non-zero value in ``lv_conf.h``.
All text has a base direction (LTR or RTL) which determines some
rendering rules and the default alignment of the text (left or right).
However, in LVGL, the base direction is not only applied to labels. It's
a general property which can be set for every Widget. If not set then it
will be inherited from the parent. This means it's enough to set the
base direction of a screen and its child Widgets will inherit it.
The default base direction for screens can be set by
:c:macro:`LV_BIDI_BASE_DIR_DEF` in ``lv_conf.h`` and other Widgets inherit the
base direction from their parent.
To set a Widget's base direction use :cpp:expr:`lv_obj_set_style_base_dir(widget, base_dir, selector)`.
The possible base directions are:
- :cpp:enumerator:`LV_BASE_DIR_LTR`: Left to Right base direction
- :cpp:enumerator:`LV_BASE_DIR_RTL`: Right to Left base direction
- :cpp:enumerator:`LV_BASE_DIR_AUTO`: Auto detect base direction
This list summarizes the effect of RTL base direction on Widgets:
- Create Widgets by default on the right
- ``lv_tabview``: Displays tabs from right to left
- ``lv_checkbox``: Shows the box on the right
- ``lv_buttonmatrix``: Orders buttons from right to left
- ``lv_list``: Shows icons on the right
- ``lv_dropdown``: Aligns options to the right
- The text strings in ``lv_table``, ``lv_buttonmatrix``, ``lv_keyboard``, ``lv_tabview``,
``lv_dropdown``, ``lv_roller`` are "BiDi processed" to be displayed correctly
Arabic and Persian support
--------------------------
There are some special rules to display Arabic and Persian characters:
the *form* of a character depends on its position in the text. A
different form of the same letter needs to be used when it is isolated,
at start, middle or end positions. Besides these, some conjunction rules
should also be taken into account.
LVGL supports these rules if :c:macro:`LV_USE_ARABIC_PERSIAN_CHARS` is enabled
in ``lv_conf.h``.
However, there are some limitations:
- Only displaying text is supported (e.g. on labels), i.e. text inputs (e.g. Text
Area) do not support this feature.
- Static text (i.e. const) is not processed. E.g. text set by :cpp:func:`lv_label_set_text`
will be "Arabic processed" but :cpp:func:`lv_label_set_text_static` will not.
- Text get functions (e.g. :cpp:func:`lv_label_get_text`) will return the processed text.
.. _fonts_compressed:
Compressed fonts
----------------
The built-in font engine supports compressed bitmaps.
Compressed fonts can be generated by
- ticking the ``Compressed`` check box in the online converter
- not passing the ``--no-compress`` flag to the offline converter (compression is applied by default)
Compression is more effective with larger fonts and higher bpp. However,
it's about 30% slower to render compressed fonts. Therefore, it is
recommended to compress only the largest fonts of a user interface,
because
- they need the most memory
- they can be compressed better
- and on the likelihood that they are used less frequently than the medium-sized
fonts, the performance cost will be smaller.
Compressed fonts also support ``bpp=3``.
Kerning
-------
Fonts may provide kerning information to adjust the spacing between specific
characters.
- The online converter generates kerning tables.
- The offline converter generates kerning tables unless ``--no-kerning`` is
specified.
- FreeType integration does not currently support kerning.
- The Tiny TTF font engine supports GPOS (Glyph Positioning) and Kern tables.
To configure kerning at runtime, use :cpp:func:`lv_font_set_kerning`.
.. _add_font:
Adding a New Font
*****************
There are several ways to add a new font to your project:
1. The simplest method is to use the `Online font converter <https://lvgl.io/tools/fontconverter>`__.
Just set the parameters, click the *Convert* button, copy the font to your project
and use it. **Be sure to carefully read the steps provided on that site
or you will get an error while converting.**
2. Use the `Offline font converter <https://github.com/lvgl/lv_font_conv>`__.
(Requires Node.js to be installed)
3. If you want to create something like the built-in
fonts (Montserrat font and symbols) but in a different size and/or
ranges, you can use the ``built_in_font_gen.py`` script in
``lvgl/scripts/built_in_font`` folder. (This requires Python and
https://github.com/lvgl/lv_font_conv/ to be installed.)
To declare a font in a file, use :cpp:expr:`LV_FONT_DECLARE(my_font_name)`.
To make fonts globally available (like the built-in fonts), add them to
:c:macro:`LV_FONT_CUSTOM_DECLARE` in ``lv_conf.h``.
Adding New Symbols
******************
The built-in symbols are created from the `FontAwesome <https://fontawesome.com/>`__ font.
1. Search for a symbol on https://fontawesome.com. For example the
`USB symbol <https://fontawesome.com/icons/usb?style=brands>`__. Copy its
Unicode ID which is ``0xf287``.
2. Open the `Online font converter <https://lvgl.io/tools/fontconverter>`__.
Add `FontAwesome.woff <https://lvgl.io/assets/others/FontAwesome5-Solid+Brands+Regular.woff>`__.
3. Set the parameters such as Name, Size, BPP. You'll use this name to
declare and use the font in your code.
4. Add the Unicode ID of the symbol to the range field. E.g.\ ``0xf287``
for the USB symbol. More symbols can be enumerated with ``,``.
5. Convert the font and copy the generated source code to your project.
Make sure to compile the ``.c`` file of your font.
6. Declare the font using ``extern lv_font_t my_font_name;`` or simply
use :cpp:expr:`LV_FONT_DECLARE(my_font_name)`.
**Using the symbol**
1. Convert the Unicode value to UTF8, for example on
`this site <http://www.ltg.ed.ac.uk/~richard/utf-8.cgi?input=f287&mode=hex>`__.
For ``0xf287`` the *Hex UTF-8 bytes* are ``EF 8A 87``.
2. Create a ``#define`` string from the UTF8 values: ``#define MY_USB_SYMBOL "\xEF\x8A\x87"``
3. Create a label and set the text. Eg. :cpp:expr:`lv_label_set_text(label, MY_USB_SYMBOL)`
:note: :cpp:expr:`lv_label_set_text(label, MY_USB_SYMBOL)` searches for this symbol
in the font defined in the style's ``text.font`` property. To use the symbol
you will need to set the style's text font to use the generated font, e.g.
:cpp:expr:`lv_style_set_text_font(&my_style, &my_font_name)` or
:cpp:expr:`lv_obj_set_style_text_font(label, &my_font_name, 0)`.
Loading a Font at Run-Time
**************************
:cpp:func:`lv_binfont_create` can be used to load a font from a file. The font needs
to have a special binary format. (Not TTF or WOFF). Use
`lv_font_conv <https://github.com/lvgl/lv_font_conv/>`__ with the
``--format bin`` option to generate an LVGL compatible font file.
:note: To load a font :ref:`LVGL's filesystem <file_system>`
needs to be enabled and a driver must be added.
Example
.. code-block:: c
lv_font_t *my_font = lv_binfont_create("X:/path/to/my_font.bin");
if(my_font == NULL) return;
/* Use the font */
/* Free the font if not required anymore */
lv_binfont_destroy(my_font);
Loading a Font from a Memory Buffer at Run-Time
***********************************************
:cpp:func:`lv_binfont_create_from_buffer` can be used to load a font from a memory buffer.
This function may be useful to load a font from an external file system, which is not
supported by LVGL. The font needs to be in the same format as if it were loaded from a file.
:note: To load a font from a buffer :ref:`LVGL's filesystem <file_system>`
needs to be enabled and the MEMFS driver must be added.
Example
.. code-block:: c
lv_font_t *my_font;
uint8_t *buf;
uint32_t bufsize;
/* Read font file into the buffer from the external file system */
...
/* Load font from the buffer */
my_font = lv_binfont_create_from_buffer((void *)buf, buf));
if(my_font == NULL) return;
/* Use the font */
/* Free the font if not required anymore */
lv_binfont_destroy(my_font);
Using a BDF Font
****************
Small displays with low resolution don't look pretty with automatically rendered fonts. A bitmap font provides
the solution, but it's necessary to convert the bitmap font (BDF) to a TTF.
Convert BDF to TTF
------------------
BDF are bitmap fonts where fonts are not described in outlines but in pixels. BDF files can be used but
they must be converted into the TTF format using ``mkttf``, which can be found
in this GitHub repository: https://github.com/Tblue/mkttf . This tool uses potrace to generate outlines from
the bitmap information. The bitmap itself will be embedded into the TTF as well. `lv_font_conv <https://github.com/lvgl/lv_font_conv/>`__ uses
the embedded bitmap but it also needs the outlines. One might think you can use a fake MS Bitmap
only sfnt (ttf) (TTF without outlines) created by fontforge, but this will not work.
Install imagemagick, python3, python3-fontforge and potrace
On Ubuntu Systems, just type
.. code:: bash
sudo apt install imagemagick python3-fontforge potrace
Clone mkttf
.. code:: bash
git clone https://github.com/Tblue/mkttf
Read the mkttf docs.
Former versions of imagemagick needs the imagemagick call in front of convert, identify and so on.
But newer versions don't. So you might want to change 2 lines in ``potrace-wrapper.sh`` ---
open ``potrace-wrapper.sh`` and remove imagemagick from line 55 and line 64:
line 55
.. code:: bash
wh=($(identify -format '%[width]pt %[height]pt' "${input?}"))
line 64
.. code:: bash
convert "${input?}" -sample '1000%' - \
It might be necessary to change the mkttf.py script.
line 1
.. code:: bash
#!/usr/bin/env python3
Example for a 12px font
-----------------------
.. code-block:: console
cd mkttf
./mkttf.py ./TerminusMedium-12-12.bdf
Importing bitmaps from 0 additional fonts...
Importing font `./TerminusMedium-12-12.bdf' into glyph background...
Processing glyphs...
Saving TTF file...
Saving SFD file...
Done!
The TTF ``TerminusMedium-001.000.ttf`` will be created from ``./TerminusMedium-12-12.bdf``.
To create a font for LVGL:
.. code:: bash
lv_font_conv --bpp 1 --size 12 --no-compress --font TerminusMedium-001.000.ttf --range 0x20-0x7e,0xa1-0xff --format lvgl -o terminus_1bpp_12px.c
:note: use 1-bpp because we don't use anti-aliasing. It doesn't look sharp on displays with a low resolution.
Adding a New Font Engine
************************
LVGL's font interface is designed to be very flexible but, even so, you
can add your own font engine in place of LVGL's internal one. For
example, you can use `FreeType <https://www.freetype.org/>`__ to
real-time render glyphs from TTF fonts or use an external flash to store
the font's bitmap and read them when the library needs them. FreeType can be used in LVGL as described in :ref:`Freetype <freetype>`.
To add a new font engine, a custom :cpp:type:`lv_font_t` variable needs to be created:
.. code-block:: c
/* Describe the properties of a font */
lv_font_t my_font;
my_font.get_glyph_dsc = my_get_glyph_dsc_cb; /* Set a callback to get info about glyphs */
my_font.get_glyph_bitmap = my_get_glyph_bitmap_cb; /* Set a callback to get bitmap of a glyph */
my_font.line_height = height; /* The real line height where any text fits */
my_font.base_line = base_line; /* Base line measured from the top of line_height */
my_font.dsc = something_required; /* Store any implementation specific data here */
my_font.user_data = user_data; /* Optionally some extra user data */
...
/* Get info about glyph of `unicode_letter` in `font` font.
* Store the result in `dsc_out`.
* The next letter (`unicode_letter_next`) might be used to calculate the width required by this glyph (kerning)
*/
bool my_get_glyph_dsc_cb(const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, uint32_t unicode_letter, uint32_t unicode_letter_next)
{
/* Your code here */
/* Store the result.
* For example ...
*/
dsc_out->adv_w = 12; /* Horizontal space required by the glyph in [px] */
dsc_out->box_h = 8; /* Height of the bitmap in [px] */
dsc_out->box_w = 6; /* Width of the bitmap in [px] */
dsc_out->ofs_x = 0; /* X offset of the bitmap in [pf] */
dsc_out->ofs_y = 3; /* Y offset of the bitmap measured from the as line */
dsc_out->format= LV_FONT_GLYPH_FORMAT_A2;
return true; /* true: glyph found; false: glyph was not found */
}
/* Get the bitmap of `unicode_letter` from `font`. */
const uint8_t * my_get_glyph_bitmap_cb(const lv_font_t * font, uint32_t unicode_letter)
{
/* Your code here */
/* The bitmap should be a continuous bitstream where
* each pixel is represented by `bpp` bits */
return bitmap; /* Or NULL if not found */
}
Using Font Fallback
*******************
If the font in use does not have a glyph needed in a text-rendering task, you can
specify a ``fallback`` font to be used in :cpp:type:`lv_font_t`.
``fallback`` can be chained, so it will try to solve until there is no ``fallback`` set.
.. code-block:: c
/* Roboto font doesn't have support for CJK glyphs */
lv_font_t *roboto = my_font_load_function();
/* Droid Sans Fallback has more glyphs but its typeface doesn't look good as Roboto */
lv_font_t *droid_sans_fallback = my_font_load_function();
/* So now we can display Roboto for supported characters while having wider characters set support */
roboto->fallback = droid_sans_fallback;
.. _fonts_api:
API
***

View File

@@ -0,0 +1,392 @@
.. _file_system:
=======================
File System (lv_fs_drv)
=======================
LVGL has a "File system" abstraction module that enables you to attach
any type of file system. A file system is identified by an assigned
identifier letter. For example, if an SD card is associated with the letter
``'S'``, a file can be reached using ``"S:/path/to/file.txt"``. See details
under :ref:`lv_fs_identifier_letters`.
.. note::
If you want to skip the drive-letter prefix in Unix-like paths, you can use the
:c:macro:`LV_FS_DEFAULT_DRIVER_LETTER` config parameter.
Ready-to-Use Drivers
********************
LVGL contains prepared drivers for the API of POSIX, standard C,
Windows, and `FATFS <http://elm-chan.org/fsw/ff/00index_e.html>`__.
Learn more :ref:`here <libs_filesystem>`.
.. _lv_fs_identifier_letters:
Identifier Letters
******************
As mentioned above, a file system is identified by an assigned identifier letter.
This identifier is merely a way for the LVGL File System abtraction logic to look up
the appropriate registered file-system driver for a given path.
**How it Works:**
You register a driver for your file system and assign it an identifier letter. This
letter must be unique among all registered file-system drivers, and in the range [A-Z]
or the character '/'. See :ref:`lv_fs_adding_a_driver` for how this is done.
Later, when using paths to files on your file system, you prefix the path with that
identifier character plus a colon (':').
.. note::
Do not confuse this with a Windows or DOS drive letter.
**Example:**
Let's use the letter 'Z' as the identifier character, and "path_to_file" as the path,
then the path strings you pass to ``lv_fs_...()`` functions would look like this::
"Z:path_to_file"
^ ^^^^^^^^^^^^
| |
| +-- This part gets passed to the OS-level file-system functions.
|
+-- This part LVGL strips from path string, and uses it to find the appropriate
driver (i.e. set of functions) that apply to that file system.
Note also that the path can be a relative path or a "rooted path" (beginning with
``/``), though rooted paths are recommended since the driver does not yet provide a
way to set the default directory.
**Examples for Unix-like file systems:**
- "Z:/etc/images/splash.png"
- "Z:/etc/images/left_button.png"
- "Z:/etc/images/right_button.png"
- "Z:/home/users/me/wip/proposal.txt"
**Examples for Windows/DOS-like file systems:**
- "Z:C:/Users/me/wip/proposal.txt"
- "Z:/Users/me/wip/proposal.txt" (if the default drive is known to be C:)
- "Z:C:/Users/Public/Documents/meeting_notes.txt"
- "Z:D:/to_print.docx"
Reminder: Note carefully that the prefixed "Z:" has nothing to do with the "C:" and
"D:" Windows/DOS drive letters in 3 of the above examples, which are part of the path.
"Z:" is used to look up the driver for that file system in the list of all file-system
drivers registered with LVGL.
.. _lv_fs_adding_a_driver:
Adding a Driver
***************
Registering a driver
--------------------
To add a driver, a :cpp:type:`lv_fs_drv_t` object needs to be initialized and
registered in a way similar to the code below. The :cpp:type:`lv_fs_drv_t` variable
needs to be static, global or dynamically allocated and not a local variable, since
its contents need to remain valid as long as the driver is in use.
.. code-block:: c
static lv_fs_drv_t drv; /* Needs to be static or global */
lv_fs_drv_init(&drv); /* Basic initialization */
drv.letter = 'S'; /* An uppercase letter to identify the drive */
drv.cache_size = my_cache_size; /* Cache size for reading in bytes. 0 to not cache. */
drv.ready_cb = my_ready_cb; /* Callback to tell if the drive is ready to use */
drv.open_cb = my_open_cb; /* Callback to open a file */
drv.close_cb = my_close_cb; /* Callback to close a file */
drv.read_cb = my_read_cb; /* Callback to read a file */
drv.write_cb = my_write_cb; /* Callback to write a file */
drv.seek_cb = my_seek_cb; /* Callback to seek in a file (Move cursor) */
drv.tell_cb = my_tell_cb; /* Callback to tell the cursor position */
drv.dir_open_cb = my_dir_open_cb; /* Callback to open directory to read its content */
drv.dir_read_cb = my_dir_read_cb; /* Callback to read a directory's content */
drv.dir_close_cb = my_dir_close_cb; /* Callback to close a directory */
drv.user_data = my_user_data; /* Any custom data if required */
lv_fs_drv_register(&drv); /* Finally register the drive */
Any of the callbacks can be ``NULL`` to indicate that operation is not
supported.
Implementing the callbacks
--------------------------
Open callback
~~~~~~~~~~~~~
The prototype of ``open_cb`` looks like this:
.. code-block:: c
void * (*open_cb)(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode);
``path`` is the path after the drive letter (e.g. "S:path/to/file.txt" -> "path/to/file.txt").
``mode`` can be :cpp:enumerator:`LV_FS_MODE_WR` or :cpp:enumerator:`LV_FS_MODE_RD` to open for writes or reads.
The return value is a pointer to a *file object* that describes the
opened file or ``NULL`` if there were any issues (e.g. the file wasn't
found). The returned file object will be passed to other file system
related callbacks. (See below.)
Other callbacks
---------------
The other callbacks are quite similar. For example ``write_cb`` looks
like this:
.. code-block:: c
lv_fs_res_t (*write_cb)(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw);
For ``file_p``, LVGL passes the return value of ``open_cb``, ``buf`` is
the data to write, ``btw`` is the number of "bytes to write", ``bw`` is the number of
"bytes written" (written to during the function call).
For a list of prototypes for these callbacks see
`lv_fs_template.c <https://github.com/lvgl/lvgl/blob/master/examples/porting/lv_port_fs_template.c>`__.
This file also provides a template for new file-system drivers you can use if the
one you need is not already provided.
Drivers that come with LVGL
---------------------------
As of this writing, the list of already-available file-system drivers can be enabled
by setting one or more of the following macros to a non-zero value in ``lv_conf.h``.
The drivers are as implied by the macro names.
If you use more than one, each associated identifier letter you use must be unique.
- :c:macro:`LV_USE_FS_FATFS`
- :c:macro:`LV_USE_FS_STDIO`
- :c:macro:`LV_USE_FS_POSIX`
- :c:macro:`LV_USE_FS_WIN32`
- :c:macro:`LV_USE_FS_MEMFS`
- :c:macro:`LV_USE_FS_LITTLEFS`
- :c:macro:`LV_USE_FS_ARDUINO_ESP_LITTLEFS`
- :c:macro:`LV_USE_FS_ARDUINO_SD`
Limiting Directory Access
*************************
If you are using one of the following file-system drivers:
- :c:macro:`LV_USE_FS_STDIO`
- :c:macro:`LV_USE_FS_POSIX`
- :c:macro:`LV_USE_FS_WIN32`
you will have a ``LV_FS_xxx_PATH`` macro available to you in ``lv_conf.h`` that you
can use to provide a path that gets dynamically prefixed to the ``path_to_file``
portion of of the path strings provided to ``lv_fs_...()`` functions when files and
directories are opened. This can be useful to limit directory access (e.g. when a
portion of a path can be typed by an end user), or simply to reduce the length of the
path strings provided to ``lv_fs_...()`` functions.
Do this by filling in the full path to the directory you wish his access to be
limited to in the applicable ``LV_FS_xxx_PATH`` macro in ``lv_conf.h``. Do not
prefix the path with the driver-identifier letter, and do append a directory
separator character at the end.
**Examples for Unix-like file systems:**
.. code-block:: c
#define LV_FS_WIN32_PATH "/home/users/me/"
**Examples for Windows/DOS-like file systems:**
.. code-block:: c
#define LV_FS_WIN32_PATH "C:/Users/me/"
Then in both cases, path strings passed to ``lv_fs_...()`` functions in the
application get reduced to:
- "Z:wip/proposal.txt"
Usage Example
*************
The example below shows how to read from a file:
.. code-block:: c
lv_fs_file_t f;
lv_fs_res_t res;
res = lv_fs_open(&f, "S:folder/file.txt", LV_FS_MODE_RD);
if(res != LV_FS_RES_OK) my_error_handling();
uint32_t read_num;
uint8_t buf[8];
res = lv_fs_read(&f, buf, 8, &read_num);
if(res != LV_FS_RES_OK || read_num != 8) my_error_handling();
lv_fs_close(&f);
The mode in :cpp:func:`lv_fs_open` can be :cpp:enumerator:`LV_FS_MODE_WR` to open for
writes only, :cpp:enumerator:`LV_FS_MODE_RD` for reads only, or
:cpp:enumerator:`LV_FS_MODE_RD` ``|`` :cpp:enumerator:`LV_FS_MODE_WR` for both.
This example shows how to read a directory's content. It's up to the
driver how to mark directories in the result but it can be a good
practice to insert a ``'/'`` in front of each directory name.
.. code-block:: c
lv_fs_dir_t dir;
lv_fs_res_t res;
res = lv_fs_dir_open(&dir, "S:/folder");
if(res != LV_FS_RES_OK) my_error_handling();
char fn[256];
while(1) {
res = lv_fs_dir_read(&dir, fn, sizeof(fn));
if(res != LV_FS_RES_OK) {
my_error_handling();
break;
}
/* fn is empty if there are no more files to read. */
if(strlen(fn) == 0) {
break;
}
printf("%s\n", fn);
}
lv_fs_dir_close(&dir);
Use Drives for Images
*********************
:ref:`Image <lv_image>` Widgets can be opened from files as well (besides
variables stored in the compiled program).
To use files in Image Widgets the following callbacks are required:
- open
- close
- read
- seek
- tell
.. _file_system_cache:
Optional File Buffering/Caching
*******************************
Files will buffer their reads if the corresponding ``LV_FS_*_CACHE_SIZE``
config option is set to a value greater than zero. Each open file will
buffer up to that many bytes to reduce the number of FS driver calls.
Generally speaking, file buffering can be optimized for different kinds
of access patterns. The one implemented here is optimal for reading large
files in chunks, which is what the image decoder does.
It has the potential to call the driver's ``read`` fewer
times than ``lv_fs_read`` is called. In the best case where the cache size is
\>= the size of the file, ``read`` will only be called once. This strategy is good
for linear reading of large files but less helpful for short random reads across a file bigger than the buffer
since data will be buffered that will be discarded after the next seek and read.
The cache should be sufficiently large or disabled in that case. Another case where the cache should be disabled
is if the file contents are expected to change by an external factor like with special OS files.
The implementation is documented below. Note that the FS functions make calls
to other driver FS functions when the cache is enabled. i.e., ``lv_fs_read`` may call the driver's ``seek``
so the driver needs to implement more callbacks when the cache is enabled.
``lv_fs_read`` :sub:`(behavior when cache is enabled)`
------------------------------------------------------
.. mermaid::
:zoom:
%%{init: {'theme':'neutral'}}%%
flowchart LR
A["call lv_fs_read and
the cache is enabled"] --> B{{"is there cached data
at the file position?"}}
B -->|yes| C{{"does the cache have
all required bytes available?"}}
C -->|yes| D["copy all required bytes from
the cache to the destination
buffer"]
C -->|no| F["copy the available
required bytes
until the end of the cache
into the destination buffer"]
--> G["seek the real file to the end
of what the cache had available"]
--> H{{"is the number of remaining bytes
larger than the size of the whole cache?"}}
H -->|yes| I["read the remaining bytes
from the real file to the
destination buffer"]
H -->|no| J["eagerly read the real file
to fill the whole cache
or as many bytes as the
read call can"]
--> O["copy the required bytes
to the destination buffer"]
B -->|no| K["seek the real file to
the file position"]
--> L{{"is the number of required
bytes greater than the
size of the entire cache?"}}
L -->|yes| M["read the real file to
the destination buffer"]
L -->|no| N["eagerly read the real file
to fill the whole cache
or as many bytes as the
read call can"]
--> P["copy the required bytes
to the destination buffer"]
``lv_fs_write`` :sub:`(behavior when cache is enabled)`
-------------------------------------------------------
The part of the cache that coincides with the written content
will be updated to reflect the written content.
``lv_fs_seek`` :sub:`(behavior when cache is enabled)`
------------------------------------------------------
The driver's ``seek`` will not actually be called unless the ``whence``
is ``LV_FS_SEEK_END``, in which case ``seek`` and ``tell`` will be called
to determine where the end of the file is.
``lv_fs_tell`` :sub:`(behavior when cache is enabled)`
------------------------------------------------------
The driver's ``tell`` will not actually be called.
.. _file_system_api:
API
***

View File

@@ -0,0 +1,646 @@
.. _overview_image:
=================
Images (lv_image)
=================
An image can be a file or a variable which stores the bitmap itself and
some metadata.
Store images
************
You can store images in two places
- as a variable in internal memory (RAM or ROM)
- as a file
.. _overview_image_variables:
Variables
---------
Images stored internally in a variable are composed mainly of an
:cpp:struct:`lv_image_dsc_t` structure with the following fields:
- **header**:
- *cf*: Color format. See :ref:`below <overview_image_color_formats>`
- *w*: width in pixels (<= 2048)
- *h*: height in pixels (<= 2048)
- *always zero*: 3 bits which need to be always zero
- *reserved*: reserved for future use
- **data**: pointer to an array where the image itself is stored
- **data_size**: length of ``data`` in bytes
These are usually stored within a project as C files. They are linked
into the resulting executable like any other constant data.
.. _overview_image_files:
Files
-----
To deal with files you need to add a storage *Drive* to LVGL. In short,
a *Drive* is a collection of functions (*open*, *read*, *close*, etc.)
registered in LVGL to make file operations. You can add an interface to
a standard file system (FAT32 on SD card) or you create your simple file
system to read data from an SPI Flash memory. In every case, a *Drive*
is just an abstraction to read and/or write data to memory. See the
:ref:`File system <file_system>` section to learn more.
Images stored as files are not linked into the resulting executable, and
must be read into RAM before being drawn. As a result, they are not as
resource-friendly as images linked at compile time. However, they are
easier to replace without needing to rebuild the main program.
.. _overview_image_color_formats:
Color formats
*************
Various built-in color formats are supported:
- :cpp:enumerator:`LV_COLOR_FORMAT_NATIVE`: Simply stores the RGB colors (in whatever color depth LVGL is configured for).
- :cpp:enumerator:`LV_COLOR_FORMAT_NATIVE_WITH_ALPHA`: Like :cpp:enumerator:`LV_COLOR_FORMAT_NATIVE` but it also adds an alpha (transparency) byte for every pixel.
- :cpp:enumerator:`LV_COLOR_FORMAT_I1`, :cpp:enumerator:`LV_COLOR_FORMAT_I2`, :cpp:enumerator:`LV_COLOR_FORMAT_I4`, :cpp:enumerator:`LV_COLOR_FORMAT_I8`:
Uses a palette with 2, 4, 16 or 256 colors and stores each pixel in 1, 2, 4 or 8 bits.
- :cpp:enumerator:`LV_COLOR_FORMAT_A1`, :cpp:enumerator:`LV_COLOR_FORMAT_A2`, :cpp:enumerator:`LV_COLOR_FORMAT_A4`, :cpp:enumerator:`LV_COLOR_FORMAT_A8`:
**Only stores the Alpha value with 1, 2, 4 or 8 bits.** The pixels take the color of ``style.img_recolor`` and
the set opacity. The source image has to be an alpha channel. This is
ideal for bitmaps similar to fonts where the whole image is one color
that can be altered.
The bytes of :cpp:enumerator:`LV_COLOR_FORMAT_NATIVE` images are stored in the following order.
- 32-bit color depth:
- **Byte 0**: Blue
- **Byte 1**: Green
- **Byte 2**: Red
- **Byte 3**: Alpha (only with :cpp:enumerator:`LV_COLOR_FORMAT_NATIVE_WITH_ALPHA`)
- 16-bit color depth:
- **Byte 0**: Green 3 lower bit, Blue 5 bit
- **Byte 1**: Red 5 bit, Green 3 higher bit
- **Byte 2**: Alpha byte (only with :cpp:enumerator:`LV_COLOR_FORMAT_NATIVE_WITH_ALPHA`)
- 8-bit color depth:
- **Byte 0**: Red 3 bit, Green 3 bit, Blue 2 bit
- **Byte 2**: Alpha byte (only with :cpp:enumerator:`LV_COLOR_FORMAT_NATIVE_WITH_ALPHA`)
You can store images in a *Raw* format to indicate that it's not encoded
with one of the built-in color formats and an external :ref:`Image decoder <overview_image_decoder>`
needs to be used to decode the image.
- :cpp:enumerator:`LV_COLOR_FORMAT_RAW`: Indicates a basic raw image (e.g. a PNG or JPG image).
- :cpp:enumerator:`LV_COLOR_FORMAT_RAW_ALPHA`: Indicates that an image has alpha and an alpha byte is added for every pixel.
Add and use images
******************
You can add images to LVGL in two ways:
- using the online converter
- manually create images
Online converter
----------------
The online Image converter is available here:
https://lvgl.io/tools/imageconverter
Adding an image to LVGL via the online converter is easy.
1. You need to select a *BMP*, *PNG* or *JPG* image first.
2. Give the image a name that will be used within LVGL.
3. Select the :ref:`Color format <overview_image_color_formats>`.
4. Select the type of image you want. Choosing a binary will generate a
``.bin`` file that must be stored separately and read using the :ref:`file support <overview_image_files>`.
Choosing a variable will generate a standard C file that can be linked into your project.
5. Hit the *Convert* button. Once the conversion is finished, your
browser will automatically download the resulting file.
In the generated C arrays (variables), bitmaps for all the color depths
(1, 8, 16 or 32) are included in the C file, but only the color depth
that matches :c:macro:`LV_COLOR_DEPTH` in *lv_conf.h* will actually be linked
into the resulting executable.
In the case of binary files, you need to specify the color format you
want:
- RGB332 for 8-bit color depth
- RGB565 for 16-bit color depth
- RGB565 Swap for 16-bit color depth (two bytes are swapped)
- RGB888 for 32-bit color depth
Manually create an image
------------------------
If you are generating an image at run-time, you can craft an image
variable to display it using LVGL. For example:
.. code-block:: c
uint8_t my_img_data[] = {0x00, 0x01, 0x02, ...};
static lv_image_dsc_t my_img_dsc = {
.header.always_zero = 0,
.header.w = 80,
.header.h = 60,
.data_size = 80 * 60 * LV_COLOR_DEPTH / 8,
.header.cf = LV_COLOR_FORMAT_NATIVE, /* Set the color format */
.data = my_img_data,
};
Another (possibly simpler) option to create and display an image at
run-time is to use the :ref:`Canvas <lv_canvas>` Widget.
Use images
----------
The simplest way to use an image in LVGL is to display it with an
:ref:`lv_image` Widget:
.. code-block:: c
lv_obj_t * icon = lv_image_create(lv_screen_active(), NULL);
/* From variable */
lv_image_set_src(icon, &my_icon_dsc);
/* From file */
lv_image_set_src(icon, "S:my_icon.bin");
If the image was converted with the online converter, you should use
:cpp:expr:`LV_IMAGE_DECLARE(my_icon_dsc)` to declare the image in the file where
you want to use it.
.. _overview_image_decoder:
Image decoder
*************
As you can see in the :ref:`overview_image_color_formats` section, LVGL
supports several built-in image formats. In many cases, these will be
all you need. LVGL doesn't directly support, however, generic image
formats like PNG or JPG.
To handle non-built-in image formats, you need to use external libraries
and attach them to LVGL via the *Image decoder* interface.
An image decoder consists of 4 callbacks:
:info: get some basic info about the image (width, height and color format).
:open: open an image:
- store a decoded image
- set it to ``NULL`` to indicate the image can be read line-by-line.
:get_area: if *open* didn't fully open an image this function should give back part of image as decoded data.
:close: close an opened image, free the allocated resources.
You can add any number of image decoders. When an image needs to be
drawn, the library will try all the registered image decoders until it
finds one which can open the image, i.e. one which knows that format.
The following formats are understood by the built-in decoder:
- :cpp:enumerator:`LV_COLOR_FORMAT_I1`
- :cpp:enumerator:`LV_COLOR_FORMAT_I2`
- :cpp:enumerator:`LV_COLOR_FORMAT_I4`
- :cpp:enumerator:`LV_COLOR_FORMAT_I8`
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB888`
- :cpp:enumerator:`LV_COLOR_FORMAT_XRGB8888`
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8888`
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB565`
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB565A8`
Custom image formats
--------------------
The easiest way to create a custom image is to use the online image
converter and select ``Raw`` or ``Raw with alpha`` format.
It will just take every byte of the
binary file you uploaded and write it as an image "bitmap". You then
need to attach an image decoder that will parse that bitmap and generate
the real, renderable bitmap.
``header.cf`` will be :cpp:enumerator:`LV_COLOR_FORMAT_RAW`, :cpp:enumerator:`LV_COLOR_FORMAT_RAW_ALPHA`
accordingly. You should choose the correct format according to your needs:
a fully opaque image, using an alpha channel.
After decoding, the *raw* formats are considered *True color* by the
library. In other words, the image decoder must decode the *Raw* images
to *True color* according to the format described in the :ref:`overview_image_color_formats` section.
Registering an image decoder
----------------------------
Here's an example of getting LVGL to work with PNG images.
First, you need to create a new image decoder and set some functions to
open/close the PNG files. It should look like this:
.. code-block:: c
/* Create a new decoder and register functions */
lv_image_decoder_t * dec = lv_image_decoder_create();
lv_image_decoder_set_info_cb(dec, decoder_info);
lv_image_decoder_set_open_cb(dec, decoder_open);
lv_image_decoder_set_get_area_cb(dec, decoder_get_area);
lv_image_decoder_set_close_cb(dec, decoder_close);
/**
* Get info about a PNG image
* @param decoder pointer to the decoder where this function belongs
* @param src can be file name or pointer to a C array
* @param header image information is set in header parameter
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't get the info
*/
static lv_result_t decoder_info(lv_image_decoder_t * decoder, const void * src, lv_image_header_t * header)
{
/* Check whether the type `src` is known by the decoder */
if(is_png(src) == false) return LV_RESULT_INVALID;
/* Read the PNG header and find `width` and `height` */
...
header->cf = LV_COLOR_FORMAT_ARGB8888;
header->w = width;
header->h = height;
}
/**
* Open a PNG image and decode it into dsc.decoded
* @param decoder pointer to the decoder where this function belongs
* @param dsc image descriptor
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't open the image
*/
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
(void) decoder; /* Unused */
/* Check whether the type `src` is known by the decoder */
if(is_png(dsc->src) == false) return LV_RESULT_INVALID;
/* Decode and store the image. If `dsc->decoded` is `NULL`, the `decoder_get_area` function will be called to get the image data line-by-line */
dsc->decoded = my_png_decoder(dsc->src);
/* Change the color format if decoded image format is different than original format. For PNG it's usually decoded to ARGB8888 format */
dsc->decoded.header.cf = LV_COLOR_FORMAT_...
/* Call a binary image decoder function if required. It's not required if `my_png_decoder` opened the image in true color format. */
lv_result_t res = lv_bin_decoder_open(decoder, dsc);
return res;
}
/**
* Decode an area of image
* @param decoder pointer to the decoder where this function belongs
* @param dsc image decoder descriptor
* @param full_area input parameter. the full area to decode after enough subsequent calls
* @param decoded_area input+output parameter. set the values to `LV_COORD_MIN` for the first call and to reset decoding.
* the decoded area is stored here after each call.
* @return LV_RESULT_OK: ok; LV_RESULT_INVALID: failed or there is nothing left to decode
*/
static lv_result_t decoder_get_area(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc,
const lv_area_t * full_area, lv_area_t * decoded_area)
{
/**
* If `dsc->decoded` is always set in `decoder_open` then `decoder_get_area` does not need to be implemented.
* If `dsc->decoded` is only sometimes set or never set in `decoder_open` then `decoder_get_area` is used to
* incrementally decode the image by calling it repeatedly until it returns `LV_RESULT_INVALID`.
* In the example below the image is decoded line-by-line but the decoded area can have any shape and size
* depending on the requirements and capabilities of the image decoder.
*/
my_decoder_data_t * my_decoder_data = dsc->user_data;
/* if `decoded_area` has a field set to `LV_COORD_MIN` then reset decoding */
if(decoded_area->y1 == LV_COORD_MIN) {
decoded_area->x1 = full_area->x1;
decoded_area->x2 = full_area->x2;
decoded_area->y1 = full_area->y1;
decoded_area->y2 = decoded_area->y1; /* decode line-by-line, starting with the first line */
/* create a draw buf the size of one line */
bool reshape_success = NULL != lv_draw_buf_reshape(my_decoder_data->partial,
dsc->decoded.header.cf,
lv_area_get_width(full_area),
1,
LV_STRIDE_AUTO);
if(!reshape_success) {
lv_draw_buf_destroy(my_decoder_data->partial);
my_decoder_data->partial = lv_draw_buf_create(lv_area_get_width(full_area),
1,
dsc->decoded.header.cf,
LV_STRIDE_AUTO);
my_png_decode_line_reset(full_area);
}
}
/* otherwise decoding is already in progress. decode the next line */
else {
/* all lines have already been decoded. indicate completion by returning `LV_RESULT_INVALID` */
if (decoded_area->y1 >= full_area->y2) return LV_RESULT_INVALID;
decoded_area->y1++;
decoded_area->y2++;
}
my_png_decode_line(my_decoder_data->partial);
return LV_RESULT_OK;
}
/**
* Close PNG image and free data
* @param decoder pointer to the decoder where this function belongs
* @param dsc image decoder descriptor
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't open the image
*/
static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
/* Free all allocated data */
my_png_cleanup();
my_decoder_data_t * my_decoder_data = dsc->user_data;
lv_draw_buf_destroy(my_decoder_data->partial);
/* Call the built-in close function if the built-in open/get_area was used */
lv_bin_decoder_close(decoder, dsc);
}
So in summary:
- In ``decoder_info``, you should collect some basic information about the image and store it in ``header``.
- In ``decoder_open``, you should try to open the image source pointed by
``dsc->src``. Its type is already in ``dsc->src_type == LV_IMG_SRC_FILE/VARIABLE``.
If this format/type is not supported by the decoder, return :cpp:enumerator:`LV_RESULT_INVALID`.
However, if you can open the image, a pointer to the decoded image should be
set in ``dsc->decoded``. If the format is known, but you don't want to
decode the entire image (e.g. no memory for it), set ``dsc->decoded = NULL`` and
use ``decoder_get_area`` to get the image area pixels.
- In ``decoder_close`` you should free all allocated resources.
- ``decoder_get_area`` is optional. In this case you should decode the whole image In
``decoder_open`` function and store image data in ``dsc->decoded``.
Decoding the whole image requires extra memory and some computational overhead.
Manually use an image decoder
-----------------------------
LVGL will use registered image decoders automatically if you try and
draw a raw image (i.e. using the ``lv_image`` Widget) but you can use them
manually as well. Create an :cpp:type:`lv_image_decoder_dsc_t` variable to describe
the decoding session and call :cpp:func:`lv_image_decoder_open`.
The ``color`` parameter is used only with ``LV_COLOR_FORMAT_A1/2/4/8``
images to tell color of the image.
.. code-block:: c
lv_result_t res;
lv_image_decoder_dsc_t dsc;
lv_image_decoder_args_t args = { 0 }; /* Custom decoder behavior via args */
res = lv_image_decoder_open(&dsc, &my_img_dsc, &args);
if(res == LV_RESULT_OK) {
/* Do something with `dsc->decoded`. You can copy out the decoded image by `lv_draw_buf_dup(dsc.decoded)`*/
lv_image_decoder_close(&dsc);
}
Image post-processing
---------------------
Considering that some hardware has special requirements for image formats,
such as alpha premultiplication and stride alignment, most image decoders (such as PNG decoders)
may not directly output image data that meets hardware requirements.
For this reason, LVGL provides a solution for image post-processing.
First, call a custom post-processing function after ``lv_image_decoder_open`` to adjust the data in the image cache,
and then mark the processing status in ``cache_entry->process_state`` (to avoid repeated post-processing).
See the detailed code below:
- Stride alignment and premultiply post-processing example:
.. code-block:: c
/* Define post-processing state */
typedef enum {
IMAGE_PROCESS_STATE_NONE = 0,
IMAGE_PROCESS_STATE_STRIDE_ALIGNED = 1 << 0,
IMAGE_PROCESS_STATE_PREMULTIPLIED_ALPHA = 1 << 1,
} image_process_state_t;
lv_result_t my_image_post_process(lv_image_decoder_dsc_t * dsc)
{
lv_color_format_t color_format = dsc->header.cf;
lv_result_t res = LV_RESULT_OK;
if(color_format == LV_COLOR_FORMAT_ARGB8888) {
lv_cache_lock();
lv_cache_entry_t * entry = dsc->cache_entry;
if(!(entry->process_state & IMAGE_PROCESS_STATE_PREMULTIPLIED_ALPHA)) {
lv_draw_buf_premultiply(dsc->decoded);
LV_LOG_USER("premultiplied alpha OK");
entry->process_state |= IMAGE_PROCESS_STATE_PREMULTIPLIED_ALPHA;
}
if(!(entry->process_state & IMAGE_PROCESS_STATE_STRIDE_ALIGNED)) {
uint32_t stride_expect = lv_draw_buf_width_to_stride(decoded->header.w, decoded->header.cf);
if(decoded->header.stride != stride_expect) {
LV_LOG_WARN("Stride mismatch");
lv_draw_buf_t * aligned = lv_draw_buf_adjust_stride(decoded, stride_expect);
if(aligned == NULL) {
LV_LOG_ERROR("No memory for Stride adjust.");
return NULL;
}
decoded = aligned;
}
entry->process_state |= IMAGE_PROCESS_STATE_STRIDE_ALIGNED;
}
alloc_failed:
lv_cache_unlock();
}
return res;
}
- GPU draw unit example:
.. code-block:: c
void gpu_draw_image(lv_draw_unit_t * draw_unit, const lv_draw_image_dsc_t * draw_dsc, const lv_area_t * coords)
{
...
lv_image_decoder_dsc_t decoder_dsc;
lv_result_t res = lv_image_decoder_open(&decoder_dsc, draw_dsc->src, NULL);
if(res != LV_RESULT_OK) {
LV_LOG_ERROR("Failed to open image");
return;
}
res = my_image_post_process(&decoder_dsc);
if(res != LV_RESULT_OK) {
LV_LOG_ERROR("Failed to post-process image");
return;
}
...
}
.. _overview_image_caching:
Image caching
*************
Sometimes it takes a lot of time to open an image. Continuously decoding
a PNG/JPEG image or loading images from a slow external memory would be
inefficient and detrimental to the user experience.
Therefore, LVGL caches image data. Caching means some
images will be left open, hence LVGL can quickly access them from
``dsc->decoded`` instead of needing to decode them again.
Of course, caching images is resource intensive as it uses more RAM to
store the decoded image. LVGL tries to optimize the process as much as
possible (see below), but you will still need to evaluate if this would
be beneficial for your platform or not. Image caching may not be worth
it if you have a deeply embedded target which decodes small images from
a relatively fast storage medium.
Cache size
----------
The size of cache (in bytes) can be defined with
:c:macro:`LV_CACHE_DEF_SIZE` in *lv_conf.h*. The default value is 0, so
no image is cached.
The size of cache can be changed at run-time with
:cpp:expr:`lv_cache_set_max_size(size_t size)`,
and get with :cpp:expr:`lv_cache_get_max_size()`.
Value of images
---------------
When you use more images than available cache size, LVGL can't cache all the
images. Instead, the library will close one of the cached images to free
space.
To decide which image to close, LVGL uses a measurement it previously
made of how long it took to open the image. Cache entries that hold
slower-to-open images are considered more valuable and are kept in the
cache as long as possible.
If you want or need to override LVGL's measurement, you can manually set
the *weight* value in the cache entry in
``cache_entry->weight = time_ms`` to give a higher or lower value. (Leave
it unchanged to let LVGL control it.)
Every cache entry has a *"life"* value. Every time an image is opened
through the cache, the *life* value of all entries is increased by their
*weight* values to make them older.
When a cached image is used, its *usage_count* value is increased
to make it more alive.
If there is no more space in the cache, the entry with *usage_count == 0*
and lowest life value will be dropped.
Memory usage
------------
Note that a cached image might continuously consume memory. For example,
if three PNG images are cached, they will consume memory while they are
open.
Therefore, it's the user's responsibility to be sure there is enough RAM
to cache even the largest images at the same time.
Clean the cache
---------------
Let's say you have loaded a PNG image into a :cpp:struct:`lv_image_dsc_t` ``my_png``
variable and use it in an ``lv_image`` Widget. If the image is already
cached and you then change the underlying PNG file, you need to notify
LVGL to cache the image again. Otherwise, there is no easy way of
detecting that the underlying file changed and LVGL will still draw the
old image from cache.
To do this, use :cpp:expr:`lv_cache_invalidate(lv_cache_find(&my_png, LV_CACHE_SRC_TYPE_PTR, 0, 0))`.
Custom cache algorithm
----------------------
If you want to implement your own cache algorithm, you can refer to the
following code to replace the LVGL built-in cache manager:
.. code-block:: c
static lv_cache_entry_t * my_cache_add_cb(size_t size)
{
...
}
static lv_cache_entry_t * my_cache_find_cb(const void * src, lv_cache_src_type_t src_type, uint32_t param1, uint32_t param2)
{
...
}
static void my_cache_invalidate_cb(lv_cache_entry_t * entry)
{
...
}
static const void * my_cache_get_data_cb(lv_cache_entry_t * entry)
{
...
}
static void my_cache_release_cb(lv_cache_entry_t * entry)
{
...
}
static void my_cache_set_max_size_cb(size_t new_size)
{
...
}
static void my_cache_empty_cb(void)
{
...
}
void my_cache_init(void)
{
/* Initialize new cache manager. */
lv_cache_manager_t my_manager;
my_manager.add_cb = my_cache_add_cb;
my_manager.find_cb = my_cache_find_cb;
my_manager.invalidate_cb = my_cache_invalidate_cb;
my_manager.get_data_cb = my_cache_get_data_cb;
my_manager.release_cb = my_cache_release_cb;
my_manager.set_max_size_cb = my_cache_set_max_size_cb;
my_manager.empty_cb = my_cache_empty_cb;
/* Replace existing cache manager with the new one. */
lv_cache_lock();
lv_cache_set_manager(&my_manager);
lv_cache_unlock();
}
.. _overview_image_api:
API
***
.. API startswith:
lv_image_

View File

@@ -0,0 +1,698 @@
.. _indev:
=======================
Input Device (lv_indev)
=======================
.. _indev_creation:
Creating an Input Device
************************
To create an input device on the :ref:`default_display`:
.. code-block:: c
/* 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:
- :cpp:enumerator:`LV_INDEV_TYPE_POINTER`: touchpad or mouse
- :cpp:enumerator:`LV_INDEV_TYPE_KEYPAD`: keyboard or keypad
- :cpp:enumerator:`LV_INDEV_TYPE_ENCODER`: encoder with left/right turn and push options
- :cpp:enumerator:`LV_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:
.. code-block:: c
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;
}
}
.. _indev_cursor:
Mouse Cursor
~~~~~~~~~~~~
Pointer input devices (like a mouse) can have a cursor.
.. code-block:: c
...
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
:cpp:expr:`lv_obj_remove_flag(cursor_obj, LV_OBJ_FLAG_CLICKABLE)`.
For images, *clicking* is disabled by default.
.. _indev_gestures:
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 :cpp:enumerator:`LV_EVENT_GESTURE` event. For example:
.. 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 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 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
~~~~~~~~~~~~~~
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 :cpp:enumerator:`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:
1. in the input device by the ``indev->rotary_sensitivity`` element (1/256 unit), and
2. by 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, 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.
Currently, the system sends the 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.
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;
/* 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;
lv_indev_update_recognizers(drv, &touches[0], touch_cnt);
touch_cnt = 0;
/* Set the gesture information, before returning to LVGL */
lv_indev_gesture_recognizers_set_data(drv, data);
}
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``:
.. code-block:: c
/**
* @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;
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 <https://wayland.freedesktop.org/libinput/doc/latest/>`_ 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``.
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
:cpp:enumerator:`LV_INDEV_TYPE_KEYPAD`.
- Create a Widget Group (``lv_group_t * g = lv_group_create()``) and add Widgets to
it with :cpp:expr:`lv_group_add_obj(g, widget)`.
- Assign the group to an input device: :cpp:expr:`lv_indev_set_group(indev, g)`.
- Use ``LV_KEY_...`` to navigate among the Widgets in the group. See
``lv_core/lv_group.h`` for the available keys.
.. code-block:: c
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:
1. press of its button,
2. oong-press of its button,
3. turn left, and
4. 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.
.. code-block:: c
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;
}
.. _indev_groups:
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 :ref:`Text Area <lv_textarea>` has focus
and you press some letter on a keyboard, the keys will be sent and inserted into the
text area. Similarly, if a :ref:`Slider <lv_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 :cpp:expr:`lv_group_t * g = lv_group_create()` and to add
a Widget to the group use :cpp:expr:`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 :cpp:expr:`lv_obj_get_group(widget)`.
To find out what Widget in a group has focus, if any, call
:cpp:expr:`lv_group_get_focused(group)`. If a Widget in that group has focus, it
will return a pointer to it, otherwise it will return NULL.
To associate a group with an input device use :cpp:expr:`lv_indev_set_group(indev, g)`.
.. _indev_keys:
Keys
~~~~
There are some predefined keys which have special meaning:
- :cpp:enumerator:`LV_KEY_NEXT`: Move focus to next object
- :cpp:enumerator:`LV_KEY_PREV`: Move focus to previous object
- :cpp:enumerator:`LV_KEY_ENTER`: Triggers :cpp:enumerator:`LV_EVENT_PRESSED`, :cpp:enumerator:`LV_EVENT_CLICKED`, or :cpp:enumerator:`LV_EVENT_LONG_PRESSED` etc. events
- :cpp:enumerator:`LV_KEY_UP`: Increase value or move up
- :cpp:enumerator:`LV_KEY_DOWN`: Decrease value or move down
- :cpp:enumerator:`LV_KEY_RIGHT`: Increase value or move to the right
- :cpp:enumerator:`LV_KEY_LEFT`: Decrease value or move to the left
- :cpp:enumerator:`LV_KEY_ESC`: Close or exit (e.g. close a :ref:`Drop-Down List <lv_dropdown>`)
- :cpp:enumerator:`LV_KEY_DEL`: Delete (e.g. a character on the right in a :ref:`Text Area <lv_textarea>`)
- :cpp:enumerator:`LV_KEY_BACKSPACE`: Delete (e.g. a character on the left in a :ref:`Text Area <lv_textarea>`)
- :cpp:enumerator:`LV_KEY_HOME`: Go to the beginning/top (e.g. in a :ref:`Text Area <lv_textarea>`)
- :cpp:enumerator:`LV_KEY_END`: Go to the end (e.g. in a :ref:`Text Area <lv_textarea>`)
The most important special keys in your :cpp:func:`read_cb` function are:
- :cpp:enumerator:`LV_KEY_NEXT`
- :cpp:enumerator:`LV_KEY_PREV`
- :cpp:enumerator:`LV_KEY_ENTER`
- :cpp:enumerator:`LV_KEY_UP`
- :cpp:enumerator:`LV_KEY_DOWN`
- :cpp:enumerator:`LV_KEY_LEFT`
- :cpp:enumerator:`LV_KEY_RIGHT`
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 :cpp:enumerator:`LV_KEY_LEFT` and :cpp:enumerator:`LV_KEY_RIGHT` because most
Widgets can be fully controlled with them.
With an encoder you should use only :cpp:enumerator:`LV_KEY_LEFT`, :cpp:enumerator:`LV_KEY_RIGHT`,
and :cpp:enumerator:`LV_KEY_ENTER`.
Edit and Navigate Mode
~~~~~~~~~~~~~~~~~~~~~~
Since a keypad has plenty of keys, it's easy to navigate between Widgets
and edit them using the keypad. But encoders have a limited number of
"keys" and hence it is difficult to navigate using the default options.
*Navigate* and *Edit* modes are used to avoid this problem with
encoders.
In *Navigate* mode, an encoder's :cpp:enumerator:`LV_KEY_LEFT` or :cpp:enumerator:`LV_KEY_RIGHT` is translated to
:cpp:enumerator:`LV_KEY_NEXT` or :cpp:enumerator:`LV_KEY_PREV`. Therefore, the next or previous object will be
selected by turning the encoder. Pressing :cpp:enumerator:`LV_KEY_ENTER` will change
to *Edit* mode.
In *Edit* mode, :cpp:enumerator:`LV_KEY_NEXT` and :cpp:enumerator:`LV_KEY_PREV` is usually used to modify an
object. Depending on the Widget's type, a short or long press of
:cpp:enumerator:`LV_KEY_ENTER` changes back to *Navigate* mode. Usually, a Widget
which cannot be pressed (like a :ref:`Slider <lv_slider>`) leaves
*Edit* mode upon a short click. But with Widgets where a short click has
meaning (e.g. :ref:`Button <lv_button>`), a long press is required.
Default Group
~~~~~~~~~~~~~
Interactive widgets (such as Buttons, Checkboxes, Sliders, etc.) can
be automatically added to a default group. Just create a group with
:cpp:expr:`lv_group_t * g = lv_group_create()` and set the default group with
:cpp:expr:`lv_group_set_default(g)`
Don't forget to assign one or more input devices to the default group
with :cpp:expr:`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 :cpp:enumerator:`LV_STATE_FOCUSED`
state. Hence, focused styles will be applied to it.
If a Widget switches to edit mode it enters the
:cpp:expr:`LV_STATE_FOCUSED | LV_STATE_EDITED` states so any style properties
assigned to these states will be shown.
See :ref:`styles` for more details.
Using Buttons with Encoder Logic
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In addition to standard encoder behavior, you can also utilize its logic
to navigate(focus) and edit widgets using buttons. This is especially
handy if you have only few buttons available, or you want to use other
buttons in addition to an encoder wheel.
You need to have 3 buttons available:
- :cpp:enumerator:`LV_KEY_ENTER`: will simulate press or pushing of the encoder button.
- :cpp:enumerator:`LV_KEY_LEFT`: will simulate turning encoder left.
- :cpp:enumerator:`LV_KEY_RIGHT`: will simulate turning encoder right.
- other keys will be passed to the focused widget.
If you hold the keys it will simulate an encoder advance with period
specified in ``indev_drv.long_press_repeat_time``.
.. code-block:: c
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
...
void encoder_with_keys_read(lv_indev_t * indev, lv_indev_data_t * data){
data->key = last_key(); /* Get the last pressed or released key */
/* use LV_KEY_ENTER for encoder press */
if(key_pressed()) data->state = LV_INDEV_STATE_PRESSED;
else {
data->state = LV_INDEV_STATE_RELEASED;
/* Optionally you can also use enc_diff, if you have encoder */
data->enc_diff = enc_get_new_moves();
}
}
Hardware Button
---------------
A *Hardware Button* here is an external button (switch) typically next to the screen
which is assigned to specific coordinates of the screen. If a button is pressed it
will simulate the pressing on the assigned coordinate, similar to a touchpad.
To assign Hardware Buttons to coordinates use ``lv_indev_set_button_points(my_indev,
points_array)``. ``points_array`` should look like ``const lv_point_t points_array[]
= { {12,30}, {60,90}, ...}``
.. admonition:: Important:
``points_array`` cannot be allowed to go out of scope. Either declare it as a
global variable or as a static variable inside a function.
.. code-block:: c
lv_indev_set_type(indev, LV_INDEV_TYPE_BUTTON);
...
void button_read(lv_indev_t * indev, lv_indev_data_t * data){
static uint32_t last_btn = 0; /* Store the last pressed button */
int btn_pr = my_btn_read(); /* Get the ID (0,1,2...) of the pressed button */
if(btn_pr >= 0) { /* Is there a button press? (E.g. -1 indicated no button was pressed) */
last_btn = btn_pr; /* Save the ID of the pressed button */
data->state = LV_INDEV_STATE_PRESSED; /* Set the pressed state */
} else {
data->state = LV_INDEV_STATE_RELEASED; /* Set the released state */
}
data->btn_id = last_btn; /* Save the last button */
}
When the ``button_read`` callback in the example above changes the ``data->btn_id`` to ``0``
a press/release action at the first index of the ``points_array`` will be performed (``{12,30}``).
.. _indev_other_features:
Other Features
**************
Parameters
----------
The default value of the following parameters can be changed in :cpp:type:`lv_indev_t`:
- ``scroll_limit`` Number of pixels to slide before actually scrolling the Widget
- ``scroll_throw`` Scroll throw (momentum) slow-down in [%]. Greater value means faster slow-down.
- ``long_press_time`` Press time to send :cpp:enumerator:`LV_EVENT_LONG_PRESSED` (in milliseconds)
- ``long_press_repeat_time`` Interval of sending :cpp:enumerator:`LV_EVENT_LONG_PRESSED_REPEAT` (in milliseconds)
- ``read_timer`` pointer to the ``lv_timer`` which reads the input device. Its parameters
can be changed by calling ``lv_timer_...()`` functions. :c:macro:`LV_DEF_REFR_PERIOD`
in ``lv_conf.h`` sets the default read period.
Feedback
--------
Besides ``read_cb`` a ``feedback_cb`` callback can be also specified in
:cpp:type:`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 :cpp:enumerator:`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 :c:macro:`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:
.. code-block:: c
/* 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:: :cpp:func:`lv_indev_read`, :cpp:func:`lv_timer_handler` and :cpp:func:`_lv_display_refr_timer` cannot run at the same time.
.. note:: For devices in event-driven mode, `data->continue_reading` is ignored.
.. admonition:: Further Reading
- `lv_port_indev_template.c <https://github.com/lvgl/lvgl/blob/master/examples/porting/lv_port_indev_template.c>`__
for a template for your own Input-Device driver.
.. _indev_api:
API
***

View File

@@ -0,0 +1,18 @@
.. _main_modules:
============
Main Modules
============
.. toctree::
:maxdepth: 2
display/index
indev
color
font
image
timer
animation
fs
draw/index

View File

@@ -0,0 +1,224 @@
.. _timer:
================
Timer (lv_timer)
================
LVGL has a built-in Timer system. You can register a function to have it
be called periodically. The Timers are handled and called in
:cpp:func:`lv_timer_handler`, which needs to be called every few milliseconds.
See :ref:`timer_handler` for more information.
By default, LVGL itself uses Timers to:
- refresh each display --- during the creation of each :ref:`Display`, a Timer is
created for that Display. That Timer refreshes the display based on the configured
value of :c:macro:`LV_DEF_REFR_PERIOD`, and also sends all display-related events,
like :cpp:enumerator:`LV_EVENT_REFR_START`, :cpp:enumerator:`LV_EVENT_REFR_READY`,
etc.
- read input devices --- during the creation of each :ref:`indev`, a Timer is
created for that Input Device based on the configured value of
:c:macro:`LV_DEF_REFR_PERIOD`. That Timer causes that input device to be read and
also sends all input-device-related events, like :cpp:enumerator:`LV_EVENT_CLICKED`,
:cpp:enumerator:`LV_EVENT_PRESSED`, etc.
- update system-monitor values --- if :c:macro:`LV_USE_SYSMON` is set to ``1`` in
``lv_conf.h``, one or more timers are created to periodically compute and
monitor system performance statistics and LVGL's memory usage.
Timers are non-preemptive, which means a Timer cannot interrupt another
Timer. Therefore, you can call any LVGL related function in a Timer.
Creating a Timer
****************
To create a new Timer, use
:cpp:expr:`lv_timer_create(timer_cb, period_ms, user_data)`. It returns an
:cpp:type:`lv_timer_t` ``*`` which can be used later to modify the
parameters of the Timer, pause it, or delete it when it is no longer needed.
:cpp:func:`lv_timer_create_basic` can also be used to create a new Timer without
specifying any parameters.
A Timer callback should have this prototype: ``void (*lv_timer_cb_t)(lv_timer_t *)``.
For example:
.. code-block:: c
void my_timer(lv_timer_t * timer)
{
/* Use the user_data */
uint32_t * user_data = lv_timer_get_user_data(timer);
printf("my_timer called with user data: %d\n", *user_data);
/* Do something with LVGL */
if(something_happened) {
something_happened = false;
lv_button_create(lv_screen_active());
}
}
...
static uint32_t user_data = 10;
lv_timer_t * timer = lv_timer_create(my_timer, 500, &user_data);
Ready and Reset
***************
:cpp:expr:`lv_timer_ready(timer)` makes a Timer run on the next call of
:cpp:func:`lv_timer_handler`.
:cpp:expr:`lv_timer_reset(timer)` resets the period of a Timer. It will be
called again after its currently-set period (in milliseconds) has elapsed.
See what happens when :ref:`no timers are ready <timer_handler_no_timer_ready>`
and :cpp:func:`lv_timer_handler` is called.
Setting Parameters
******************
You can modify these Timer parameters at any time during its life:
- :cpp:expr:`lv_timer_set_cb(timer, new_cb)`
- :cpp:expr:`lv_timer_set_period(timer, new_period_ms)`
- :cpp:expr:`lv_timer_set_user_data(timer, user_data)`
Repeat Count
************
When a Timer is created, its repeat-count is set to ``-1`` to cause it to repeat
indefinitely. You can make a Timer repeat only a given number of times with
:cpp:expr:`lv_timer_set_repeat_count(timer, count)`. By default, once the Timer has
run ``count`` times, it will be automatically deleted.
You can use :cpp:expr:`lv_timer_set_auto_delete(timer, false)` if you want the timer
to instead be paused after it has run ``count`` times. This can be handy if you
reuse that timer repeatedly and want to avoid the CPU and :cpp:func:`lv_malloc`
overhead of repeatedly creating and deleting a timer. If you use this option, you
will need to set its repeat count (to either ``-1`` or a positive repeat count, since
it will have decremented to ``0``) and :ref:`resume <timer_pause_and_resume>` it to
make it active again.
.. _timer_pause_and_resume:
Pause and Resume
****************
:cpp:expr:`lv_timer_pause(timer)` pauses the specified Timer.
:cpp:expr:`lv_timer_resume(timer)` resumes the specified Timer.
Measuring Idle Time
*******************
You can get the idle percentage time of :cpp:func:`lv_timer_handler` with
:cpp:func:`lv_timer_get_idle`. Note that it does not measure the idle time of
the overall system, only of :cpp:func:`lv_timer_handler`. This can be misleading if
you are using an operating system and DMA and/or GPU are used during rendering, as it
does not actually measure the time the OS spends in an idle thread.
If you are using an OS and wish to get the time the CPU is spending in an idle
thread, one way of doing so is configuring :c:macro:`LV_USE_SYSMON` and
:c:macro:`LV_USE_PERF_MONITOR` to ``1`` in ``lv_conf.h`` (if they are not already),
and setting the macro :c:macro:`LV_SYSMON_GET_IDLE` to the name of a function that
fetches the percent of CPU time spent in the OS's idle thread. An example of such
a function is :cpp:func:`lv_os_get_idle_percent` in ``lv_freertos.c``. While the
configuration is set this way, some system performance statistics (including CPU
load) will appear on the display in a partially-transparent label whose location is
set by the :c:macro:`LV_USE_PERF_MONITOR_POS` macro.
Enable and Disable
******************
You can temporarily disable Timer handling with :cpp:expr:`lv_timer_enable(false)`.
Be advised: this also pauses handling of Timers that refresh Display(s) and read
from input devices, so don't forget to re-enable it with
:cpp:expr:`lv_timer_enable(true)` as soon as the need for the pause is over.
Timer Handler Resume Callback
*****************************
When the Timer system has been disabled (causing :cpp:func:`lv_timer_handler` to
return early before it has processed any timers), if you want to take some action
when the Timer system is re-enabled again, set a resume callback using
:cpp:expr:`lv_timer_handler_set_resume_cb(cb, user_data)`. The callback should have
this prototype: ``void (*lv_timer_handler_resume_cb_t)(void*)``.
Asynchronous calls
******************
There are several cases in which you may not want to perform an action immediately.
Some examples are:
- you cannot delete a Widget because something else is still using it,
- you don't want to block execution now, or
- you detect the need to delete a Widget in a thread other than the thread making
LVGL calls (e.g. in a case where you are using a :ref:`Gateway Thread <Gateway
Thread>` to make all LVGL calls in a multi-threaded environment).
For these cases,
:cpp:expr:`lv_async_call(my_function, data_p)` can be used to call ``my_function`` on
the next invocation of :cpp:func:`lv_timer_handler`. As a side effect, this also
ensures it is called in a thread in which it is safe to make LVGL calls.
``data_p`` will be passed to the function when it's called. Note that only the data's
pointer is saved, so whatever it is pointing to needs to remain valid until the
function is called, so it can point to ``static``, global or dynamically allocated
data. If you want to cancel an asynchronous call, call
:cpp:expr:`lv_async_call_cancel(my_function, data_p)`, which will remove all
asynchronous calls matching ``my_function`` and ``data_p``.
Note that if :cpp:expr:`lv_async_call(my_function, data_p)` is called from a thread
other than the one that normally makes LVGL calls, you are still obligated to protect
the LVGL data structure using a MUTEX.
For example:
.. code-block:: c
void my_screen_clean_up(void * scr)
{
/* Free some resources related to `scr`*/
/* Finally delete the screen */
lv_obj_delete(scr);
}
...
/* Do something with the Widget on the current screen */
/* Delete screen on next call of `lv_timer_handler`, not right now. */
lv_lock();
lv_async_call(my_screen_clean_up, lv_screen_active());
lv_unlock();
/* The screen is still valid so you can do other things with it */
If you just want to delete a Widget and don't need to clean anything up
in ``my_screen_cleanup`` you could just use :cpp:expr:`lv_obj_delete_async(widget)` which
will delete the Widget on the next call to :cpp:func:`lv_timer_handler`.
.. _timer_api:
API
***