add some code
This commit is contained in:
@@ -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
|
||||
***
|
||||
@@ -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
|
||||
***
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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`
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,13 @@
|
||||
.. _draw:
|
||||
|
||||
=======
|
||||
Drawing
|
||||
=======
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
draw_pipeline
|
||||
draw_api
|
||||
draw_layers
|
||||
draw_descriptors
|
||||
@@ -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
|
||||
***
|
||||
@@ -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
|
||||
***
|
||||
@@ -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_
|
||||
@@ -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
|
||||
***
|
||||
@@ -0,0 +1,18 @@
|
||||
.. _main_modules:
|
||||
|
||||
============
|
||||
Main Modules
|
||||
============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
display/index
|
||||
indev
|
||||
color
|
||||
font
|
||||
image
|
||||
timer
|
||||
animation
|
||||
fs
|
||||
draw/index
|
||||
@@ -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
|
||||
***
|
||||
Reference in New Issue
Block a user