add some code

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

View File

@@ -0,0 +1,226 @@
.. _file_explorer:
=============
File Explorer
=============
``lv_file_explorer`` provides a UI enabling the end user to browse the contents of a
file system. Its main area is called the "Browsing Area" and provides the list of
files contained in the currently-viewed directory.
When enabled, there is also a "Quick-Access" panel on the left, which provides a
convenient way to reach parts of the file system that are frequently accessed.
Available "Quick-Access" destinations are:
- File System,
- HOME,
- Video,
- Pictures,
- Music, and
- Documents.
You specify what paths these lead to during ``lv_file_explorer``\ 's initialization.
``lv_file_explorer`` only provides the file browsing and events caused by user
activity (e.g. clicking a file), but does not provide the actual file operations.
Client code must hook various events and decide what to do when they are emitted
(e.g. a click or double-click on a file). The actions taken might to open the file,
display it, send it to some other part of the application, etc..
``lv_file_explorer`` passes the full path and name of file that was clicked to the
event callback functions. What happens next is up to the application designer.
``lv_file_explorer`` uses the :ref:`lv_table` Widget for the "Browsing Area", and the
:ref:`lv_list` Widget for the "Quick-Access" panel when it is enabled. Thus,
:c:macro:`LV_USE_TABLE` macro must be set to a non-zero value in ``lv_conf.h`` in
order to use ``lv_file_explorer``, and and :c:macro:`LV_USE_LIST` must be set to a
non-zero value to use the "Quick-Access" panel.
.. note::
In order to use File Explorer, :ref:`file_system` has to be set up and
know about all the drive letters you use when passing paths to File System
(described below).
Prerequisites
*************
If you haven't already done so, you will need to learn about the LVGL :ref:`File
System abstraction <file_system>`, since it must be set up and be functional
for File Explorer to work.
.. _file_explorer_usage:
Usage
*****
Set :c:macro:`LV_USE_FILE_EXPLORER` to a non-zero value in ``lv_conf.h``.
First use :cpp:expr:`lv_file_explorer_create(lv_screen_active())` to create a File
Explorer. The default size is the screen size. After that, you can
customize the style like any Widget.
The size of the ``current_path`` buffer is set by :c:macro:`LV_FILE_EXPLORER_PATH_MAX_LEN`
in ``lv_conf.h``.
The object hierarchy of a freshly-created File Explorer looks like this:
- ``File Explorer``: occupies full area of parent Widget, typically a Screen (Flex-Flow COLUMN)
- ``Container``: occupies full area of File Explorer (Flex grow 1)
- ``Quick-Access Panel``:
- ``Device List``: grows to accommodate children
- ``File System``: button
- ``Places List``: grows to accommodate children
- ``HOME``: button
- ``Video``: button
- ``Pictures``: button
- ``Music``: button
- ``Documents``: button
- ``Browser Panel``:
- ``Header``: 14% of ``Browser Panel`` height
- ``Current Path``: label
- ``File Table``: with 1 column, 86% of ``Browser Panel`` height
- Fields:
- ``home_dir`` = NULL
- ``video_dir`` = NULL
- ``pictures_dir`` = NULL
- ``music_dir`` = NULL
- ``docs_dir`` = NULL
- ``fs_dir`` = NULL
- ``current_path`` = [empty buffer]
- ``sel_fn`` (selected file)
- ``sort`` (default :cpp:enumerator:`LV_EXPLORER_SORT_NONE`)
Accessing the Parts
-------------------
This list of functions provides access to the parts shown in diagram above:
- :cpp:expr:`lv_file_explorer_get_selected_file_name(explorer)` (pointer
to NUL-terminated string containing file-path user selected; typically used inside
an :cpp:enumerator:`LV_EVENT_CLICKED` event)
- :cpp:expr:`lv_file_explorer_get_current_path(explorer)` (pointer to ``current_path`` ``char`` buffer)
- :cpp:expr:`lv_file_explorer_get_file_table(explorer)` (pointer to ``File Table`` :ref:`lv_table` Widget)
- :cpp:expr:`lv_file_explorer_get_header(explorer)` (pointer to ``Header`` :ref:`base_widget` Widget)
- :cpp:expr:`lv_file_explorer_get_path_label(explorer)` (pointer to ``Current Path Label`` :ref:`lv_label` Widget)
- :cpp:expr:`lv_file_explorer_get_quick_access_area(explorer)` (pointer to ``Quick-Access Panel`` :ref:`base_widget`)
- :cpp:expr:`lv_file_explorer_get_places_list(explorer)` (pointer to ``Places List`` :ref:`lv_list` Widget)
- :cpp:expr:`lv_file_explorer_get_device_list(explorer)` (pointer to ``Device List`` :ref:`lv_list` Widget)
Quick-Access Panel
------------------
The ``Quick-Access Panel`` behaves like a typical navigation panel and appears on the
left, while the ``Browser Panel`` appears on the right
This panel is optional. If you set :c:macro:`LV_FILE_EXPLORER_QUICK_ACCESS` to ``0``
in ``lv_conf.h``, the ``Quick-Access Panel`` will not be created. This saves only a
little bit of memory.
Soon after the File Explorer is created, you typically use
:cpp:expr:`lv_file_explorer_set_quick_access_path(explorer, LV_EXPLORER_XXX_DIR, "path")`
to set the path that will be navigated to when the buttons in the ``Quick-Access Panel``
are clicked, which is currently a fixed list. The corresponding values you will need
to pass as the 2nd argument are the following:
- :cpp:enumerator:`LV_EXPLORER_HOME_DIR`
- :cpp:enumerator:`LV_EXPLORER_MUSIC_DIR`
- :cpp:enumerator:`LV_EXPLORER_PICTURES_DIR`
- :cpp:enumerator:`LV_EXPLORER_VIDEO_DIR`
- :cpp:enumerator:`LV_EXPLORER_DOCS_DIR`
- :cpp:enumerator:`LV_EXPLORER_FS_DIR`
.. _file_explorer_sort:
Sort
----
You can use
:cpp:expr:`lv_file_explorer_set_sort(explorer, LV_EXPLORER_SORT_XX)` to set
the sorting method.
These are the possible sorting methods:
- :cpp:enumerator:`LV_EXPLORER_SORT_NONE` (default)
- :cpp:enumerator:`LV_EXPLORER_SORT_KIND`
:cpp:expr:`lv_file_explorer_get_sort(explorer)` returns the current sorting method.
.. _file_explorer_events:
Events
******
- :cpp:enumerator:`LV_EVENT_READY` Sent when a directory is opened, which can happen:
- when the File Explorer is initially opened,
- after a user clicks on a ``Quick-Access Panel`` navigation button, and
- after the user clicks on a directory displayed in the ``Browser Panel``.
You can use it to, for example, customize the file sort.
- :cpp:enumerator:`LV_EVENT_VALUE_CHANGED` Sent once when any item (file) in the
``Brwoser Panel``\ 's file list is clicked.
- :cpp:enumerator:`LV_EVENT_CLICKED` Sent twice when an item in the ``Browser Panel``
is clicked: once as a result of the input-device :cpp:enumerator:`LV_EVENT_RELEASED`
event and a second as a result of the input device :cpp:enumerator:`LV_EVENT_CLICKED`
event. This applies to files, directories, and the "< Back" item in the ``Browser Panel``.
In these events you can use :cpp:func:`lv_file_explorer_get_current_path` to get the
current path and :cpp:func:`lv_file_explorer_get_selected_file_name` to get the name
of the currently selected file in the event processing function. For example:
.. code-block:: c
static void file_explorer_event_handler(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = lv_event_get_target(e);
if(code == LV_EVENT_VALUE_CHANGED) {
char * cur_path = lv_file_explorer_get_current_path(widget);
char * sel_fn = lv_file_explorer_get_selected_file_name(widget);
LV_LOG_USER("%s%s", cur_path, sel_fn);
}
}
You can also save the obtained **path** and **file** name into an array
through functions such as :cpp:func:`strcpy` and :cpp:func:`strcat` for later use.
.. _file_explorer_example:
Example
*******
.. include:: ../../examples/others/file_explorer/index.rst
.. _file_explorer_api:
API
***

View File

@@ -0,0 +1,137 @@
.. _font_manager:
============
Font Manager
============
The font manager is a secondary encapsulation of the LVGL font engine,
which facilitates the use and management of fonts for applications.
The font management functions currently provided include:
- Font resource reference counting (reduces repeated creation of font resources).
- Font resource concatenation (font fallback).
- Font resource recycling mechanism (buffers recently deleted font resources to
reduce the time overhead of repeated creation).
.. _font_manager_usage:
Usage
*****
Enable FreeType and Font Manager in ``lv_conf.h`` by setting the
:c:macro:`LV_USE_FONT_MANAGER` macros to non-zero
values, and configure :c:macro:`LV_FONT_MANAGER_NAME_MAX_LEN` to set the maximum
length of the font name.
Initialize Font Manager
-----------------------
Use :cpp:func:`lv_font_manager_create` to create a font manager, where the
:cpp:func:`recycle_cache_size` parameter is used to set the number of font recycling
caches, which can improve font creation efficiency.
Use :cpp:func:`lv_font_manager_add_src_static` to add a mapping between font names
and font resources to tell the font manager how to access the font resources.
Note that if the font resource description structure is not statically allocated
(for example, allocated from a local variable), use :cpp:func:`lv_font_manager_add_src` to add the resource.
This function will copy the contents of the structure itself.
Use :cpp:func:`lv_font_manager_remove_src` to remove the font resource mapping.
It should be noted that the ``src`` parameter must strictly correspond to ``class_p``.
``class_p`` will affect the way the font manager interprets src. If an incompatible parameter is passed, the program may fail.
For currently supported font classes, please refer to the example code.
.. code-block:: c
static lv_font_manager_t * g_font_manager = NULL;
void font_manager_init(void)
{
/* Create font manager, with 8 fonts recycling buffers */
g_font_manager = lv_font_manager_create(8);
/* Add font path mapping to font manager */
lv_font_manager_add_src_static(g_font_manager, "Lato-Regular", "./lvgl/examples/libs/freetype/Lato-Regular.ttf", &lv_freetype_font_class);
char path[] = "/path/to/myfont.ttf";
lv_font_manager_add_src(g_font_manager, "MyFont", path, &lv_freetype_font_class);
}
Create Font from Font Manager
-----------------------------
The parameters will be passed to the font creation function of the font backend,
such as :cpp:func:`lv_freetype_font_create` and :cpp:func:`lv_tiny_ttf_create_file`.
The font backend will select the supported parameters by itself and ignore the unsupported parameters.
The ``font_family`` parameter can be filled with the names of multiple fonts
(separated by ``,``) to achieve font concatenation (when the corresponding glyph is
not found in a font file, it will automatically search from the next concatenated
font).
.. code-block:: c
static lv_font_t * g_font = NULL;
/* Create font from font manager */
lv_font_t * g_font = lv_font_manager_create_font(g_font_manager,
"Lato-Regular,MyFont",
LV_FREETYPE_FONT_RENDER_MODE_BITMAP,
24,
LV_FREETYPE_FONT_STYLE_NORMAL,
LV_FONT_KERNING_NONE);
/* Handle error case */
if(g_font == NULL) {
g_font = (lv_font_t *)LV_FONT_DEFAULT;
}
/* Create label with the font */
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_obj_set_style_text_font(label, g_font, 0);
lv_label_set_text(label, "Hello World!");
Delete Font
-----------
Use :cpp:func:`lv_font_manager_delete_font` to delete the font when it is no longer needed.
The font manager will mark the font resource as a recyclable font so that it has the
possibility of being more quickly created next time.
Note that you need to delete any Widgets that used the font first, and then
delete the font to avoid accessing wild pointers.
.. code-block:: c
/* Delete label and font */
lv_obj_delete(label);
lv_font_manager_delete_font(g_font_manager, g_font);
g_font = NULL;
Delete Font Manager
-------------------
Use :cpp:func:`lv_font_manager_delete` to destroy the entire font manager. It should
be noted that before destruction, it is necessary to ensure that the application has
deleted all fonts using :cpp:func:`lv_font_manager_delete_font`. The font manager
will check the reference status of all allocated fonts. If there are still fonts
being referenced, the font manager will fail to be destroyed and the function will return false.
.. _font_manager_example:
Example
*******
.. include:: ../../examples/others/font_manager/index.rst
.. _font_manager_api:
API
***

View File

@@ -0,0 +1,92 @@
.. _fragment:
========
Fragment
========
Fragment is a concept copied from
`Android <https://developer.android.com/guide/fragments>`__.
It represents a reusable portion of your app's UI. A fragment defines
and manages its own layout, has its own lifecycle, and can handle its
own events. Like Android's Fragment that must be hosted by an activity
or another fragment, Fragment in LVGL needs to be hosted by a Widget,
or another fragment. The fragment's view hierarchy becomes part of, or
attaches to, the host's view hierarchy.
Such concept also has some similarities to `UiViewController on
iOS <https://developer.apple.com/documentation/uikit/uiviewcontroller>`__.
Fragment Manager is a manager holding references to fragments attached
to it, and has an internal stack to achieve forward and backwards navigation. You can use
fragment manager to build a navigation stack, or a multi-pane application
easily.
.. _fragment_usage:
Usage
*****
Enable :c:macro:`LV_USE_FRAGMENT` in ``lv_conf.h``.
Create Fragment Class
---------------------
.. code-block:: c
struct sample_fragment_t {
/* IMPORTANT: don't miss this part */
lv_fragment_t base;
/* States, object references and data fields for this fragment */
const char *title;
};
const lv_fragment_class_t sample_cls = {
/* Initialize something needed */
.constructor_cb = sample_fragment_ctor,
/* Create view objects */
.create_obj_cb = sample_fragment_create_obj,
/* IMPORTANT: size of your fragment struct */
.instance_size = sizeof(struct sample_fragment_t),
};
Use ``lv_fragment_manager``
---------------------------
.. code-block:: c
/* Create fragment instance, and Widgets will be added to container */
lv_fragment_manager_t *manager = lv_fragment_manager_create(container, NULL);
/* Replace current fragment with instance of sample_cls, and init_argument is user defined pointer */
lv_fragment_manager_replace(manager, &sample_cls, init_argument);
Fragment Based Navigation
-------------------------
.. code-block:: c
/* Add one instance into manager stack. View object of current fragment will be destroyed,
* but instances created in class constructor will be kept.
*/
lv_fragment_manager_push(manager, &sample_cls, NULL);
/* Remove the top most fragment from the stack, and bring back previous one. */
lv_fragment_manager_pop(manager);
.. _fragment_example:
Example
*******
.. include:: ../../examples/others/fragment/index.rst
.. _fragment_api:
API
***

View File

@@ -0,0 +1,93 @@
.. _gridnav:
===============
Grid navigation
===============
Grid navigation (gridnav for short) is a feature that moves focus among a set
of child Widgets via arrow-key presses.
If the child Widgets are arranged into a grid-like layout then the up, down,
left and right arrows move focus to the nearest sibling in the
respective direction.
It doesn't matter how the children are positioned, as only the current x
and y coordinates are considered. This means that gridnav works with
manually positioned children, as well as :ref:`flex` and
:ref:`grid` layouts.
Gridnav also works if the children are arranged into a single row or
column. This is useful, for example, to simplify navigation among items in a
:ref:`List Widget <lv_list>`.
Gridnav assumes that the Widget to which gridnav is added is part of a
:ref:`group <indev_groups>`. This way, if the Widget with
gridnav has focus, the arrow key presses are automatically forwarded to
the Widget so that gridnav can process the arrow keys.
To move the focus to the next widget of the group use
:cpp:enumerator:`LV_KEY_NEXT` or :cpp:enumerator:`LV_KEY_PREV`.
Optionally you can also use :cpp:func:`lv_group_focus_next`
or :cpp:func:`lv_group_focus_prev` or the ``TAB``
key on keyboard as usual.
If the container is scrollable and the focused child is out of the view,
gridnav will automatically scroll the child into view.
.. _gridnav_usage:
Usage
*****
To add gridnav behavior to any Widget (e.g. one serving as a container for
child Widgets that the end user will navigate among using arrow keys) use
:cpp:expr:`lv_gridnav_add(container, flags)`.
The ``flags`` argument controls the navigation behavior:
- :cpp:enumerator:`LV_GRIDNAV_CTRL_NONE`: Default settings
- :cpp:enumerator:`LV_GRIDNAV_CTRL_ROLLOVER`: If there is no next/previous Widget in a
direction, the focus goes to the Widget in the next/previous row (on
left/right keys) or first/last row (on up/down keys)
- :cpp:enumerator:`LV_GRIDNAV_CTRL_SCROLL_FIRST`: If an arrow is pressed and the focused
Widget can be scrolled in that direction then it will be scrolled instead of
going to the next/previous Widget. If there is no more room for scrolling the
next/previous Widget will receive focus normally.
- :cpp:enumerator:`LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY`: Only use the left/right keys
for grid navigation. Up/down key events will be sent to the Widget that has focus.
- :cpp:enumerator:`LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY`: Only use the up/down keys
for grid navigation. Left/right key events will be sent to the Widget that has focus.
While the above behaviors can be combined by bit-wise OR-ing the above values together,
:cpp:enumerator:`LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY` and :cpp:enumerator:`LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY`
should not be used together.
:cpp:expr:`lv_gridnav_remove(container)` Removes gridnav behavior from a Widget.
Focusable Widgets
*****************
A Widget needs to be clickable or click focusable
(:cpp:enumerator:`LV_OBJ_FLAG_CLICKABLE` or :cpp:enumerator:`LV_OBJ_FLAG_CLICK_FOCUSABLE`) and not
hidden (:cpp:enumerator:`LV_OBJ_FLAG_HIDDEN`) to receive focus via gridnav.
.. _gridnav_example:
Examples
********
.. include:: ../../examples/others/gridnav/index.rst
.. _gridnav_api:
API
***

View File

@@ -0,0 +1,132 @@
.. _ime_pinyin:
==========
Pinyin IME
==========
Pinyin IME provides an API to provide Chinese Pinyin input method (Chinese
input) for a Keyboard Widget, which supports both 26-key and 9-key input modes.
You can think of ``lv_ime_pinyin`` as a Pinyin input method plug-in for
the Keyboard Widget.
Normally, an environment where :ref:`lv_keyboard` can
run can also run ``lv_ime_pinyin``. There are two main influencing
factors: the size of the font file and the size of the dictionary.
.. _ime_pinyin_usage:
Usage
*****
Enable :c:macro:`LV_USE_IME_PINYIN` in ``lv_conf.h``.
First use :cpp:expr:`lv_ime_pinyin_create(lv_screen_active())` to create a Pinyin
input-method plug-in, then use
:cpp:expr:`lv_ime_pinyin_set_keyboard(pinyin_ime, kb)` to add the Keyboard Widget
you created to the Pinyin input method plug-in. You can use
:cpp:expr:`lv_ime_pinyin_set_dict(pinyin_ime, your_dict)` to use a custom
dictionary. If you don't want to use the built-in dictionary,
you can disable :c:macro:`LV_IME_PINYIN_USE_DEFAULT_DICT` in ``lv_conf.h``,
which can save a lot of memory space.
The built-in thesaurus is customized based on the
**LV_FONT_SOURCE_HAN_SANS_SC_16_CJK** font library, which currently has more
than 1,000 of the most common CJK radicals, so it is recommended to use a
custom font and thesaurus.
In the process of using the Pinyin input method plug-in, you can change
the Keyboard and dictionary at any time.
Custom Dictionary
*****************
If you don't want to use the built-in Pinyin dictionary, or if you feel that the
built-in phonetic dictionary consumes too much memory, you can use a custom dictionary.
Customizing the dictionary is very simple.
First, set :c:macro:`LV_IME_PINYIN_USE_DEFAULT_DICT` to ``0`` in ``lv_conf.h``
Then, write a dictionary in the following format.
Dictionary format
-----------------
The arrangement order of each pinyin syllable is very important. If you
need to customize your own thesaurus according to the Hanyu Pinyin
syllable table, you can read
`here <https://baike.baidu.com/item/%E6%B1%89%E8%AF%AD%E6%8B%BC%E9%9F%B3%E9%9F%B3%E8%8A%82/9167981>`__
to learn about the Hanyu Pinyin syllables and the syllable table.
Then, write your own dictionary according to the following format:
.. code-block:: c
lv_100ask_pinyin_dict_t your_pinyin_dict[] = {
{ "a", "啊阿呵吖" },
{ "ai", "埃挨哎唉哀皑蔼矮碍爱隘癌艾" },
{ "an", "按安暗岸俺案鞍氨胺厂广庵揞犴铵桉谙鹌埯黯" },
{ "ang", "昂肮盎仰" },
{ "ao", "凹敖熬翱袄傲奥懊澳" },
{ "ba", "芭捌叭吧笆八疤巴拔跋靶把坝霸罢爸扒耙" },
{ "bai", "白摆佰败拜柏百稗伯" },
/* ...... */
{ "zuo", "昨左佐做作坐座撮琢柞"},
{NULL, NULL}
**The last item** must be ``{null, null}``, or it will not work
properly.
.. _ime_pinyin_apply_new_dictionary:
Applying a new dictionary
-------------------------
After writing a dictionary according to the above dictionary format, you
only need to call this function to set up and use your dictionary:
.. code-block:: c
lv_obj_t * pinyin_ime = lv_100ask_pinyin_ime_create(lv_screen_active());
lv_100ask_pinyin_ime_set_dict(pinyin_ime, your_pinyin_dict);
.. _ime_pinyin_modes:
Modes
*****
lv_ime_pinyin has the following modes:
- :cpp:enumerator:`LV_IME_PINYIN_MODE_K26`: Pinyin 26-key input mode
- :cpp:enumerator:`LV_IME_PINYIN_MODE_K9`: Pinyin 9-key input mode
- :cpp:enumerator:`LV_IME_PINYIN_MODE_K9_NUMBER`: Numeric keypad mode
The Keyboard's ``TEXT``-mode layout contains buttons to change mode.
To set the mode manually, use :cpp:expr:`lv_ime_pinyin_set_mode(pinyin_ime, mode)`.
The default mode is :cpp:enumerator:`LV_IME_PINYIN_MODE_K26`.
.. _ime_pinyin_example:
Example
*******
.. include:: ../../examples/others/ime/index.rst
.. _ime_pinyin_api:
API
***

View File

@@ -0,0 +1,53 @@
.. _lv_imgfont:
==========
Image font
==========
Draw image in **label** or **span** obj with :cpp:type:`lv_imgfont`. This is often used to
display Unicode emoji icons in text.
Supported image formats: determined by enabled LVGL :ref:`image decoders <overview_image_decoder>`.
.. _lv_imgfont_usage:
Usage
*****
Enable :c:macro:`LV_USE_IMGFONT` in ``lv_conf.h``.
To create a new *imgfont*:
.. code-block:: c
static lv_font_t * imgfont;
...
imgfont = lv_imgfont_create(height, path_cb, user_data);
- ``height`` Font size.
- ``path_cb`` A function to get the image path of a character.
Pass ``NULL`` if no image should be shown, but the character itself.
- ``user_data`` Pointer to user data.
To use the *imgfont* in a label, reference it:
:cpp:expr:`lv_obj_set_style_text_font(label, imgfont, LV_PART_MAIN)`
To destroy the *imgfont* that is no longer used, use :cpp:expr:`lv_imgfont_destroy(imgfont)`.
.. _lv_imgfont_example:
Example
*******
.. include:: ../../examples/others/imgfont/index.rst
.. _lv_imgfont_api:
API
***

View File

@@ -0,0 +1,22 @@
.. _auxiliary_modules:
=================
Auxiliary Modules
=================
.. toctree::
:maxdepth: 2
file_explorer
font_manager
fragment
gridnav
ime_pinyin
imgfont
monkey
obj_id
obj_property
observer/index
snapshot
test
xml/index

View File

@@ -0,0 +1,62 @@
.. _monkey:
======
Monkey
======
The Monkey module provides LVGL applications with a simple monkey test. Monkey
Testing is a technique where the user tests the application or system by providing
random inputs and checking the behavior or seeing whether the aplication or system
will crash. This module provides this service as simulated random input to stress
test an LVGL application.
.. _monkey_usage:
Usage
*****
First, enable :c:macro:`LV_USE_MONKEY` in ``lv_conf.h``.
Next, declare a variable (it can be local) of type :c:type:`lv_monkey_config_t` to
define the configuration structure, initialize it using
:cpp:expr:`lv_monkey_config_init(cfg)` then set its ``type`` member to the desired
type of :ref:`input device <indev>`, and set the ``min`` and ``max`` values for its
``period_range`` and ``input_range`` members to set the time ranges (in milliseconds)
and input ranges the Monkey module will use to generate random input at random times.
Next, call :cpp:expr:`lv_monkey_create(cfg)` to create the Monkey. It returns a
pointer to the ``lv_monkey_t`` created.
Finally call :cpp:expr:`lv_monkey_set_enable(monkey, true)` to enable Monkey.
If you want to pause the monkey, call
:cpp:expr:`lv_monkey_set_enable(monkey, false)`. To delete it, call
:cpp:expr:`lv_monkey_delete(monkey)`.
Note that ``input_range`` has different meanings depending on the ``type`` input device:
- :cpp:enumerator:`LV_INDEV_TYPE_POINTER`: No effect, click randomly within the pixels of the screen resolution.
- :cpp:enumerator:`LV_INDEV_TYPE_ENCODER`: The minimum and maximum values of ``enc_diff``.
- :cpp:enumerator:`LV_INDEV_TYPE_BUTTON`: The minimum and maximum values of ``btn_id``.
Use :cpp:func:`lv_monkey_get_indev` to get the input device, and use
:cpp:func:`lv_indev_set_button_points` to map the key ID to the coordinates.
- :cpp:enumerator:`LV_INDEV_TYPE_KEYPAD`: No effect, Send random :ref:`indev_keys`.
.. _monkey_example:
Example
*******
.. include:: ../../examples/others/monkey/index.rst
.. _monkey_api:
API
***

View File

@@ -0,0 +1,197 @@
.. _widget_id:
=========
Widget ID
=========
Widgets can optionally have identifiers added to their functionality if needed for
the application. Exactly how that happens is designed to be flexible, and can morph
with the needs of the application. It can even be a timestamp or other data current
at the time the Widget was created.
.. _widget_id_usage:
Usage
*****
Enable Widget ID functionality by setting :c:macro:`LV_USE_OBJ_ID` to ``1`` in ``lv_conf.h``.
Once enabled, several things change:
- each Widget will now have a ``void *`` field called ``id``;
- these two API functions become available:
- :cpp:expr:`lv_obj_get_id(widget)`,
- :cpp:expr:`lv_obj_find_by_id(widget, id)`;
- several more Widget-ID-related API functions become available if
:c:macro:`LV_USE_OBJ_ID_BUILTIN` is non-zero (more on this below);
- two additional configuration macros both :c:macro:`LV_OBJ_ID_AUTO_ASSIGN` and
:c:macro:`LV_USE_OBJ_ID_BUILTIN` now have meaning.
:c:macro:`LV_OBJ_ID_AUTO_ASSIGN`
--------------------------------
This macro in ``lv_conf.h`` defaults to whatever value :c:macro:`LV_USE_OBJ_ID`
equates to. You can change this if you wish. Either way, if it equates to a
non-zero value, it causes two things to happen:
- :cpp:expr:`lv_obj_assign_id(class_p, widget)` will be called at the end of each
Widget's creation, and
- :cpp:expr:`lv_obj_free_id(widget)` will be called at the end of the sequence when
each Widget is deleted.
Because of this timing, custom versions of these functions can be used according to
the below, and they can even be used like "event hooks" to implement a trace
operation that occurs when each Widget is created and deleted.
:cpp:expr:`lv_obj_assign_id(class_p, widget)`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This function (whether provided by LVGL or by you --- more on this below) is
responsible for assigning a value to the Widget's ``id`` field, and possibly do
other things, depending on the implementation.
:cpp:expr:`lv_obj_free_id(widget)`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This function (whether provided by LVGL or by you --- more on this below) is
responsible for doing the clean-up of any resources allocated by
:cpp:func:`lv_obj_assign_id()`
:c:macro:`LV_USE_OBJ_ID_BUILTIN`
--------------------------------
This macro in ``lv_conf.h`` equates to ``1`` by default. You can change this if you
wish. When it equates to a non-zero value the following function implementations are
provided by LVGL:
- :cpp:expr:`lv_obj_assign_id(class_p, widget)`
- :cpp:expr:`lv_obj_free_id(widget)`
- :cpp:expr:`lv_obj_set_id(widget, id)`
- :cpp:expr:`lv_obj_stringify_id(widget, buf, len)`
- :cpp:expr:`lv_obj_id_compare(id1, id2)`
These supply the default implementation for Widget IDs, namely that for each Widget
created, :cpp:expr:`lv_obj_stringify_id(widget, buf, len)` will produce a unique
string for it. Example: if the following 6 Widgets are created in this sequence:
- Screen
- Label
- Button
- Label
- Label
- Image
the strings produced by :cpp:expr:`lv_obj_stringify_id(widget, buf, len)` would be
- obj1
- label1
- btn1
- label2
- label3
- image1
respectively.
.. _widget_id_custom_generator:
Using a custom ID generator
---------------------------
If you wish, you can provide custom implementations for several Widget-ID related
functions. You do this by first setting :c:macro:`LV_USE_OBJ_ID_BUILTIN` to `0` in
``lv_conf.h``.
You will then need to provide implementations for the following functions (and link
them with LVGL):
.. code-block:: c
const char * lv_obj_stringify_id(lv_obj_t * widget, char * buf, uint32_t len);
int lv_obj_id_compare(const void * id1, const void * id2);
If :c:macro:`LV_OBJ_ID_AUTO_ASSIGN` equates to a non-zero value (or if you otherwise
simply need to use them), you will also need to provide implementations for:
.. code-block:: c
void lv_obj_assign_id(const lv_obj_class_t * class_p, lv_obj_t * widget);
void lv_obj_free_id(lv_obj_t * widget);
If :c:macro:`LV_BUILD_TEST` equates to a non-zero value and you are including LVGL
test code in your compile (or if you otherwise simply need to use them), you
will also need to provide an implementation for:
.. code-block:: c
void lv_obj_set_id(lv_obj_t * widget, void * id);
Examples of implementations of these functions exist in ``lv_obj_id_builtin.c``, but
you are free to use a different design if needed.
:cpp:func:`lv_obj_stringify_id` converts the passed ``widget`` to a string
representation (typically incorporating the ``id`` field) and writes it into the
buffer provided in its ``buf`` argument.
:cpp:func:`lv_obj_id_compare` compares 2 ``void * id`` values and returns ``0`` when
they are considered equal, and non-zero otherwise.
If :c:macro:`LV_OBJ_ID_AUTO_ASSIGN` equates to a non-zero value,
:cpp:func:`lv_obj_assign_id` is called when a Widget is created. It is responsible
for assigning a value to the Widget's ``id`` field. A pointer to the Widget's final
class is passed in its ``class_p`` argument in case it is needed for determining the
value for the ``id`` field, or for other possible needs related to your design for
Widget IDs. Note that this pointer may be different than :cpp:expr:`widget->class_p`
which is the class of the Widget currently being created.
If :c:macro:`LV_OBJ_ID_AUTO_ASSIGN` equates to a non-zero value,
:cpp:func:`lv_obj_free_id` is called when a Widget is deleted. It needs to perform
the clean-up for any resources allocated by :cpp:func:`lv_obj_assign_id`.
Dumping a Widget Tree
---------------------
Regardless of the state of any of the above macros, the function
:cpp:expr:`lv_obj_dump_tree(widget)` provides a "dump" of the Widget Tree for the
specified Widget (that Widget plus all its children recursively) using the
currently-configured method used by the :c:macro:`LV_LOG_USER` macro. If NULL is
passed instead of a pointer to a "root" Widget, the dump will include the Widget Tree
for all :ref:`Screens`, for all :ref:`Displays <display>` in the system.
For :c:macro:`LV_LOG_USER` to produce output, the following needs to be true in
``lv_conf.h``:
- :c:macro:`LV_USE_LOG` must equate to a non-zero value
- :c:macro:`LV_LOG_LEVEL` <= :c:macro:`LV_LOG_LEVEL_USER`
It will recursively walk through all that Widget's children (starting with the Widget
itself) and print the Widget's parent's address, the Widget's address, and if
:c:macro:`LV_USE_OBJ_ID` equates to a non-zero value, will also print the output of
:cpp:func:`lv_obj_stringify_id` for that Widget.
This can be useful in the event of a UI crash. From that log you can examine the
state of the Widget Tree when :cpp:expr:`lv_obj_dump_tree(widget)` was called.
For example, if a pointer to a deleted Widget is stored in a Timer's
:cpp:expr:`timer->user_data` field when the timer event callback is called, attempted
use of that pointer will likely cause a crash because the pointer is not valid any
more. However, a timely dump of the Widget Tree right before that point will show
that the Widget no longer exists.
Find child by ID
----------------
.. warning::
``lv_obj_find_by_id(widget, id)`` is deprecated. To find a widget use ``obj_name``.
:cpp:expr:`lv_obj_find_by_id(widget, id)` will perform a recursive walk through
``widget``\ 's children and return the first child encountered having the given ID.

View File

@@ -0,0 +1,278 @@
.. _widget_property:
=================
Widget Properties
=================
Widget Properties provides a way to greatly reduce the size of the interface between
LVGL and whatever logic layer is just above it, to get and set the most important
properties of Widgets. It's intended use is to:
- simplify (decreasing development time) writing bindings for LVGL in another
language, such as:
- Micropython,
- Lua,
- Python,
- Perl,
- .NET
- make it possible to control the UI (or parts of it, e.g. animation) via external
input, without modifying firmware, such as:
- an external text file (YAML, JSON, XML, custom)
- any external input source (e.g. serial)
While using it consumes more program space and more CPU overhead while setting and
getting Widget properties, it is designed so minimize that additional CPU overhead.
What is a Widget Property?
**************************
A Widget's properties are the combined set of :ref:`styles` plus additional properties
that are unique to each type of Widget, that determine what the Widget looks like and
how it behaves. Examples: size, position, color, are properties of all Widgets
whereas text, long-mode, selection-start, and selection-end, are properties unique to
Label Widgets. A Widget's :ref:`local styles <style_local>` are also valid
properties in this context.
The non-style Widget properties available for a given Widget are implemented at the
top of that Widget's primary ``.c`` file as a ``const`` id-to-function-pointer lookup
array, like this example for the Label Widget:
.. code:: c
#if LV_USE_OBJ_PROPERTY
static const lv_property_ops_t properties[] = {
{
.id = LV_PROPERTY_LABEL_TEXT,
.setter = lv_label_set_text,
.getter = lv_label_get_text,
},
{
.id = LV_PROPERTY_LABEL_LONG_MODE,
.setter = lv_label_set_long_mode,
.getter = lv_label_get_long_mode,
},
{
.id = LV_PROPERTY_LABEL_TEXT_SELECTION_START,
.setter = lv_label_set_text_selection_start,
.getter = lv_label_get_text_selection_start,
},
{
.id = LV_PROPERTY_LABEL_TEXT_SELECTION_END,
.setter = lv_label_set_text_selection_end,
.getter = lv_label_get_text_selection_end,
},
};
#endif
This array is attached to the ``properties`` field of the Widget's class, so all
Widgets of the same type share the same id-to-function-pointer lookup array.
Some properties are read-only. When this is the case, only the ``getter`` field in
the corresponding array element will be initialized with a function pointer.
Example: an object's child-Widget count or scroll position must be controlled via
other types of input, but their values are readable through this API.
.. _widget_property_usage:
Usage
*****
By default, this feature of LVGL is turned off. It can be turned on by configuring
:c:macro:`LV_USE_OBJ_PROPERTY` to ``1`` in ``lv_conf.h``.
The 3 functions that then become available are:
- :cpp:type:`lv_result_t` :cpp:expr:`lv_obj_set_property(widget, lv_property_t * value)`
Sets specified property of Widget.
- :cpp:type:`lv_property_t` :cpp:expr:`lv_obj_get_property(widget, lv_prop_id_t id)`
Reads property value from Widget.
- :cpp:type:`lv_result_t` :cpp:expr:`lv_obj_set_properties(widget, lv_property_t * values, count)`
Sets multiple Widget properties from an array of :cpp:type:`lv_property_t`.
An ``lv_prop_id_t`` is a :ref:`widget_property_id`, whereas an ``lv_property_t`` is a
struct that pairs a :ref:`widget_property_id` with a :ref:`widget_property_value`.
The following is an example of an array that could be used as the ``values`` argument
in :cpp:func:`lv_obj_set_properties`:
.. code-block:: c
lv_property_t values[] = {
{ .id = LV_PROPERTY_IMAGE_SRC, .ptr = &img_demo_widgets_avatar, },
{ .id = LV_PROPERTY_IMAGE_PIVOT, .ptr = &pivot_50, },
{ .id = LV_PROPERTY_IMAGE_SCALE, .num = 128, },
{ .id = LV_PROPERTY_OBJ_FLAG_CLICKABLE, .num = 1, },
{ .id = LV_STYLE_IMAGE_OPA, .num = 128, },
{ .id = LV_STYLE_BG_COLOR, .color = (lv_color_t){.red = 0x11, .green = 0x22, .blue = 0x33}, },
}
Alternately, :cpp:expr:`lv_obj_set_property(widget, value)` could be called using
this array's individual ``value`` elements inside a loop.
.. _widget_property_id:
Property ID
-----------
:cpp:type:`lv_prop_id_t` identifies which property to get/set. It is an enum value
defined in the primary ``.h`` file for the Widget in question. Because the actual
names are "assembled" by a preprocessor string-concatenation macro and are thus
hard to visualize, you can also find the names in the Widget's primary ``.c`` file in
the ``properties[]`` array initializing the ``.id`` fields in the array. For example,
``LV_PROPERTY_LABEL_TEXT`` is one found in ``lv_label.c``, and the properties
available to all Widgets are found near the top of the ``lv_obj.c`` file.
That array is attached to the Widget's class, enabling "getter" and "setter" functions
to be looked up for each type of Widget where Widget properties has been implemented.
(Note: this is done internally so you don't have to.)
If the property you need to set or get using this API is not implemented yet, you can
add your own Widget property ID following same rules and using one of two helper
macros in the ``enum`` in the Widget's primary ``.h`` file. In both cases, the
"assembled" value is a 32-bit value:
- :c:macro:`LV_PROPERTY_ID` (for single values -- see :ref:`Single Values` below)`;
bits ``<31:28>`` contain the property's value type and bits ``<27:0>`` contain the
property ID.
- :c:macro:`LV_PROPERTY_ID2` (for paired values -- see :ref:`Paired Values` below)`;
bits ``<31:28>`` contain the type for the property's 1st value, bits ``<27:24>``
contain the type for the 2nd value, and bits ``<23:0>`` contain the property ID.
Just make sure the ID is unique across all Widgets.
Note that :cpp:type:`lv_style_prop_t` (enumerator values beginning with ``LV_PROPERTY_STYLE_...``)
are also valid property IDs, and can be used to set or get a Widget's style values.
.. _widget_property_value:
Property Value
--------------
:cpp:type:`lv_property_t` is a struct that begins with an ``id`` field whose meaning
is the same as property ID described above, paired with a value, which is a union of
all possible property types including integer, pointer and color. The ``value``
field is also capable of carrying the different value types for styles. It does this
by being a union of all the different types that might be needed. The list of
"union-ed" fields at this writing are:
.. _single values:
Single Values
~~~~~~~~~~~~~
.. code-block:: c
int32_t num; /**< Signed integer number (enums or "normal" numbers) */
uint32_t num_u; /**< Unsigned integer number (opacity, Booleans) */
bool enable; /**< Booleans */
const void * ptr; /**< Constant pointers (font, cone text, etc.) */
lv_color_t color; /**< Colors */
lv_value_precise_t precise; /**< float or int for precise value */
lv_point_t point; /**< Point, contains two int32_t */
struct {
/**
* Note that place struct member `style` at first place is intended.
* `style` shares same memory with `num`, `ptr`, `color`.
* So we set the style value directly without using `prop.style.num`.
*
* E.g.
*
* static const lv_property_t obj_pos_x = {
* .id = LV_PROPERTY_STYLE_X,
* .num = 123,
* .selector = LV_STATE_PRESSED,
* }
*
* instead of:
* static const lv_property_t obj_pos_x = {
* .id = LV_PROPERTY_STYLE_X,
* .style.num = 123, // note this line.
* .selector = LV_STATE_PRESSED,
* }
*/
lv_style_value_t style; /**< Make sure it's the first element in struct. */
uint32_t selector; /**< Style selector, lv_part_t | lv_state_t */
};
.. _paired values:
Paired Values
~~~~~~~~~~~~~
.. code-block:: c
/**
* For some properties like slider range, it contains two simple (4-byte) values
* so we can use `arg1.num` and `arg2.num` to set the argument.
*/
struct {
union {
int32_t num;
uint32_t num_u;
bool enable;
const void * ptr;
lv_color_t color;
lv_value_precise_t precise;
} arg1, arg2;
};
You can find the current :cpp:type:`lv_property_t` struct in the
`lv_obj_property.h <https://github.com/lvgl/lvgl/blob/master/src/core/lv_obj_property.h>`__ file.
Property ID Lookup by Name
--------------------------
Setting configuration macro :c:macro:`LV_USE_OBJ_PROPERTY_NAME` to ``1`` enables the
following functions to look up property IDs by passing property name (a string):
- :cpp:type:`lv_prop_id_t` :cpp:expr:`lv_obj_property_get_id(widget, name)`
Gets property ID by recursively searching for ``name`` in Widget's class hierarchy,
and if still not found, then searches style properties.
- :cpp:type:`lv_prop_id_t` :cpp:expr:`lv_obj_class_property_get_id(class_p, name)`
Gets property ID by doing a non-recursive search for ``name`` directly in Widget
class properties.
- :cpp:type:`lv_prop_id_t` :cpp:expr:`lv_style_property_get_id(name)`
Gets style property ID by name.
The latter two functions are useful when you already know ``name`` is among the
properties of a specific Widget class, or is a style name, since a property name may
exist in both lists. Because of the search sequence in
:cpp:expr:`lv_obj_property_get_id(widget, name)`, if a name does exist in both lists,
then using this function forces the name in the Widget's class hierarchy properties
to have precedence over the style name.
You can tell which names are available by looking in the ``.c`` files in the
``./src/widgets/property/`` directory. Note that to support binary name searches,
these arrays are generated so that they are guaranteed to be in alphabetical order.
If you need to add a property that is not present, it is recommended to add it in the
``enum`` near the top of the Widget's primary ``.h`` file, and re-generate these
lists using ``./scripts/properties.py`` to ensure alphabetical ordering is preserved.
Additional Notes
****************
For the ``lv_property_t * value`` argument of the :cpp:func:`lv_obj_set_property`
function, the language used to call that function (e.g. in a static or
dynamically-loaded library) may need additional code to convert values from their
local data type (e.g. dict, table, etc.) to a C struct before passing it to the
:cpp:func:`lv_obj_set_property` function.
API
***

View File

@@ -0,0 +1,11 @@
.. _observer:
========
Observer
========
.. toctree::
:maxdepth: 2
observer
observer_examples

View File

@@ -0,0 +1,546 @@
.. _observer_how_to_use:
==========
How to Use
==========
.. _observer_overview:
Overview
********
.. _observer pattern: https://en.wikipedia.org/wiki/Observer_pattern
The ``lv_observer`` module is an implemention of the `Observer Pattern`_.
This implementation consists of:
:Subjects: (in global memory or heap) are "logic packages", each containing the
value being "observed" and its type (integer (``int32_t``), a string, a
pointer, an :cpp:type:`lv_color_t`, or a group);
:Observers: (zero or more per Subject, always dynamically-allocated) are always
attached to exactly one Subject, and provide user-defined notifications
each the time Subject's value changes.
A Subject and its Observers can be used in various ways:
1. Simply subscribe to a Subject and get notified when the Subject's value changes.
2. Subscribe to a group Subject (connects a group of Subjects) to get notified when
any of the Subjects' values change in the group.
3. Bind Widgets to Subjects to automatically match the Widget's value with the
Subject (e.g. a Label's text or an Arc's value).
.. _observer_usage:
Usage
*****
Using Observer first requires :c:macro:`LV_USE_OBSERVER` be configured to ``1``.
(It is ``1`` by default, and can be set to ``0`` to save some program space if you
will not be using Observer.)
A typical use case looks like this:
.. code-block:: c
// Any typical global variable
lv_subject_t my_subject;
/*-------
* main.c
*-------*/
extern lv_subject_t my_subject;
void main(void)
{
// Initialize Subject as integer with the default value of 10.
lv_subject_init_int(&my_subject, 10);
some_module_init();
}
/*--------------
* some_module.c
*--------------*/
extern lv_subject_t some_subject;
// Will be called when Subject's value changes
static void some_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
int32_t v = lv_subject_get_int(subject);
do_something(v);
}
void some_module_init(void)
{
// Subscribe to Subject as an Observer.
lv_subject_add_observer(&some_subject, some_observer_cb, NULL);
}
/*--------------
* some_system.c
*--------------*/
extern lv_subject_t some_subject;
void some_event(void)
{
// The below call sets Subject's value to 30 and notifies current Observers.
lv_subject_set_int(&some_subject, 30);
}
.. _observer_subject:
Subject
-------
Subject Initialization
~~~~~~~~~~~~~~~~~~~~~~
Subjects have to be static or global variables, or dynamically-allocated
:cpp:type:`lv_subject_t` objects. Reason: their content must remain valid through
the life of the Subject.
To initialize a Subject use ``lv_subject_init_<type>(&subject, params, init_value)``.
The following initialization functions exist, one for each of the Subject types:
:Integer: void :cpp:expr:`lv_subject_init_int(subject, int_value)`
:String: void :cpp:expr:`lv_subject_init_string(subject, buf, prev_buf, buf_size, initial_string)`
:Pointer: void :cpp:expr:`lv_subject_init_pointer(subject, ptr)`
:Color: void :cpp:expr:`lv_subject_init_color(subject, color)`
:Group: void :cpp:expr:`lv_subject_init_group(group_subject, subject_list[], count)`
Setting a Subject's Value
~~~~~~~~~~~~~~~~~~~~~~~~~
The following functions are used to update a Subject's value:
:Integer: void :cpp:expr:`lv_subject_set_int(subject, int_value)`
:String: void :cpp:expr:`lv_subject_copy_string(subject, buf)`
:Pointer: void :cpp:expr:`lv_subject_set_pointer(subject, ptr)`
:Color: void :cpp:expr:`lv_subject_set_color(subject, color)`
At the end of each of these calls, if the new value differs from the previous value,
a notification is sent to all current Observers.
Getting a Subject's Value
~~~~~~~~~~~~~~~~~~~~~~~~~
The following functions are used to get a Subject's current value:
:Integer: int32_t :cpp:expr:`lv_subject_get_int(subject)`
:String: const char * :cpp:expr:`lv_subject_get_string(subject)`
:Pointer: const void * :cpp:expr:`lv_subject_get_pointer(subject)`
:Color: lv_color_t :cpp:expr:`lv_subject_get_color(subject)`
Getting a Subject's Previous Value
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following functions are used to get a Subject's previous value:
:Integer: int32_t :cpp:expr:`lv_subject_get_previous_int(subject)`
:String: const char * :cpp:expr:`lv_subject_get_previous_string(subject)`
:Pointer: const void * :cpp:expr:`lv_subject_get_previous_pointer(subject)`
:Color: lv_color_t :cpp:expr:`lv_subject_get_previous_color(subject)`
.. _observer_observer:
Observer
--------
Subscribing to a Subject
~~~~~~~~~~~~~~~~~~~~~~~~
The action of subscribing to a Subject:
- dynamically allocates an Observer object,
- attaches it to the Subject,
- performs an initial notification to the Observer (allowing the Observer to
update itself with the Subject's current value), and
- returns a pointer to the newly-created Observer.
Thereafter the Observer will receive a notification each time the Subject's value
changes, as long as that Observer remains attached (subscribed) to that Subject.
Notifications are performed by calling the callback function provided when
subscribing to the Subject.
Simple Subscription
~~~~~~~~~~~~~~~~~~~
To subscribe to a Subject one of the ``lv_subject_add_observer...()`` functions are
used. Alternately, if you want to bind a Subject's value to a Widget's property, one
of the ``lv_<widget_type>_bind_...()`` functions can be used. The former are covered
below. The latter are covered in the :ref:`observer_widget_binding` section.
For the most basic use case, subscribe to a Subject by using the following function:
lv_observer_t * observer = :cpp:expr:`lv_subject_add_observer(&some_subject, some_observer_cb, user_data)`
where the Observer's notification callback should look like this:
.. code-block:: c
static void some_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
...
}
This function returns a pointer to the newly-created Observer.
When using this method of subscribing, it is the responsibility of the user to call
:cpp:expr:`lv_observer_remove(observer)` when the Observer is no longer needed, which
both unsubscribes it from the Subject and deletes it from the LVGL heap.
Subscribing While Associating Observer with a Non-Widget Object
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The function subscribes to a Subject additionally associates the Observer with a
pointer to any type of object, a copy of which is saved in the Observer's ``target``
field. This function should be used when the pointer *does not* point to a Widget.
lv_observer_t * observer = :cpp:expr:`lv_subject_add_observer_with_target(&some_subject, some_observer_cb, some_pointer, user_data)`
A copy of the passed pointer can be retrieved by calling
:cpp:expr:`lv_observer_get_target(observer)`, e.g. inside the callback function.
When using this method of subscribing, it is the responsibility of the user to call
:cpp:expr:`lv_observer_remove(observer)` when the Observer is no longer needed, which
both unsubscribes it from the Subject and deletes it from the LVGL heap.
Subscribing While Associating Observer with a Widget
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The function below assocates a Widget with an Observer while subscribing to a
Subject. A copy of the pointer to that Widget is saved in the Observer's ``target``
field. This works exactly like the above method except that when the Widget is
deleted, the Observer thus created will be automatically unsubscribed from the
Subject and deleted from the LVGL heap. Note this is different from
:ref:`observer_widget_binding`.
lv_observer_t * observer = :cpp:expr:`lv_subject_add_observer_obj(&some_subject, some_observer_cb, widget, user_data)`
Any number of Observers can be created and be associated with a Widget this way.
A copy of the pointer to the Widget can be retrieved by calling
:cpp:expr:`lv_observer_get_target_obj(observer)`, e.g. inside the callback function.
Note that this function returns the stored pointer as a ``lv_obj_t *`` type, as
opposed to the ``void *`` type returned by
:cpp:expr:`lv_observer_get_target_obj(observer)`.
(:cpp:expr:`lv_observer_get_target(observer)` can still be used if you need that
pointer as a ``void *`` type for any reason, but in practice, this would be rare.)
Unsubscribing from a Subject
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When an Observer and its associated events are no longer needed, you can remove it
from memory using any of the methods below, depending on the effect you want to create:
- If Widget needs to be deleted, simply delete the Widget, which will automatically
gracefully remove the Observer (and its events) from the Subject, while deleting
the Widget.
- If Widget does NOT need to be deleted:
- :cpp:expr:`lv_obj_remove_from_subject(widget, subject)` deletes all Observers
associated with ``widget``. ``subject`` can be NULL to unsubscribe the Widget
from all associated Subjects.
- :cpp:expr:`lv_subject_deinit(subject)` gracefully disconnects ``subject`` from
all associated Observers and Widget events. This includes subscriptions made
using any of the :ref:`observer_widget_binding` functions covered below.
- :cpp:expr:`lv_observer_remove(observer)` deletes that specific Observer and
gracefully disconnects it from its ``subject`` and any associated Widgets,
where ``observer`` is the return value any of the above
``lv_subject_add_observer_...()`` functions.
.. _observer_subject_groups:
Subject Groups
--------------
When something in your system relies on more than one value (i.e. it needs to be
notified when any of a SET of two or more values changes), it can be made an
Observer of a Subject Group.
Let us consider an example of an instrument which measures either voltage or current.
To display the measured value on a label, 3 things are required:
1. What is being measured (current or voltage)?
2. What is the measured value?
3. What is the range or unit ("mV", "V", "mA", "A")?
When any of these 3 input values change, the label needs to be updated, and it needs
to know all 3 values to compose its text.
To handle this you can create an array from the addresses of all the Subjects that
are relied upon, and pass that array as a parameter when you initialize a Subject
with GROUP type.
.. code-block:: c
static lv_subject_t * subject_list[3] = {&subject_1, &subject_2, &subject_3};
lv_subject_init_group(&subject_all, subject_list, 3); /* Last argument is number of elements. */
Observers are then added to Subject Groups (e.g. ``subject_all``) in the usual way.
When this is done, a change to the value of any of the Subjects in the group triggers
a notification to all Observers subscribed to the Subject Group (e.g. ``subject_all``).
As an example, the above scenario with Voltage/Current measurement might look like this:
.. code-block:: c
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_subject_t subject_mode; // Voltage or Current
lv_subject_t subject_value; // Measured value
lv_subject_t subject_unit; // The unit
lv_subject_t subject_all; // Subject group that connects the above 3 Subjects
lv_subject_t * subject_list[3] = {&subject_mode, &subject_value, &subject_unit}; // The elements of the group
lv_subject_init_int(&subject_mode, 0); // Let's say 0 is Voltage, 1 is Current
lv_subject_init_int(&subject_value, 0);
lv_subject_init_pointer(&subject_unit, "V");
lv_subject_init_group(&subject_all, subject_list, 3);
lv_subject_add_observer_obj(&subject_all, all_observer_cb, label, NULL);
...
static void all_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
lv_obj_t * label = lv_observer_get_target(observer);
lv_subject_t * subject_mode = lv_subject_get_group_element(subject, 0);
lv_subject_t * subject_value = lv_subject_get_group_element(subject, 1);
lv_subject_t * subject_unit = lv_subject_get_group_element(subject, 2);
int32_t mode = lv_subject_get_int(subject_mode);
int32_t value = lv_subject_get_int(subject_value);
const char * unit = lv_subject_get_pointer(subject_unit);
lv_label_set_text_fmt(label, "%s: %d %s", mode ? "Current" : "Voltage", value, unit);
}
.. _observer_widget_binding:
Widget Binding
--------------
The following methods of subscribing to an integer-type Subject associate the
Observer with ONE of a Widget's properties as thought that property itself were the
Observer. Any of the following Widget properties can be thus bound to an Subject's
integer value:
- flag (or OR-ed combination of flags) from from the ``LV_OBJ_FLAG_...`` enumeration values;
- state (or OR-ed combination of states) from the ``LV_STATE_...`` enumeration values;
- text value for Label Widgets;
- integer value for these Widget types:
- Arc
- Drop-Down
- Roller
- Slider
Any number of Observers can be created for a single Widget, each bound to ONE of
the above properties.
For all of the ``lv_..._bind_...()`` functions covered below, they are similar to
:cpp:expr:`lv_subject_add_observer_obj(&some_subject, some_observer_cb, widget, user_data)`
in that they create an Observer and associates the Widget with it. What is different
is that updates to the Widget's property thus bound are handled internally -- the
user *does not supply callback functions* for any of these subscribing methods -- the
callback methods are supplied by the Observer subsystem.
.. note::
While the examples below show saving a reference to the created Observer objects
for the various ``lv_..._bind_...()`` functions, it is not necessary to do so
unless you need them for some purpose, because the created Observer objects will
be automatically deleted when the Widget is deleted.
Any Widget Type
~~~~~~~~~~~~~~~
Flags
^^^^^
The following methods of subscribing to an integer Subject affect a Widget's flag (or
OR-ed combination of flags). When the subscribing occurs, and each time the
Subject's value is changed thereafter, the Subject's value is compared with the
specified reference value, and the specified flag(s) is (are):
- SET when the Subject's integer value fulfills the indicated condition, and
- CLEARED otherwise.
Here are the functions that carry out this method of subscribing to a Subject. The
``flags`` argument can contain a single, or bit-wise OR-ed combination of any of the
``LV_OBJ_FLAG_...`` enumeration values.
:equal: :cpp:expr:`lv_obj_bind_flag_if_eq(widget, &subject, flags, ref_value)`
:not equal: :cpp:expr:`lv_obj_bind_flag_if_not_eq(widget, &subject, flags, ref_value)`
:greater than: :cpp:expr:`lv_obj_bind_flag_if_gt(widget, &subject, flags, ref_value)`
:greater than or equal: :cpp:expr:`lv_obj_bind_flag_if_ge(widget, &subject, flags, ref_value)`
:less than: :cpp:expr:`lv_obj_bind_flag_if_lt(widget, &subject, flags, ref_value)`
:less than or equal: :cpp:expr:`lv_obj_bind_flag_if_le(widget, &subject, flags, ref_value)`
States
^^^^^^
The following methods of subscribing to an integer Subject affect a Widget's states
(or OR-ed combination of states). When the subscribing occurs, and each time the
Subject's value is changed thereafter, the Subject's value is compared with the
specified reference value, and the specified state(s) is (are):
- SET when the Subject's integer value fulfills the indicated condition, and
- CLEARED otherwise.
Here are the functions that carry out this method of subscribing to a Subject. The
``states`` argument can contain a single, or bit-wise OR-ed combination of any of the
``LV_STATE_...`` enumeration values.
:equal: :cpp:expr:`lv_obj_bind_state_if_eq(widget, &subject, states, ref_value)`
:not equal: :cpp:expr:`lv_obj_bind_state_if_not_eq(widget, &subject, states, ref_value)`
:greater than: :cpp:expr:`lv_obj_bind_state_if_gt(widget, &subject, states, ref_value)`
:greater than or equal: :cpp:expr:`lv_obj_bind_state_if_ge(widget, &subject, states, ref_value)`
:less than: :cpp:expr:`lv_obj_bind_state_if_lt(widget, &subject, states, ref_value)`
:less than or equal: :cpp:expr:`lv_obj_bind_state_if_le(widget, &subject, states, ref_value)`
Checked State
^^^^^^^^^^^^^
The following method of subscribing to an integer Subject affects a Widget's
:cpp:enumerator:`LV_STATE_CHECKED` state. When the subscribing occurs, and each time
the Subject's value is changed thereafter, the Subject's value is compared to a
reference value of ``0``, and the :cpp:enumerator:`LV_STATE_CHECKED` state is:
- CLEARED when the Subject's value is 0, and
- SET when the Subject's integer value is non-zero.
Note that this is a two-way binding (Subject <===> Widget) so direct (or
programmatic) interaction with the Widget that causes its
:cpp:enumerator:`LV_STATE_CHECKED` state to be SET or CLEARED also causes the
Subject's value to be set to ``1`` or ``0`` respectively.
- :cpp:expr:`lv_obj_bind_checked(widget, &subject)`
Label Widgets
~~~~~~~~~~~~~
.. |deg| unicode:: U+000B0 .. DEGREE SIGN
This method of subscribing to an integer Subject affects a Label Widget's
``text``. The Subject can be an STRING, POINTER or INTEGER type.
When the subscribing occurs, and each time the Subject's value is changed thereafter,
the Subject's value is used to update the Label's text as follows:
:string Subject: Subject's string is used to directly update the Label's text.
:pointer Subject: If NULL is passed as the ``format_string`` argument when
subscribing, the Subject's pointer value is assumed to point to a
NUL-terminated string. and is used to directly update the Label's
text. See :ref:`observer_format_string` for other options.
:integer Subject: Subject's integer value is used with the ``format_string`` argument.
See See :ref:`observer_format_string` for details.
Note that this is a one-way binding (Subject ===> Widget).
- :cpp:expr:`lv_label_bind_text(label, &subject, format_string)`
.. _observer_format_string:
The ``format_string`` Argument
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``format_string`` argument is optional and if provided, must contain exactly 1
printf-like format specifier and be one of the following:
:string or pointer Subject: "%s" to format the new pointer value as a string or "%p"
to format the pointer as a pointer (typically the
pointer's address value is spelled out with 4, 8 or 16
hexadecimal characters depending on the platform).
:integer Subject: "%d" format specifier (``"%" PRIdxx`` --- a
cross-platform equivalent where ``xx`` can be ``8``,
``16``, ``32`` or ``64``, depending on the platform).
If NULL is passed for the ``format_string`` argument:
:string or pointer Subject: Updates expect the pointer to point to a NUL-terminated string.
:integer Subject: The Label will display an empty string (i.e. nothing).
**Example:** "%d |deg|\C"
Arc Widgets
~~~~~~~~~~~
This method of subscribing to an integer Subject affects an Arc Widget's integer
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
user's direct interaction with the Arc Widget updates the Subject's value and vice
versa. (Requires :c:macro:`LV_USE_ARC` to be configured to ``1``.)
- :cpp:expr:`lv_arc_bind_value(arc, &subject)`
Slider Widgets
~~~~~~~~~~~~~~
This method of subscribing to an integer Subject affects a Slider Widget's integer
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
user's direct interaction with the Slider Widget updates the Subject's value and vice
versa. (Requires :c:macro:`LV_USE_SLIDER` to be configured to ``1``.)
- :cpp:expr:`lv_slider_bind_value(slider, &subject)`
Roller Widgets
~~~~~~~~~~~~~~
This method of subscribing to an integer Subject affects a Roller Widget's integer
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
user's direct interaction with the Slider Widget updates the Subject's value and vice
versa. (Requires :c:macro:`LV_USE_ROLLER` to be configured to ``1``.)
- :cpp:expr:`lv_roller_bind_value(roller, &subject)`
Drop-Down Widgets
~~~~~~~~~~~~~~~~~
This method of subscribing to an integer Subject affects a Drop-Down Widget's integer
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
user's direct interaction with the Drop-Down Widget updates the Subject's value and
vice versa. (Requires :c:macro:`LV_USE_DROPDOWN` to be configured to ``1``.)
- :cpp:expr:`lv_dropdown_bind_value(dropdown, &subject)`
.. _observer_api:
API
***

View File

@@ -0,0 +1,8 @@
.. _observer examples:
========
Examples
========
.. include:: ../../../examples/others/observer/index.rst

View File

@@ -0,0 +1,86 @@
.. _snapshot:
========
Snapshot
========
Snapshot provides an API to take a snapshot image for an LVGL Widget together
with its children. The image will look exactly like the Widget on the display.
.. _snapshot_usage:
Usage
*****
Simply call function :cpp:expr:`lv_snapshot_take(widget, color_format)` to generate
the image descriptor which can be used as an Image Widget's image source using
:cpp:func:`lv_image_set_src`.
Note, only following color formats are supported at this time:
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB565`
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB888`
- :cpp:enumerator:`LV_COLOR_FORMAT_XRGB8888`
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8888`
Freeing the Image
-----------------
The memory :cpp:func:`lv_snapshot_take` uses is dynamically allocated using
:cpp:func:`lv_draw_buf_create`. Use :cpp:func:`lv_draw_buf_destroy` to free the
memory it allocated.
The snapshot image which is the draw buffer returned by :cpp:func:`lv_snapshot_take`
normally won't be added to the cache because it can be drawn directly. So you don't need
to invalidate the cache by calling :cpp:func:`lv_image_cache_drop` before destroying
the draw buffer.
The below code snippet demonstrates correct use of :cpp:func:`lv_snapshot_take`:
.. code-block:: c
void update_snapshot(lv_obj_t * widget, lv_obj_t * img_snapshot)
{
lv_draw_buf_t* snapshot = (void*)lv_image_get_src(img_snapshot);
if(snapshot) {
lv_draw_buf_destroy(snapshot);
}
snapshot = lv_snapshot_take(widget, LV_COLOR_FORMAT_ARGB8888);
lv_image_set_src(img_snapshot, snapshot);
}
Using an Existing Buffer
------------------------
If the snapshot needs to be updated repeatedly, or if the caller provides the draw
buffer, use :cpp:expr:`lv_snapshot_take_to_draw_buf(widget, color_format, draw_buf)`.
In this case, the caller is responsible for creating and destroying the draw buffer.
If snapshot is generated successfully, the image descriptor is updated,
the image data will be stored to the provided ``draw_buf``, and the function will
return :cpp:enumerator:`LV_RESULT_OK`.
Note that snapshot may fail if the provided buffer is not large enough, which can
happen if the Widget's size changes. It's recommended to use
:cpp:expr:`lv_snapshot_reshape_draw_buf(widget, draw_buf)` to first ensure the buffer
is large enough, and if it fails, destroy the existing draw buffer and call
`lv_snapshot_take` directly.
.. _snapshot_example:
Example
*******
.. include:: ../../examples/others/snapshot/index.rst
.. _snapshot_api:
API
***

View File

@@ -0,0 +1,163 @@
.. _test:
==========
UI Testing
==========
Overview
********
The Test module provides functions to emulate clicks, key presses, encoder turns, time passing, and
compare the UI with reference images.
These functions can be easily used in any test framework (such as Unity, GoogleTest, etc.), and
assertions can be performed to check if, for example:
- A widget's value is different from the expected value after emulating user inputs.
- The values are incorrect after some time has passed.
- The screen's content is different from the reference image.
- Some event functions are not triggered.
- Etc.
Note that it is assumed the tests are performed on a desktop or server environment,
where there are no memory constraints.
Usage
*****
The Test module can be enabled by configuring ``LV_USE_TEST`` to a non-zero value,
and it consists of the following components:
- Helpers
- Display emulation
- Input device emulation
- Screenshot comparison
Helpers
-------
Time
~~~~
To emulate elapsed time, two functions can be used:
1. :cpp:expr:`lv_test_wait(ms)`: Emulates that ``ms`` milliseconds have elapsed, but it also calls ``lv_timer_handler`` after each millisecond.
This is useful to check if events (e.g., long press, long press repeat) and timers were triggered correctly over time.
2. :cpp:expr:`lv_test_fast_forward(ms)`: Jumps ``ms`` milliseconds ahead and calls ``lv_timer_handler`` only once at the end.
:cpp:expr:`lv_refr_now(NULL)` is called at the end of both functions to ensure that animations and
widget coordinates are recalculated.
:cpp:expr:`lv_refr_now(NULL)` can also be called manually to force LVGL to refresh the emulated display.
Memory Usage
~~~~~~~~~~~~
If ``LV_USE_STDLIB_MALLOC`` is set to ``LV_STDLIB_BUILTIN``, memory usage and memory leaks can be monitored.
.. code-block:: c
size_t mem1 = lv_test_get_free_mem();
<create and delete items>
size_t mem2 = lv_test_get_free_mem();
if(mem1 != mem2) fail();
It might make sense to create and delete items in a loop many times and add a small tolerance
to the memory leakage test. This might be needed due to potential memory fragmentation. Empirically,
a tolerance of 32 bytes is recommended.
.. code-block:: c
if(LV_ABS((int64_t)mem2 - (int64_t)mem1) > 32) fail();
Display Emulation
-----------------
By calling :cpp:expr:`lv_test_display_create(hor_res, ver_res)`, a dummy display can be created.
It functions like any other normal display, but its content exists only in memory.
When creating this display, the horizontal and vertical resolutions must be passed. Internally,
a framebuffer will be allocated for this size, and ``XRGB8888`` color format will be set.
The resolution and color format can be changed at any time by calling :cpp:func:`lv_display_set_resolution` and
:cpp:func:`lv_display_set_color_format`.
Input Device Emulation
----------------------
By calling :cpp:func:`lv_test_indev_create_all`, three test input devices will be created:
1. A pointer (for touch or mouse)
2. A keypad
3. An encoder
For example, this is how a scroll gesture can be emulated:
.. code-block:: c
lv_test_mouse_move_to(20, 30);
lv_test_mouse_press();
lv_test_wait(20);
lv_test_mouse_move_by(0, 100);
lv_test_wait(20);
lv_test_mouse_release();
lv_test_wait(20);
It is recommended to add :cpp:func:`lv_test_wait` after user actions to ensure that
the new state and coordinates are read and applied from the input device.
After that, the user can check if the given widget was really scrolled
by getting the Y coordinate of a child.
.. code-block:: c
int32_t y_start = lv_obj_get_y(child);
<scroll emulation>
int32_t y_end = lv_obj_get_y(child);
if(y_start + 100 != y_end) fail();
Please refer to :ref:`lv_test_indev_h` for the list of supported input device emulation functions.
Screenshot Comparison
---------------------
``bool lv_test_screenshot_compare(const char * fn_ref)`` is a useful function
to compare the content of the emulated display with reference PNG images.
This function works in a practical way:
- If the folder(s) referenced in ``fn_ref`` do not exist, they will be created automatically.
- If the reference image is not found, it will be created automatically from the rendered screen.
- If the comparison fails, an ``<image_name>_err.png`` file will be created with the rendered content next to the reference image.
- If the comparison fails, the X and Y coordinates of the first divergent pixel, along with the actual and expected colors, will also be printed.
The reference PNG images should have a **32-bit color format** and match the display size.
The test display's content will be converted to ``XRGB8888`` to simplify comparison with the reference images.
The conversion is supported from the following formats (i.e., the test display should have a color
format in this list):
- :cpp:enumerator:`LV_COLOR_FORMAT_XRGB8888`
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8888`
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8888_PREMULTIPLIED`
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB888`
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB565`
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB565_SWAPPED`
- :cpp:enumerator:`LV_COLOR_FORMAT_L8`
- :cpp:enumerator:`LV_COLOR_FORMAT_AL88`
- :cpp:enumerator:`LV_COLOR_FORMAT_I1`
To read and decode PNG images and to store the converted rendered image, a few MBs of RAM are dynamically allocated using the standard ``malloc``
(not :cpp:expr:`lv_malloc`).
The screenshot comparison uses `lodepng` which is built-in to LVGL and just needs to be enabled with
``LV_USE_LODEPNG``.
To avoid making the entire Test module dependent on `lodepng`, screenshot comparison can be individually enabled by
``LV_USE_TEST_SCREENSHOT_COMPARE``.
API
***

View File

@@ -0,0 +1,7 @@
.. _xml_animations:
==========
Animations
==========
TODO

View File

@@ -0,0 +1,285 @@
.. _xml_api:
===
API
===
The ``<api>`` tag can be a child of ``<widget>`` and ``<components>`` tags, although
each supports slightly different features.
Properties
**********
Inside ``<prop>`` elements, ``<param>`` elements can be defined to describe the arguments.
For **Widgets**, all properties are optional.
If a property is not set on an instance of a Widget, it simply won't be applied,
and the created Widget's default value for that property will be used (e.g., ``text``
for a label's text).
For **Components**, all properties are mandatory; however, default values can be defined
to be used when a property is not set.
If a property has only one parameter (which is usually the case), a shorthand syntax
can be applied as shown below.
For example:
.. code-block:: xml
<api>
<prop name="range" default="0 100" help="Set the range.">
<param name="range_min" type="int" help="Sets the minimum value."/>
<param name="range_max" type="int" help="Sets the maximum value."/>
</prop>
<prop name="title" type="string" help="The title of the slider"/>
</api>
When a property is used, all parameters are set as a single attribute value. For example:
.. code-block:: xml
<my_slider range="-100 100" title="Room 1"/>
For **Widgets**, each property corresponds to a setter function.
The ``name`` in ``<prop>`` is used to build the name of the setter function like this:
.. code-block:: c
<widget_name>_set_<prop_name>(lv_obj_t * obj, <param1_type> <param1_name>, <param2_type> <param2_name>, ...);
For **Components**, the exported code contains only a single ``create`` function
to which all the properties are passed:
.. code-block:: c
<component_name>_create(lv_obj_t * parent, <param1_type> <param1_name>, <param2_type> <param2_name>, ...);
``<prop>`` elements have an optional ``<postponed>`` boolean attribute.
By default, it is ``false``, but if set to ``true``, the given property will be
applied after all children are created. A practical example is setting the current
tab of a tab view, which cannot be set before the tabs are created. This feature is
not supported yet.
``<enumdef>``
*************
This tag is used only with Widgets. It is used to define new enum types for a given
Widget. It should contain ``<enum>`` elements to define possible options.
Example:
.. code-block:: xml
<!-- my_widget.xml -->
<api>
<enumdef name="my_widget_mode" help="Possible modes">
<enum name="normal" help="Normal mode" value="0x10"/>
<enum name="inverted" help="Inverted mode"/>
</enumdef>
<prop name="mode" help="Set Widget mode">
<param name="mode" type="enum:my_widget_mode"/>
</prop>
</api>
Note that the enum values are not important because:
1. When the code is exported, the enum names will be used, and the compiler generates
its own value for each enumerator symbol.
2. When loaded from XML, the Widget's XML parser should convert the enum names to C
enum fields.
``<element>``
*************
``<element>`` tags also apply only to Widgets. Elements are used to describe
sub-Widgets or internal parts of Widgets. Examples include the list of a dropdown,
the tabs of a tab view, or the data series of a chart.
Elements can have ``<arg>`` and ``<prop>`` definitions. ``<arg>`` elements are
mandatory (default values are supported) as they are used to create the element,
whereas ``<prop>`` elements are optional as they are mapped to setter functions.
An element in a ``<view>`` can be referenced like this: ``<widget_name-element_name>``.
Note that the ``-`` separates two names inside that tag name: the Widget name and the
element name. ``-`` is not allowed in Widget and element names. Only ``_`` can be
used to separate words in tag names.
Example:
.. code-block:: xml
<my_chart-super_series color="0xff0000"/>
An important attribute of elements is ``access``. The possible values are:
- ``add``: Create any number of elements dynamically (e.g., chart series).
- ``get``: Get a pointer to an implicitly created Widget or any data (e.g., list of a Drop-Down List).
- ``set``: Select specific parts of the Widget with indexes (e.g., table cells).
Elements with ``access="add"`` or ``access="get"`` can have a custom data type
defined using ``type="my_data"``. In these cases, no children can be added. If the
``type`` is ``lv_obj``, the element can have children.
It is not yet possible to describe the ``<view>`` of elements in XML; only the API can be defined.
The actual implementation needs to be done in C.
``access="add"``
----------------
The element is explicitly created with an ``add`` function, e.g., ``lv_tabview_add_tab(obj, "Title");``.
``<arg>`` elements defined as direct children of the ``<element>`` are passed to the
``add`` function as arguments.
Example:
.. code-block:: xml
<!-- my_widget.xml -->
<api>
<element name="indicator" type="lv_obj" help="The indicator of my_widget" access="add">
<arg name="color" type="color" help="Help for color"/>
<arg name="max_value" type="int" help="Help for max_value"/>
<prop name="value" help="Set a new value for the indicator">
<param name="value" type="int" help="Help for value"/>
</prop>
</element>
</api>
<view extends="obj">
<button name="btn1"/>
</view>
In a view it can be used like this:
.. code-block:: xml
<!-- complex_widget.xml -->
<view>
<lv_label text="Title"/>
<my_widget width="100px" y="40px">
<my_widget-indicator name="indic1" color="0xff0000" max_value="120" value="30"/>
</my_widget>
</view>
From the API definition the following functions are generated:
.. code-block:: c
lv_obj_t * my_widget_add_indicator(lv_obj_t * parent, lv_color_t color, int32_t max_value);
void my_widget_set_indicator_value(lv_obj_t * obj, int32_t value);
And this is the related C file where the indicator is created:
.. code-block:: c
lv_obj_t * indic1 = my_widget_add_indicator(parent, color, max_value);
lv_my_widget_set_indicator_value(indic1, value);
``access="get"``
----------------
If the element is created internally and implicitly, it can be retrieved with a
function like ``lv_dropdown_get_list(obj);``.
``<arg>`` elements are passed to the ``get`` function as arguments.
Example:
.. code-block:: xml
<!-- my_widget.xml -->
<api>
<element name="control_button" type="lv_obj" help="A control button of my_widget" access="get">
<arg name="index" type="int" help="Zero-based index of the control button"/>
<prop name="title">
<param name="text" type="string"/>
</prop>
</element>
</api>
In a view:
.. code-block:: xml
<!-- complex_widget.xml -->
<view>
<my_widget width="100px">
<my_widget-control_button name="btn1" index="3" title="Hello"/>
</my_widget>
</view>
Generated API:
.. code-block:: c
lv_obj_t * my_widget_get_control_button(lv_obj_t * parent, int32_t index);
void my_widget_set_control_button_title(lv_obj_t * obj, const char * text);
And this is a C file where the control button is retrieved:
.. code-block:: c
lv_obj_t * btn1 = lvmy_widget_get_control_button(parent, index);
my_widget_set_control_button_title(btn1, text);
``access="set"``
----------------
The "set" value is used when elements are created automatically but need to be selected in API calls,
e.g., ``lv_table_set_cell_value(table, row, col, "text");``.
Example:
.. code-block:: xml
<!-- my_widget.xml -->
<api>
<element name="item" type="lv_obj" help="An item on my_widget" access="set">
<arg name="index" type="int" help="The zero-based index of the item"/>
<prop name="icon" help="Set the icon of an item">
<param name="icon_src" type="img_src" help="The image to set as an icon."/>
</prop>
<prop name="color" help="Set the color">
<param name="color" type="color" help="The color to set for the item."/>
</prop>
</element>
</api>
In a view:
.. code-block:: xml
<!-- complex_widget.xml -->
<view>
<my_widget width="100px">
<my_widget-item index="3" icon_src="image1" color="0xff0000"/>
</my_widget>
</view>
This is the generated header file:
.. code-block:: c
void my_widget_set_item_icon(lv_obj_t * parent, int32_t index, const void * icon_src);
void my_widget_set_item_color(lv_obj_t * parent, int32_t index, lv_color_t color);
And this is the related C file where the item properties are set:
.. code-block:: c
my_widget_set_item_icon(parent, index, image1);
my_widget_set_item_color(parent, index, color);

View File

@@ -0,0 +1,189 @@
.. _xml_component_library:
=================
Component Library
=================
Overview
********
.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE
:trim:
The collection of Components, Widgets, Screens, Images, Fonts, etc., is called a
Component Library.
A Component Library can be fully self-sufficient, but it can also reference data from
other Component Libraries.
LVGL itself is a Component Library that supplies the built-in Widgets, data types,
etc., so typically Component Libraries use at least the core LVGL data. You can
find the XML files that describe the LVGL Widgets
`here <https://github.com/lvgl/lvgl/tree/master/xmls>`__.
A project always has at least 2 Component Libraries: that of LVGL as mentioned
above, and its own where the Screens, Components, and Widgets of the project are
defined. A project may include additional Component Libraries.
Structure
*********
A typical structure for a Component Library looks like this:
.. code-block:: none
name_of_the_component_library
├── globals.xml
├── components
│ ├── component1.xml
│ ├── component2.xml
│ └── other_folder
│ ├── component3.xml
│ └── component4.xml
├── widgets
│ ├── widget1
│ │ ├── widget1.xml
│ │ ├── widget1.c
│ │ ├── widget1.h
│ │ ├── widget1_gen.c
│ │ ├── widget1_gen.h
│ │ ├── widget1_private_gen.h
│ │ └── widget1_xml_parser.c
│ └── widget2
│ └── ...same as widget1...
├── screens
│ ├── screen1.xml
│ └── screen2.xml
├── fonts
│ ├── font1.ttf
│ └── font2.ttf
└── images
├── image1.png
└── image2.png
Visibility
**********
The content of all ``globals.xml`` files is part of a common global scope, and
any Components, Widgets or Screens defined therein can be used in all .XML files.
Styles, constants, and other data defined in the XML file of Components, Widgets, or Screens
are local to that XML file.
Thus, there are two namespaces:
1. **local namespace** within the given XML file of Components, Widgets and Screens.
2. **global namespace** created from the data in the ``globals.xml`` file from each
Component Library included.
To find names referenced in XML files, the local namespace is checked first, and if
a name is not found there, then the global namespace is checked.
The names of defined Components, Widgets and Screens become part of the global
namespace and must be unique therein. This ensures that each Component has a unique
name.
All data belonging to the LVGL core Component Library is prefixed by ``lv_``
(e.g., ``lv_label``, ``lv_font_default``).
A custom Component can be prefixed with ``watch_``, ``small_``, ``light_``, or
anything else the developer deems appropriate.
LVGL's UI |nbsp| Editor will show an error if there is a name conflict.
``globals.xml``
***************
A single ``globals.xml`` file should be created in the root directory of each
Component Library. The definitions in it do not belong to any specific Widget but
are available throughout the entire UI, Widgets, and all XML files. The valid tags
in it are:
:<config>: Can specify name and help.
:<api>: Used with ``<enumdefs>`` to show possible values for Widget or Component attributes.
:<subjects>: List of :ref:`Subjects <observer_subject>`. Can be considered the API of a Component Library.
:<consts>: Globally available constants.
:<styles>: Globally available styles.
:<fonts>: Globally available fonts.
:<images>: Globally available images.
:<const_variants>: See below.
:<style_variants>: See below.
The ``globals.xml`` must be the only ``globals.xml`` file within the Component
Library's folder tree.
From each ``globals.xml`` file, a ``<config.name>.h`` file is generated, which is
included by all generated header files --- not only in the sub-folders where
``globals.xml`` is created, but in all exported .C and .H files. This ensures that
constants, fonts, and other global data are available for all Widgets and new Widgets.
Variants
--------
``<const_variant>`` can be used by constants to create variants that can be selected at compile time.
This can be used to select a different display size, color scheme, etc.
``<style_variant>`` can be used by styles only, to modify styles at runtime. To
select the current style variant, an integer :ref:`Subject <observer_subject>` (i.e.
a Subject containing an integer value in the Observer Pattern implemented in
``lv_observer.c/.h``) ``<style_variant.name>_variant`` is created. Styles can
subscribe to this, and the style properties can be changed according to the selected
variant's integer value. (See `observer` for details about how to do this.)
All possible variants should be defined in ``globals.xml``.
Example
-------
A ``globals.xml`` file of a Component Library might look like this:
.. code-block:: xml
<globals>
<config name="mylib" help="This is my great Component Library"/>
<const_variants>
<const_variant name="size" help="Select the size">
<case name="small" help="Assets for 320x240 Screen"/>
<case name="large" help="Assets for 1280x768 Screen"/>
</const_variant>
</const_variants>
<style_variants>
<style_variant name="color" help="Select the color of the UI">
<case name="red" help="Select a red theme"/>
<case name="blue" help="Select a blue theme"/>
</style_variant>
</style_variants>
<api>
<enumdef name="mode">
<enum name="slow"/>
<enum name="fast"/>
</enumdef>
</api>
<consts>
<px name="small_unit" value="8"/>
<px name="large_unit" value="16"/>
</consts>
<styles>
<style name="card" bg_color="0xeee" radius="#small_unit" padding="12px"/>
</styles>
<images>
<file name="arrow_left" src="A:/images/arrow_left.png"/>
</images>
<fonts>
<tiny_ttf name="big" src="A:/fonts/arial.ttf" size="28"/>
</fonts>
</globals>

View File

@@ -0,0 +1,163 @@
.. _xml_components:
==========
Components
==========
Overview
********
``<component>`` the following child elements:
- ``<consts>``,
- ``<api>``,
- ``<styles>``, and
- ``<view>``.
Unlike Widgets (which are always compiled into the application), Components can either:
1. be loaded at runtime from XML, or
2. be exported to C code and compiled with the application.
Usage from Exported Code
************************
From each Component XML file, a C and H file is exported with a single function inside:
.. code-block:: c
lv_obj_t * component_name_create(lv_obj_t * parent, ...api properties...);
where 'component_name' (in the function above) is replaced by the Component's XML
file name.
When a Component is used in another Component's XML code and the code is exported,
this ``create`` function will be called. This means that Components do not have a
detailed set/get API but can be created with a fixed set of parameters.
If the user needs to access or modify values dynamically, it is recommended to use a
:ref:`Subject <observer_subject>`.
The user can also call these ``..._create()`` functions at any time from application code.
Usage from XML
**************
Registration
------------
Once a Component is created (e.g., ``my_button``), it can be registered by calling either:
- ``lv_xml_component_register_from_file("A:lvgl/examples/others/xml/my_button.xml");``
- ``lv_xml_component_register_from_data("my_button", xml_data_of_my_button);``
These registration functions process the XML data and store relevant information internally.
This is required to make LVGL recognize the Component by name.
Note that the "A:" in the above path is a file system "driver identifier letter" from
:ref:`file_system` and used accordingly. See that documentation for details.
When loaded from a file, the file name is used as the Component name.
Instantiation
-------------
After registration, a new instance of any registered Component can be created with:
.. code-block:: c
lv_obj_t * obj = lv_xml_create(lv_screen_active(), "my_button", NULL);
The created Widget is a normal LVGL Widget that can be used like any other manually-created Widget.
The last parameter can be ``NULL`` or an attribute list, like this:
.. code-block:: c
/* Can be local */
char * my_button_attrs[] = {
"x", "10",
"y", "-10",
"align", "bottom_left",
"btn_text", "New button",
NULL, NULL,
};
lv_obj_t * btn1 = lv_xml_create(lv_screen_active(), "my_button", my_button_attrs);
Parameters
**********
The properties of child elements can be adjusted, such as:
.. code-block:: xml
<lv_label x="10" text="Hello"/>
These parameters can be set to a fixed value. However, with the help of ``<prop>``
elements inside the ``<api>`` tag, they can also be passed when an instance is
created. Only the whole property can be passed, but not individual ``<param>``
elements.
Additionally, when a Component is created, it can use the extended Widget's attributes
(see ``<view extends="...">`` in the code examples below).
This means that Components inherit the API of the extended Widget as well.
The following example demonstrates parameter passing and the use of the
``text`` label property on a Component:
.. code-block:: xml
<!-- h3.xml -->
<component>
<view extends="lv_label"/>
</component>
.. code-block:: xml
<!-- red_button.xml -->
<component>
<api>
<prop type="string" name="btn_text" default="None"/>
</api>
<view extends="lv_button" style_radius="0" style_bg_color="0xff0000">
<h3 text="Some text"/>
<h3 text="$btn_text" y="40"/>
</view>
</component>
.. code-block:: c
lv_xml_component_register_from_file("A:path/to/h3.xml");
lv_xml_component_register_from_file("A:path/to/red_button.xml");
/* Creates a button with "None" text */
lv_xml_create(lv_screen_active(), "red_button", NULL);
/* Use attributes to set the button text */
const char * attrs[] = {
"btn_text", "Click here",
NULL, NULL,
};
lv_xml_create(lv_screen_active(), "red_button", attrs);
Example
*******
.. include:: ../../../examples/others/xml/index.rst
API
***

View File

@@ -0,0 +1,73 @@
.. _xml_consts:
=========
Constants
=========
Overview
********
Constants can be defined to replace any value with a selected type or to be used as special values.
The supported types are:
- ``color``
- ``px``
- ``percentage``
- ``string``
- ``opa``
- ``bool``
Usage
*****
.. code-block:: xml
<consts>
<color name="color1" value="0xff0000" help="Primary color"/>
<px name="pad_xs" value="8" help="Small padding"/>
</consts>
Constants can be used in:
- Style properties
- Widget and Component properties
And they can be used like this:
.. code-block:: xml
<styles>
<style name="style1" bg_color="#color1"/>
</styles>
Variants
********
Constants support a ``<variant>`` attribute to change the constants at compile time. For example:
.. code-block:: xml
<consts>
<px name="pad" value="8" help="General padding">
<variant name="size" case="small" value="4"/>
<variant name="size" case="large" value="12"/>
</px>
</consts>
from which the following C code can be exported:
.. code-block:: c
#if SIZE == SMALL
#define PAD 4
#elif SIZE == LARGE
#define PAD 12
#else
#define PAD 8
#endif
Where ``SMALL`` and ``LARGE`` are just preprocessor defines with incremental values.

View File

@@ -0,0 +1,59 @@
.. _xml_events:
======
Events
======
Overview
********
``<lv_event>`` tags can be added as a child of any Widget to react to user inputs or other value changes.
Right now, only a single event type is supported to call user-defined callbacks.
Usage
*****
Call function
-------------
User-defined functions can be called like this:
.. code-block:: xml
<view>
<lv_button width="200" height="100">
<lv_event-call_function callback="my_callback_1" trigger="clicked" user_data="some_text"/>
<lv_label text="Hello"/>
</lv_button>
</view>
When the XML is loaded at runtime, the callback name needs to be mapped to a function by using:
:cpp:expr:`lv_xml_register_event_cb("my_callback_1", an_event_handler)`.
The callback should have the standard LVGL event callback signature:
``void an_event_handler(lv_event_t * e);``
In the exported C code, it is assumed that there is a function with the exact name
specified as the callback name. For example, ``callback="my_callback_1"`` will be
exported as:
.. code-block:: c
void my_callback_1(lv_event_t * e); /* At the beginning of the exported file */
lv_obj_add_event_cb(obj, my_callback_1, LV_EVENT_CLICKED, "some_text");
For triggers, all LVGL event types are supported with straightforward mapping:
.. What is a trigger?
- :cpp:enumerator:`LV_EVENT_ALL`: ``"all"``
- :cpp:enumerator:`LV_EVENT_CLICKED`: ``"clicked"``
- :cpp:enumerator:`LV_EVENT_PRESSED`: ``"pressed"``
- etc.
The ``user_data`` is optional. If omitted, ``NULL`` will be passed.

View File

@@ -0,0 +1,109 @@
.. _xml_fonts:
=====
Fonts
=====
Overview
********
A ``<fonts>`` section can be added in ``globals.xml`` files.
Later, it might be supported in Components and Widgets to define local fonts and keep
the global space cleaner.
Usage
*****
The following section creates a mapping between font names and their paths with various attributes:
.. code-block:: xml
<fonts>
<bin as_file="false" name="medium" src="path/to/file.ttf" range="0x20-0x7f" symbols="°" size="24"/>
<tiny_ttf as_file="true" name="big" src_path="path/to/file.ttf" range="0x20-0x7f" symbols="auto" size="48"/>
<freetype name="chinese" src_path="file.ttf" size="48" custom_freetype_attribute="abc"/>
</fonts>
In ``<styles>`` and ``<view>``, fonts can then be referenced by their name, e.g.,
.. code-block:: xml
<style name="style1" text_font="medium"/>
The tag name determines how the font is loaded. Currently, only ``tiny_ttf as_file="true"`` is supported.
- ``bin``:
- If ``as_file="true"``: Converts the font file to ``bin`` (see `lv_font_conv`)
which will be loaded by ``lv_binfont_create()``.
- If ``as_file="false"`` (default): On export, the font file will be converted to a
C array LVGL font that can be used directly by LVGL.
- ``tiny_ttf``:
- If ``as_file="true"``: Can be loaded directly by ``lv_tiny_ttf_create_file()``.
- If ``as_file="false"`` (default): The font file will be converted to a raw C
array on export that will be loaded by ``lv_tiny_ttf_create_data()``.
- ``freetype``: The file can be loaded directly by ``lv_freetype_font_create()``.
For simplicity, if ``as_file="false"``, fonts will be loaded as files in the preview.
Setting ``as_file="false"`` affects only the C export.
If the UI is created from XML at runtime and a ``globals.xml`` is parsed, the
``as_file="false"`` tags are skipped because it is assumed that the user manually
creates the mapping. This is because the XML parser cannot automatically map an LVGL
font definition like:
.. code-block:: c
lv_font_t my_font_24;
to
.. code-block:: xml
<bin name="my_font_24"/>
Exported Code
-------------
When C code is exported, global ``const lv_font_t * <font_name>`` variables are
created, and in the initialization function of the Component Library (e.g.,
``my_lib_init_gen()``), the actual font is assigned.
.. Note: :cpp:expr: role cannot be used here because it doesn't know how to parse
the ampersand and angle brackets. An alternate approach could be to make the
arguments "style1_p, font_name", but leaving the ampersand there seems more
appropriate due to that IS the normal way to pass a style as an argument.
So it was made into a literal string instead to avoid the parsing error.
In ``lv_style_set_text_font(&style1, <font_name>)``, the created font is referenced.
Constants
---------
Constants can also be used with fonts.
.. code-block:: xml
<consts>
<int name="font_size" value="32">
<variant name="size" case="small" value="24"/>
</int>
</consts>
<fonts>
<bin name="medium" src_path="file.ttf" range="0x20-0x7f" symbols="°" size="#font_size"/>
</fonts>
Default Font
------------
``"lv_font_default"`` can be used to access ``LV_FONT_DEFAULT``. Other built-in fonts
are not exposed by default.

View File

@@ -0,0 +1,88 @@
.. _xml_images:
======
Images
======
Overview
********
.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE
:trim:
An ``<images>`` section can be added to ``globals.xml`` files. If present, it
describes how to map images with names.
(Later, it might also be possible in Components and Widgets to define local images to
keep the global space cleaner.)
Currently ``<file>`` is the only supported child element, and ``<convert>`` is not
yet implemented.
Usage
*****
.. code-block:: xml
<images>
<file name="avatar" src_path="avatar1.png">
<convert path="raw/avatar.svg" width="100px" color_format="L8"/>
</file>
<data name="logo" src_path="logo1.png" color-format="rgb565" memory="RAM2">
<convert path="https://foo.com/image.png" width="50%" height="80%"
color_format="RGB565"/>
</data>
</images>
- ``<file>`` means that the image source is used as a file path:
- ``<data>`` means that the image is converted to a C array on export.
In both cases in the exported C code global ``const void * <image_name>`` variables are created and in the
initialization function of the Component Library (e.g. ``my_lib_init_gen()``) either the path or
the pointer to the converted :cpp:type:`lv_image_dsc_t` pointers are assigned to that variable.
In :cpp:expr:`lv_image_set_src(image, image_name)` ``image_name`` is used
instead of the path or :cpp:type:`lv_image_dsc_t` pointer.
For simplicity, in the UI |nbsp| Editor preview, images are always loaded as files.
If the UI is created from XML at runtime and a ``globals.xml`` is parsed, the ``<data>`` tags are skipped
because it is assumed that the user manually created the mapping. This is because the XML parser cannot
automatically map an image like:
.. code-block:: c
lv_image_dsc_t my_logo;
to
.. code-block:: xml
<data name="my_logo"/>
Constants
---------
Constants can be used with images as well.
.. code-block:: xml
<consts>
<int name="icon_size" value="32">
<variant name="size" case="small" value="16"/>
</int>
</consts>
<images>
<data name="icon_apply" src_path="apply.png">
<convert path="raw/apply.png" width="#icon_size"/>
</data>
</images>

View File

@@ -0,0 +1,31 @@
.. _xml_component:
====================
XML - Declarative UI
====================
.. toctree::
:maxdepth: 1
intro
component_library
project
syntax
components
screens
widgets
preview
api
styles
consts
view
fonts
images
events
subjects
animations
translations

View File

@@ -0,0 +1,217 @@
.. _xml_intro:
============
Introduction
============
Overview
********
.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE
:trim:
LVGL is capable of loading UI elements written in XML. The XML file can be written by hand, but
it's highly recommended to use LVGL's UI |nbsp| Editor to write the XML files. The UI |nbsp| Editor provides
features like:
- Instant preview the XML files
- Autocomplete and Syntax highlighting
- Online preview for collaboration and testing
- `Figma <https://www.figma.com/>`__ integration to easily reimplement the designs
.. warning::
The UI |nbsp| Editor and the XML loader are still under development and not production ready.
Consider them as an open beta, or experimental features.
Describing the UI in XML in a declarative manner offers several advantages:
- XML files can be loaded at runtime (e.g., from an SD card) to change the application build.
- XML is simpler to write than C, enabling people with different skill sets to create LVGL UIs.
- XML is textual data, making it easy to parse and manipulate with a large number of programming and scripting languages.
- XML can be used to generate LVGL code in any language.
- XML helps to separate the view from the internal logic.
Currently supported features:
- Load XML Components at runtime from file or any type of input
- Nest Components and Widgets to any depth
- Dynamically create instances of XML Components in C
- Register images and font that can be accessed by name later in the XML data (only from
file, no C file is generated for images and fonts)
- Constants are working for Widget- and style-properties
- Parameters can be defined, passed and used for Components
- Most of the built-in Widgets are supported, even the complex ones (``label``, ``slider``,
``bar``, ``button``, ``chart``, ``scale``, ``button matrix``, ``table``, etc.)
- Style sheets and local styles can be assigned to parts and states supporting
almost all style properties
Limitations:
- Screens are not supported yet (only Components)
- Events are not supported yet.
- Animations are not supported yet.
- Observer pattern Subjects are not supported yet.
- The documentation is not complete yet.
Concept
*******
The XML files are Component-oriented. To be more specific, they are ``Component-Library`` oriented.
That is, they are structured in a way to make it easy to create reusable Component Libraries.
For example, a company can have a Component Library for the basic Widgets for all its products
(smart home, smart watch, smart oven, etc.), and create other industry-specific Libraries
(smart-home specific, smart-watch specific, etc.) containing only a few extra Widgets.
These Component Libraries are independent, can be reused across many products, and
can be freely versioned and managed.
Imagine a Component Library as a collection of XML files, images, fonts, and other
assets stored in a git repository, which can be a submodule in many projects.
If someone finds a bug in the Component Library, they can just fix it in just one
place and push it back to the git repository so that other projects can be updated
from it.
The built-in Widgets of LVGL are also considered a ``Component Library`` which is
always available.
A UI |nbsp| Editor project can have any number of Component Libraries and will always have
at least 2:
- LVGL's built-in Widgets, and
- XML-based definitions of Screen contents, along with other project-specific Components.
Widgets, Components, and Screens
********************************
It is important to distinguish between :dfn:`Widgets` and :dfn:`Components`.
Widgets
-------
:dfn:`Widgets` are the core building blocks of the UI and are not meant to be loaded at runtime
but rather compiled into the application. The main characteristics of Widgets are:
- In XML, they start with a ``<widget>`` root element.
- They are similar to LVGL's built-in Widgets.
- They are built using ``lv_obj_class`` objects.
- They have custom and complex logic inside.
- They cannot be loaded from XML at runtime because the custom code cannot be loaded.
- They have a large API with ``set/get/add`` functions.
- They can themselves contain Widgets as children (e.g., ``Tabview``'s tabs, ``Dropdown``'s lists).
Any handwritten Widget can be accessed from XML by:
1. Defining its API in an XML file.
2. Writing and registering an XML parser for it.
`See some examples here. <https://github.com/lvgl/lvgl/tree/master/src/others/xml/parsers>`__
Components
----------
:dfn:`Components` are built from other Components and Widgets, and can be loaded at runtime.
The main characteristics of Components are:
- In XML, they start with a ``<component>`` root element.
- They are built in XML only and cannot have custom C code.
- They can be loaded from XML at runtime as they describe only visual aspects of the UI.
- They are built from Widgets or other Components.
- They can be used for styling Widgets.
- They can contain (as children) Widgets or other Components.
- They can have a simple API to pass properties to their children (e.g., ``btn_text`` to a Label's text).
Regardless of whether the XML was written manually or by the UI |nbsp| Editor, the XML files
of Components can be registered in LVGL, and after that, instances can be created.
In other words, LVGL can just read the XML files, "learn" the Components from them, and
thereafter create Components as part of a :ref:`Screen's <screens>` :ref:`Widget Tree
<basic_data_flow>` according to their structure.
:dfn:`Screens` are similar to Components:
- In XML, they start with a ``<screen>`` root element.
- They are built from Widgets or other Components to describe the :ref:`Screen <screens>`.
- They can be loaded from XML at runtime as they describe only visual aspects of the UI.
- They do not have an API.
- They can be referenced in Screen load events.
Syntax Teaser
*************
Each Widget or Component XML file describes a single Widget or Component. The root
element for Widgets, Components, and Screens are ``<widget>``, ``<component>`` and
``<screen>`` respectively. Other than that, the contained XML elements are almost
identical. This is a high-level overview of the most important XML elements that
will be children of these root elements:
:<api>: Describes the properties that can be ``set`` for a Widget or Component.
Properties can be referenced ysubg ``$``. For Widgets, custom enums can
also be defined with the ``<enumdef>`` tag.
:<consts>: Specifies constants (local to the Widget or Component) for colors, sizes,
and other values. Constant values can be referenced using ``#``.
:<styles>: Describes style (``lv_style_t``) objects that can be referenced (and
shared) by Widgets and Components later.
:<view>: Specifies the appearance of the Widget or Component by describing the
children and their properties.
This is a simple example illustrating what an LVGL XML Component looks like.
Note that only the basic features are shown here.
.. code-block:: xml
<component>
<consts>
<px name="size" value="100"/>
<color name="orange" value="0xffa020"/>
</consts>
<api>
<prop name="btn_text" default="Apply" type="string"/>
</api>
<styles>
<style name="blue" bg_color="0x0000ff" radius="2"/>
<style name="red" bg_color="0xff0000"/>
</styles>
<view extends="lv_button" width="#size" styles="blue red:pressed">
<my_h3 text="$btn_text" align="center" color="#orange" style_text_color:checked="0x00ff00"/>
</view>
</component>
Usage Teaser
************
LVGL's UI |nbsp| Editor can be used in two different ways.
Export C and H Files
--------------------
The Widgets, Components, images, fonts, etc., can be converted to .C/.H files with
plain LVGL code. The exported code works the same way as if it was written by the
user. In this case, the XML files are not required anymore unless modifications may
be made later. The XML files were used only during editing/implementing the Widgets
and Components to save recompilation time and optionally leverage other useful
UI |nbsp| Editor features.
Load the UI from XML
--------------------
Although the Widgets' code always needs to be exported in C and compiled into the
application (just like the built-in LVGL Widgets are also part of the application), the Components'
XML can be loaded and any number of instances can be created at runtime. In the simplest case,
a Component can be registered with :cpp:expr:`lv_xml_component_register_from_file(path)` and
an instance can be created with :cpp:expr:`lv_obj_t * obj = lv_xml_create(parent, "my_button", NULL)`.

View File

@@ -0,0 +1,56 @@
.. _xml_preview:
=======
Preview
=======
Overview
********
.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE
:trim:
In ``<component>`` and ``<widget>``, it is possible to define ``<preview>`` tags.
These are **not** exported to code and are **not** loaded from XML.
They are used only by the UI |nbsp| Editor to describe the context of the Component.
For example, you might want to:
- Change the background of the Editor's preview to dark.
- Center the Component.
- Set margins.
- Change the size of the preview.
Usage
*****
You can think of a ``<preview>`` tag as an ``lv_obj`` where the following properties can be used:
- ``width``, ``height``
- Any local style properties, for example, ``style_bg_color="0x333"``
- ``flex`` and ``flex_flow``
It is also possible to define multiple previews, and in the UI |nbsp| Editor, you can
select one of them.
Example
*******
.. code-block:: xml
<component>
<previews>
<preview name="small_dark" width="content" height="100" style_bg_color="0x333" style_pad_all="32"/>
<preview name="centered" width="200" height="100" flex="row center"/>
<preview name="large_light" width="1980" height="1080" style_bg_color="0xeeeeee"/>
</previews>
<view>
...
</view>
</component>

View File

@@ -0,0 +1,55 @@
.. _xml_project:
=======
Project
=======
Overview
********
.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE
:trim:
A single ``project.xml`` file should be created for each project where the following
content is specified:
:<folders>: Specifies the path to Component Libraries. LVGL's base Widgets are
always loaded automatically.
:<targets>: Describes various hardware configurations, allowing the UI |nbsp| Editor
to check if the UI is out of resources and to select different previews
for each Screen according to the specified displays.
Example
*******
.. code-block:: xml
<project>
<folders>
<folder path="../widget_lib1"/>
<folder path="/home/user/work/ui_libs/modern"/>
<folder path="https://github.com/user/repo"/>
</folders>
<targets>
<renesas-RA8D1-EK gpu="true"/>
<target name="small">
<display width="320" height="240" color_format="RGB565"/>
<memory name="int_ram" size="128K"/>
<memory name="ext_ram" size="2M"/>
<memory name="int_flash" size="512K"/>
<memory name="ext_flash" size="32M"/>
</target>
<target name="large">
<display width="1024" height="768" color_format="XRGB8888"/>
<memory name="int_ram" size="128K"/>
<memory name="ext_ram" size="2M"/>
<memory name="int_flash" size="512K"/>
<memory name="ext_flash" size="32M"/>
</target>
</targets>
</project>

View File

@@ -0,0 +1,63 @@
.. _xml_screens:
=======
Screens
=======
Overview
********
.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE
:trim:
Screens work very similarly to Components. Both can be:
- Loaded from XML
- Contain Widgets and Components as children
- Have ``<styles>``
- Have ``<consts>``
- Have a ``<view>``
However, Screens **cannot** have an ``<api>``.
Usage
*****
Each XML file describes a :ref:`Screen <screens>`. The name of the XML file will
also be the name of the Screen.
In the ``project.xml`` file, multiple ``<display>`` elements can be defined. In the
UI |nbsp| Editor, when a Screen is being developed, the user can select from all the
defined displays in the Preview, and the Screen will be shown with the given
resolution and color depth.
This is useful for verifying responsive designs.
Example
*******
.. code-block:: xml
<screen>
<consts>
<string name="title" value="Main menu"/>
</consts>
<styles>
<style name="dark" bg_color="0x333"/>
</styles>
<view>
<header label="#title"/>
<selector_container styles="dark">
<button text="Weather" icon="cloudy"/>
<button text="Messages" icon="envelope"/>
<button text="Settings" icon="cogwheel"/>
<button text="About" icon="questionmark"/>
</selector_container>
</view>
</screen>

View File

@@ -0,0 +1,127 @@
.. _xml_styles:
======
Styles
======
Overview
********
In XML files, both style sheets (:cpp:expr:`lv_style_t`) and local styles can be used.
Style variants are also supported to change style properties at runtime.
Style Sheets
************
In the ``<styles>`` section, styles and their properties can be defined like this:
.. code-block:: xml
<style name="red"
help="What is this style about?"
border_width="2px"
border_color="0xff0000"/>
Styles can be referenced like this in the ``<view>``:
.. code-block:: xml
<view>
<slider styles="main red:indicator red:knob:focused"/>
</view>
As shown in the example, parts and states are appended after a ``:`` to the style's name.
Local Styles
************
Local styles can be used directly in a Widget, for example:
.. code-block:: xml
<lv_label style_bg_opa="200" style_bg_opa:disabled="100"/>
Gradients
*********
Before the ``<styles>`` tag, the ``<gradients>`` tag can be used to describe various
gradients, which can later be referenced in styles.
When a gradient is created, it can be referenced by its name, like:
.. code-block:: xml
<style bg_grad="grad1"/>
or
.. code-block:: xml
<lv_button style_bg_grad="grad1"/>
Horizontal or Vertical Gradient
-------------------------------
To define a simple ``<horizontal>`` or ``<vertical>`` gradients:
.. code-block:: xml
<gradients>
<horizontal name="grad1">
<stop color="#ff0000" offset="20%" opa="40%"/>
<stop color="#00ff00" offset="128" opa="100%"/>
</horizontal>
</gradients>
Linear Gradient
---------------
To define a skewed gradient from two points:
.. code-block:: xml
<gradients>
<linear name="grad1" start="50 50" end="100 80">
<stop color="#ff0000" offset="20%" opa="100%"/>
<stop color="#00ff00" offset="240" opa="100%"/>
</linear>
</gradients>
Radial Gradient
---------------
To define a radial gradient:
.. code-block:: xml
<gradients>
<radial name="grad1" center="100 50%" edge="200 50" focal_center="50 80%" focal_edge="55 80%">
<stop color="#ff0000" opa="100%"/>
<stop color="#00ff00" opa="100%"/>
</radial>
</gradients>
Conical Gradient
----------------
To define a conical gradient:
.. code-block:: xml
<gradients>
<conical name="grad1" center="80 50%" angle="45 270">
<stop color="#ff0000" opa="100%"/>
<stop color="#00ff00" opa="100%"/>
</conical>
</gradients>

View File

@@ -0,0 +1,53 @@
.. _xml_subjects:
========
Subjects
========
To connect values of the Widget internally or to external data, :ref:`Subjects
<observer_subject>` can be used. For example, an internally connected value could be
a slider's value mapped to a label. Externally connected data could be the current
number of users shown on a label.
To handle internal connections, local Subjects can be created like this:
.. code-block:: xml
<subjects>
<int name="a" value="20"/>
<string name="b" value="Hello"/>
<group name="a_and_b" value="a b"/>
</subjects>
These Subjects can be used in Widget APIs like:
.. code-block:: xml
<view>
<label bind_text="a 'Progress: %d'"/>
</view>
When generating code, the Subjects are saved in the Widget's data and are used like this:
.. code-block:: c
lv_subject_init_int(&my_widget->subject_a, 20);
lv_subject_init_string(&my_widget->subject_b, "Hello");
my_widget->subject_a_and_b_list = lv_malloc(sizeof(lv_subject_t *) * 2);
my_widget->subject_a_and_b_list[0] = &my_widget->subject_a;
my_widget->subject_a_and_b_list[1] = &my_widget->subject_b;
lv_subject_init_group(&my_widget->subject_a_and_b, my_widget->subject_a_and_b_list);
If the connection is more complex and not supported out of the box, it can be handled from code.
External Subjects are defined in the API of the Widget:
.. code-block:: xml
<api>
<prop name="bind_value" help="">
<param name="subject" type="subject" help=""/>
<param name="max_value" type="int" help="Just another parameter, e.g., to limit the value"/>
</prop>
</api>

View File

@@ -0,0 +1,171 @@
.. _xml_syntax:
======
Syntax
======
Naming conventions
******************
.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE
:trim:
- A standard XML syntax is used.
- Lowercase letters with ``_`` separation are used for attribute names.
- Tag names follow the usual variable-name rules: they must start with a letter or
``'_'`` and the rest of the name may be comprised of letters, ``'_'`` and digits.
- The LVGL API is followed as much as possible, e.g., ``align="center"``, ``bg_color="0xff0000"``.
- For colors, all these syntaxes are supported (similar to CSS colors): ``0x112233``,
``#112233``, ``112233``, ``0x123``, ``#123``, ``123``. Note that like CSS,
``0x123``, ``#123`` and ``123`` all mean ``#112233``.
- ``params`` can be referenced with ``$``.
- ``consts`` can be referenced with ``#``.
- ``styles`` can be attached to states and/or parts like this:
``styles="style1 style2:pressed style3:focused:scrollbar"``.
- Local styles (i.e. styles that are stored within the Component and thus not shared
by any other Components) can be used like this:
``<lv_label style_text_color="0xff0000" style_text_color:checked="0x00ff00"/>``.
Types
*****
All of the types can be used as API property types, but only a subset of them can be
used as constant and :ref:`Subject <observer_subject>` types.
Simple types
------------
The following simple built-in types are supported:
:bool: a ``true`` or ``false``.
:int: Integer number in the range of roughly -2B to 2B by default.
(Same as ``int32_t`` in C.)
:px: Simple pixel units. The unit ``px`` can be omitted.
:%: Percentage. ``%`` must be appended to the value as the unit.
(Means the same as :cpp:expr:`lv_pct()`.)
:content: Means ``LV_SIZE_CONTENT``.
:string: Simple NUL-terminated string. When multiple strings are used in a
property or string array, ``'`` should be used. E.g. ``foo="'a' 'b'"``.
:color: A color stored as 24-bit RGB (:cpp:expr:`lv_color_t`).
:opa: Opacity value in the range of 0 to 255 or 0 to 100%. Like CSS,
percentage values must be be followed by '%'.
:lv_obj: Pointer to a Widget (:cpp:expr:`lv_obj_t *`).
:point: A point with ``x`` and ``y`` values (:cpp:expr:`lv_point_t`).
:arglist: List all parameters as arguments. Supports only ``int`` and
``string``. E.g. ``foo="1 23 'hello' 'a'"``.
Name-based types
----------------
In XML files, fonts, images, styles, etc., are not used by pointer but by string
names. For example, a style is defined like ``<style name="red" bg_color="0xff0000"/>``.
Later, they can be referenced by their names.
This means that the actual values need to be bound to the names when the UI is loaded
from XML, otherwise, LVGL wouldn't know what a name means.
Most of these connections are done automatically (e.g., for styles, fonts, images,
animations, gradients, etc.), but others need to be connected manually (e.g., event
callbacks where the callback itself is available only in the code).
For fonts and images, the connections are created automatically if the source is a file.
If the font or image is compiled into the application (as a C array), the user needs
to specify which variable a given name refers to.
To create these connections, functions like
- ``lv_xml_register_image(&ctx, name, pointer)``
- ``lv_xml_register_font(&ctx, name, pointer)``
- ``lv_xml_register_event_cb(&ctx, name, callback)``
- etc.
can be used. Later, a pointer to the object can be retrieved by
- ``lv_xml_get_image(&ctx, name)``
- ``lv_xml_get_font(&ctx, name)``
- ``lv_xml_get_event_cb(&ctx, name)``
- etc.
:style: Name of a style. :cpp:expr:`lv_xml_get_style_by_name(&ctx, name)` returns an :cpp:expr:`lv_style_t *`.
:font: Name of a font. :cpp:expr:`lv_xml_get_font(&ctx, name)` returns an :cpp:expr:`lv_font_t *`.
:image: Name of an image. :cpp:expr:`lv_xml_get_image(&ctx, name)` returns an :cpp:expr:`const void *`,
which can be :cpp:expr:`lv_image_dsc_t *` or a NUL-terminated string path to a file.
:animation: Name of an animation descriptor. :cpp:expr:`lv_xml_get_anim(&ctx, name)` returns an :cpp:expr:`lv_anim_t *`.
:subject: Name of a :ref:`Subject <observer_subject>`. :cpp:expr:`lv_xml_get_subject(&ctx, name)` returns an :cpp:expr:`lv_subject_t *`.
:grad: Name of a gradient. :cpp:expr:`lv_xml_get_grad(&ctx, name)` returns an :cpp:expr:`lv_grad_dsc_t *`.
:event_cb: Name of an event callback. :cpp:expr:`lv_xml_get_event_cb(&ctx, name)` returns an :cpp:expr:`lv_event_cb_t`.
Arrays
------
An array of any type can be defined in four ways:
:int[N]: An integer array with ``N`` elements.
:string[...NULL]: An array terminated with a ``NULL`` element. ``NULL`` can be
replaced by any value, e.g., ``grid_template_last``.
:string[5]: An array that must have exactly 5 elements.
:string[]: No ``NULL`` termination and no count parameter, used when the
number of elements is not known or delivered via another
mechanism, such as via a function parameter.
Enums
-----
``<enumdef>`` can be used in the ``<api>`` tags to create custom enums for
**Widgets**. This is not supported for Components.
For example:
.. code-block:: xml
<api>
<enumdef name="my_widget_mode" help="Possible modes" help-zh="Chinese help">
<enum name="normal" help="Normal mode" help-zh="Normal mode in Chinese" value="0x10"/>
<enum name="inverted" help="Inverted mode"/>
</enumdef>
<prop name="mode" help="help">
<param name="mode" type="enum:my_widget_mode" help="help"/>
</prop>
</api>
When used as a type, a ``+`` suffix means multiple values can be selected and ORed.
For example: ``type="axis+"``. In this case, the options should be separated by
``|``, for example: ``axis=primary_x|secondary_y``.
Compound types
--------------
Types can be compound, meaning multiple options/types are possible. For example, for
width: ``type="px|%|content"``.
Limiting accepted values
------------------------
It is also possible to limit the possible options the user can select from an enum.
For example:
- Enums: ``type="dir(top bottom)"``
- Colors: ``type="color(0xff0000 0x00ff00 0x0000ff)"``
- Strings: ``type="string('Ok' 'Cancel')``
These are checked in the UI |nbsp| Editor, and if an invalid option is selected, it
will be highlighted as an error.

View File

@@ -0,0 +1,7 @@
.. _xml_translations:
============
Translations
============
TODO

View File

@@ -0,0 +1,7 @@
.. _xml_view:
=========
View
=========
TODO

View File

@@ -0,0 +1,120 @@
.. _xml_widgets:
=======
Widgets
=======
Overview
********
.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE
:trim:
Widgets are written in C and compiled into the application.
They can be referenced from Components, and their API can be used via the exposed attributes
(e.g., label text or slider value).
Using the UI |nbsp| Editor, all the following .C/.H files can be exported from the XML of the Widgets:
:<widget_name>_gen.h: Contains the generated API implementation of the widget
(overwritten on each code export).
:<widget_name>_private_gen.h: Contains private API and the data for the widget
(overwritten on each code export).
:<widget_name>_gen.c: Contains the internals of the Widget (overwritten on
each code export).
:<widget_name>.h: Includes ``<widget_name>_gen.h`` and allows the user to
define custom APIs. Only a skeleton is exported once.
:<widget_name>.c: Contains hooks from ``<widget_name>_gen.c`` and allows
the user to write custom code. Only a skeleton is
exported once.
:<widget_name>_xml_parser.c: Processes the XML strings and calls the required
functions according to the set attributes. Only a
skeleton is exported once.
Usage
*****
XML Parser
----------
To make the Widgets accessible from XML, an XML parser needs to be created and
registered for each Widget. The XML parser for the label Widget looks like this:
.. code-block:: c
void * lv_xml_label_create(lv_xml_parser_state_t * state, const char ** attrs)
{
/* Create the label */
void * obj = lv_label_create(lv_xml_state_get_parent(state));
return obj;
}
void lv_xml_label_apply(lv_xml_parser_state_t * state, const char ** attrs)
{
void * obj = lv_xml_state_get_item(state);
/* Apply the common properties, e.g., width, height, styles, flags, etc. */
lv_xml_obj_apply(state, attrs);
/* Process the label-specific attributes */
for(int i = 0; attrs[i]; i += 2) {
const char * name = attrs[i];
const char * value = attrs[i + 1];
if(lv_streq("text", name)) lv_label_set_text(obj, value);
if(lv_streq("long_mode", name)) lv_label_set_long_mode(obj, long_mode_text_to_enum(value));
}
}
/* Helper to convert the string to enum values */
static lv_label_long_mode_t long_mode_text_to_enum(const char * txt)
{
if(lv_streq("wrap", txt)) return LV_LABEL_LONG_WRAP;
if(lv_streq("scroll", txt)) return LV_LABEL_LONG_SCROLL;
LV_LOG_WARN("%s is an unknown value for label's long_mode", txt);
return 0; /* Return 0 in the absence of a better option. */
}
A Widget XML processor can be registered like this:
.. code-block:: c
lv_xml_widget_register("lv_label", lv_xml_label_create, lv_xml_label_apply);
After registration, a Widget can be created like this:
.. code-block:: c
const char * attrs[] = {
"text", "Click here",
"align", "center",
NULL, NULL,
};
lv_xml_create(lv_screen_active(), "lv_label", attrs);
LVGL automatically registers its built-in Widgets, so only custom Widgets need to be
registered manually.
Adding Custom Code
------------------
``<widget_name>.c`` contains three hooks:
- **Constructor hook**: Called when the Widget and all its children are created. Any
modifications can be done on the children here.
- **Destructor hook**: Called when the Widget is deleted. All manually allocated
memory needs to be freed here.
- **Event hook**: Called at the beginning of the Widget's event callback to perform
any custom action.
In this C file, the ``set`` functions for each API ``<prop>`` also need to be
implemented. The declaration of these functions is automatically exported in
``<widget_name>_gen.h``.
Besides these, any custom code and functions can be freely implemented in this file.

View File

@@ -0,0 +1,682 @@
.. _widget_basics:
=============
Widget Basics
=============
What is a Widget?
*****************
A Widget is the **basic building block** of the LVGL user interface.
Examples of Widgets: :ref:`Base Widget (and Screen) <base_widget>`,
:ref:`Button <lv_button>`, :ref:`Label <lv_label>`,
:ref:`Image <lv_image>`, :ref:`List <lv_list>`,
:ref:`Chart <lv_chart>` and :ref:`Text Area <lv_textarea>`.
See :ref:`widgets` to see all Widget types.
All Widgets are referenced using an :cpp:type:`lv_obj_t` pointer as a handle.
This pointer can later be used to read or change the Widget's attributes.
.. _widget_attributes:
Attributes
**********
Basic attributes
----------------
All Widget types share some basic attributes:
- Position
- Size
- Parent
- Styles
- Events it emits
- Flags like *Clickable*, *Scollable*, etc.
- Etc.
You can set/get these attributes with ``lv_obj_set_...`` and
``lv_obj_get_...`` functions. For example:
.. code-block:: c
/* Set basic Widget attributes */
lv_obj_set_size(btn1, 100, 50); /* Set a button's size */
lv_obj_set_pos(btn1, 20,30); /* Set a button's position */
For complete details on position, size, coordinates and layouts, see :ref:`coord`.
Widget-specific attributes
--------------------------
The Widget types have special attributes as well. For example, a slider has
- Minimum and maximum values
- Current value
For these special attributes, every Widget type may have unique API
functions. For example for a slider:
.. code-block:: c
/* Set slider specific attributes */
lv_slider_set_range(slider1, 0, 100); /* Set the min. and max. values */
lv_slider_set_value(slider1, 40, LV_ANIM_ON); /* Set the current value (position) */
The API of the widgets is described in their
:ref:`Documentation <widgets>` but you can also check the respective
header files (e.g. *widgets/lv_slider.h*)
.. _lv_obj_parents_and_children:
Parents and children
--------------------
A Widget's parent is set when the widget is created --- the parent is passed to the
creation function.
To get a Widget's current parent, use :cpp:expr:`lv_obj_get_parent(widget)`.
You can move the Widget to a new parent with :cpp:expr:`lv_obj_set_parent(widget, new_parent)`.
To get a specific child of a parent use :cpp:expr:`lv_obj_get_child(parent, idx)`.
Some examples for ``idx``:
- ``0`` get the child created first
- ``1`` get the child created second
- ``-1`` get the child created last
You can iterate through a parent Widget's children like this:
.. code-block:: c
uint32_t i;
for(i = 0; i < lv_obj_get_child_count(parent); i++) {
lv_obj_t * child = lv_obj_get_child(parent, i);
/* Do something with child. */
}
:cpp:expr:`lv_obj_get_index(widget)` returns the index of the Widget in its parent.
It is equivalent to the number of older children in the parent.
You can bring a Widget to the foreground or send it to the background with
:cpp:expr:`lv_obj_move_foreground(widget)` and :cpp:expr:`lv_obj_move_background(widget)`.
You can change the index of a Widget in its parent using :cpp:expr:`lv_obj_move_to_index(widget, index)`.
You can swap the position of two Widgets with :cpp:expr:`lv_obj_swap(widget1, widget2)`.
To get a Widget's Screen (highest-level parent) use :cpp:expr:`lv_obj_get_screen(widget)`.
.. _widget_working_mechanisms:
Working Mechanisms
******************
Parent-child structure
----------------------
A parent Widget can be considered as the container of its children. Every Widget has
exactly one parent Widget (except Screens), but a parent Widget can have any number
of children. There is no limitation for the type of the parent but there are Widgets
which are typically a parent (e.g. button) or a child (e.g. label).
Moving together
---------------
If the position of a parent changes, the children will move along with
it. Therefore, all positions are relative to the parent.
.. image:: /_static/images/par_child1.png
.. code-block:: c
lv_obj_t * parent = lv_obj_create(lv_screen_active()); /* Create a parent Widget on current Screen */
lv_obj_set_size(parent, 100, 80); /* Set size of parent */
lv_obj_t * widget1 = lv_obj_create(parent); /* Create a Widget on previously created parent Widget */
lv_obj_set_pos(widget1, 10, 10); /* Set position of new Widget */
Modify the position of the parent:
.. image:: /_static/images/par_child2.png
.. code-block:: c
lv_obj_set_pos(parent, 50, 50); /* Move the parent. The child will move with it. */
(For simplicity the adjusting of colors of the Widgets is not shown in
the example.)
Visibility only on the parent
-----------------------------
If a child is partially or fully outside its parent then the parts
outside will not be visible.
.. image:: /_static/images/par_child3.png
.. code-block:: c
lv_obj_set_x(widget1, -30); /* Move the child a little bit off the parent */
This behavior can be overwritten with
:cpp:expr:`lv_obj_add_flag(widget, LV_OBJ_FLAG_OVERFLOW_VISIBLE)` which allow the
children to be drawn out of the parent. In addition to this, you must register
the following event callback (this was not required in previous versions).
Note: ``ext_width`` should be the maximum absolute width the children will be
drawn within.
.. code-block:: c
static void ext_draw_size_event_cb(lv_event_t * e)
{
lv_event_set_ext_draw_size(e, 30); /*Set 30px extra draw area around the widget*/
}
Creating and deleting Widgets
-----------------------------
In LVGL, Widgets can be created and deleted dynamically at run time. It
means only the currently created (existing) Widgets consume RAM.
This allows for the creation of a Screen just when a button is clicked
to open it, and for deletion of Screens when a new Screen is loaded.
UIs can be created based on the current environment of the device. For
example one can create meters, charts, bars and sliders based on the
currently attached sensors.
Every widget has its own **create** function with a prototype like this:
.. code-block:: c
lv_obj_t * lv_<widget>_create(lv_obj_t * parent, <other parameters if any>);
Typically, the create functions only have a ``parent`` parameter telling
them on which Widget to create the new Widget.
The return value is a pointer to the created Widget with :cpp:type:`lv_obj_t` ``*``
type.
There is a common **delete** function for all Widget types. It deletes
the Widget and all of its children.
.. code-block:: c
void lv_obj_delete(lv_obj_t * widget);
:cpp:func:`lv_obj_delete` will delete the Widget immediately. If for any reason you
can't delete the Widget immediately you can use
:cpp:expr:`lv_obj_delete_async(widget)` which will perform the deletion on the next
call of :cpp:func:`lv_timer_handler`. This is useful e.g. if you want to
delete the parent of a Widget in the child's :cpp:enumerator:`LV_EVENT_DELETE`
handler.
You can remove all the children of a Widget (but not the Widget itself)
using :cpp:expr:`lv_obj_clean(widget)`.
You can use :cpp:expr:`lv_obj_delete_delayed(widget, 1000)` to delete a Widget after
some time. The delay is expressed in milliseconds.
Sometimes you're not sure whether a Widget was deleted and you need some way to
check if it's still "alive". Anytime before the Widget is deleted, you can use
cpp:expr:`lv_obj_null_on_delete(&widget)` to cause your Widget pointer to be set to ``NULL``
when the Widget is deleted.
Make sure the pointer variable itself stays valid until the Widget is deleted. Here
is an example:
.. code:: c
void some_timer_callback(lv_timer_t * t)
{
static lv_obj_t * my_label;
if(my_label == NULL) {
my_label = lv_label_create(lv_screen_active());
lv_obj_delete_delayed(my_label, 1000);
lv_obj_null_on_delete(&my_label);
}
else {
lv_obj_set_x(my_label, lv_obj_get_x(my_label) + 1);
}
}
.. _screens:
Screens
*******
What are Screens?
-----------------
Not to be confused with a :ref:`display`, Screens are simply any Widget created
without a parent (i.e. passing NULL for the ``parent`` argument during creation). As
such, they form the "root" of a Widget Tree.
Normally the Base Widget is used for this purpose since it has all the features most
Screens need. But an :ref:`lv_image` Widget can also be used to create a wallpaper
background for the Widget Tree.
All Screens:
- are automatically attached to the :ref:`default_display` current when the Screen
was created;
- automatically occupy the full area of the associated display;
- cannot be moved, i.e. functions such as :cpp:func:`lv_obj_set_pos` and
:cpp:func:`lv_obj_set_size` cannot be used on screens.
Each :ref:`display` object can have multiple screens associated with it, but not vice
versa. Thus the relationship::
Display
|
--- (one or more)
/|\
Screen Widgets (root of a Widget Tree)
|
O (zero or more)
/|\
Child Widgets
Creating Screens
----------------
Screens are created like this:
.. code-block:: c
lv_obj_t * scr1 = lv_obj_create(NULL);
Screens can be deleted with :cpp:expr:`lv_obj_delete(scr)`, but be sure you do not
delete the :ref:`active_screen`.
.. _active_screen:
Active Screen
-------------
While each :ref:`display` object can have any number of Screens Widgets associated
with it, only one of those Screens is considered "Active" at any given time. That
Screen is referred to as the Display's "Active Screen". For this reason, only one
Screen and its child Widgets will ever be shown on a display at one time.
When each :ref:`display` object was created, a default screen was created with it and
set as its "Active Screen".
To get a pointer to the "Active Screen", call :cpp:func:`lv_screen_active`.
To set a Screen to be the "Active Screen", call :cpp:func:`lv_screen_load` or
:cpp:func:`lv_screen_load_anim`.
.. _loading_screens:
Loading Screens
---------------
To load a new screen, use :cpp:expr:`lv_screen_load(scr1)`. This sets ``scr1`` as
the Active Screen.
Load Screen with Extended Options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There is a way to load screens that gives you 2 additional (extended) options,
allowing the caller to specify:
- an optional transition method, and
- an option to gracefully delete the screen that was being displayed.
:cpp:expr:`lv_screen_load_anim(scr, transition_type, time, delay, auto_del)`. The
following transition types exist:
- :cpp:enumerator:`LV_SCR_LOAD_ANIM_NONE`: Switch immediately after ``delay`` milliseconds
- :cpp:enumerator:`LV_SCR_LOAD_ANIM_OVER_LEFT`, :cpp:enumerator:`LV_SCR_LOAD_ANIM_OVER_RIGHT`, :cpp:enumerator:`LV_SCR_LOAD_ANIM_OVER_TOP` and :cpp:enumerator:`LV_SCR_LOAD_ANIM_OVER_BOTTOM`: Move the new screen over the current towards the given direction
- :cpp:enumerator:`LV_SCR_LOAD_ANIM_OUT_LEFT`, :cpp:enumerator:`LV_SCR_LOAD_ANIM_OUT_RIGHT`, :cpp:enumerator:`LV_SCR_LOAD_ANIM_OUT_TOP` and :cpp:enumerator:`LV_SCR_LOAD_ANIM_OUT_BOTTOM`: Move out the old screen over the current towards the given direction
- :cpp:enumerator:`LV_SCR_LOAD_ANIM_MOVE_LEFT`, :cpp:enumerator:`LV_SCR_LOAD_ANIM_MOVE_RIGHT`, :cpp:enumerator:`LV_SCR_LOAD_ANIM_MOVE_TOP` and :cpp:enumerator:`LV_SCR_LOAD_ANIM_MOVE_BOTTOM`: Move both the current and new screens towards the given direction
- :cpp:enumerator:`LV_SCR_LOAD_ANIM_FADE_IN` and :cpp:enumerator:`LV_SCR_LOAD_ANIM_FADE_OUT`: Fade the new screen over the old screen, or vice versa
Setting ``auto_del`` to ``true`` will automatically delete the old
screen when the animation (if any) is finished.
The new screen will become active (returned by :cpp:func:`lv_screen_active`) when
the animation starts after ``delay`` time. All inputs are disabled
during the screen animation.
.. _layers_overview:
Layers
------
When an ``lv_display_t`` object is created, 4 Screens (layers) are created and
attached to it.
1. Bottom Layer
2. Active Screen
3. Top Layer
4. System Layer
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 Active Screen.
See :ref:`display_screen_layers` and :ref:`transparent_screens` for more information.
.. _widget_parts:
Parts
*****
The widgets are built from multiple parts. For example a
:ref:`Base Widget <base_widget>` uses the main and scrollbar parts but a
:ref:`Slider <lv_slider>` uses the main, indicator and knob parts.
Parts are similar to *pseudo-elements* in CSS.
The following predefined parts exist in LVGL:
- :cpp:enumerator:`LV_PART_MAIN`: A background like rectangle
- :cpp:enumerator:`LV_PART_SCROLLBAR`: The scrollbar(s)
- :cpp:enumerator:`LV_PART_INDICATOR`: Indicator, e.g. for slider, bar, switch, or the tick box of the checkbox
- :cpp:enumerator:`LV_PART_KNOB`: Like a handle to grab to adjust the value
- :cpp:enumerator:`LV_PART_SELECTED`: Indicate the currently selected option or section
- :cpp:enumerator:`LV_PART_ITEMS`: Used if the widget has multiple similar elements (e.g. table cells)
- :cpp:enumerator:`LV_PART_CURSOR`: Mark a specific place e.g. text area's or chart's cursor
- :cpp:enumerator:`LV_PART_CUSTOM_FIRST`: Custom parts can be added from here.
The main purpose of parts is to allow styling the "components" of the
widgets. They are described in more detail in the
:ref:`Style overview <styles>` section.
.. _widget_states:
States
******
The Widget can be in a combination of the following states:
- :cpp:enumerator:`LV_STATE_DEFAULT`: Normal, released state
- :cpp:enumerator:`LV_STATE_CHECKED`: Toggled or checked state
- :cpp:enumerator:`LV_STATE_FOCUSED`: Focused via keypad or encoder or clicked via touchpad/mouse
- :cpp:enumerator:`LV_STATE_FOCUS_KEY`: Focused via keypad or encoder but not via touchpad/mouse
- :cpp:enumerator:`LV_STATE_EDITED`: Edit by an encoder
- :cpp:enumerator:`LV_STATE_HOVERED`: Hovered by mouse (not supported now)
- :cpp:enumerator:`LV_STATE_PRESSED`: Being pressed
- :cpp:enumerator:`LV_STATE_SCROLLED`: Being scrolled
- :cpp:enumerator:`LV_STATE_DISABLED`: Disabled state
- :cpp:enumerator:`LV_STATE_USER_1`: Custom state
- :cpp:enumerator:`LV_STATE_USER_2`: Custom state
- :cpp:enumerator:`LV_STATE_USER_3`: Custom state
- :cpp:enumerator:`LV_STATE_USER_4`: Custom state
The states are usually automatically changed by the library as the user
interacts with a Widget (presses, releases, focuses, etc.). However,
the states can be changed manually as well. To set or clear given state (but
leave the other states untouched) use
:cpp:expr:`lv_obj_add_state(widget, LV_STATE_...)` and
:cpp:expr:`lv_obj_remove_state(widget, LV_STATE_...)`. In both cases OR-ed state
values can be used as well. E.g.
:cpp:expr:`lv_obj_add_state(widget, part, LV_STATE_PRESSED | LV_PRESSED_CHECKED)`.
To learn more about the states, read the related section of
:ref:`styles_overview`.
.. _lv_obj_flags:
Flags
*****
There are some Widget attributes which can be enabled/disabled by
:cpp:expr:`lv_obj_add_flag(widget, LV_OBJ_FLAG_...)` and
:cpp:expr:`lv_obj_remove_flag(widget, LV_OBJ_FLAG_...)`.
- :cpp:enumerator:`LV_OBJ_FLAG_HIDDEN` Make the Widget hidden. (Like it wasn't there at all)
- :cpp:enumerator:`LV_OBJ_FLAG_CLICKABLE` Make the Widget clickable by input devices
- :cpp:enumerator:`LV_OBJ_FLAG_CLICK_FOCUSABLE` Add focused state to the Widget when clicked
- :cpp:enumerator:`LV_OBJ_FLAG_CHECKABLE` Toggle checked state when the Widget is clicked
- :cpp:enumerator:`LV_OBJ_FLAG_SCROLLABLE` Make the Widget scrollable
- :cpp:enumerator:`LV_OBJ_FLAG_SCROLL_ELASTIC` Allow scrolling inside but with slower speed
- :cpp:enumerator:`LV_OBJ_FLAG_SCROLL_MOMENTUM` Make the Widget scroll further when "thrown"
- :cpp:enumerator:`LV_OBJ_FLAG_SCROLL_ONE` Allow scrolling only one snappable children
- :cpp:enumerator:`LV_OBJ_FLAG_SCROLL_CHAIN_HOR` Allow propagating the horizontal scroll to a parent
- :cpp:enumerator:`LV_OBJ_FLAG_SCROLL_CHAIN_VER` Allow propagating the vertical scroll to a parent
- :cpp:enumerator:`LV_OBJ_FLAG_SCROLL_CHAIN` Simple packaging for (:cpp:expr:`LV_OBJ_FLAG_SCROLL_CHAIN_HOR | LV_OBJ_FLAG_SCROLL_CHAIN_VER`)
- :cpp:enumerator:`LV_OBJ_FLAG_SCROLL_ON_FOCUS` Automatically scroll Widget to make it visible when focused
- :cpp:enumerator:`LV_OBJ_FLAG_SCROLL_WITH_ARROW` Allow scrolling the focused Widget with arrow keys
- :cpp:enumerator:`LV_OBJ_FLAG_SNAPPABLE` If scroll snap is enabled on the parent it can snap to this Widget
- :cpp:enumerator:`LV_OBJ_FLAG_PRESS_LOCK` Keep the Widget pressed even if the press slid from the Widget
- :cpp:enumerator:`LV_OBJ_FLAG_EVENT_BUBBLE` Propagate the events to the parent as well
- :cpp:enumerator:`LV_OBJ_FLAG_GESTURE_BUBBLE` Propagate the gestures to the parent
- :cpp:enumerator:`LV_OBJ_FLAG_ADV_HITTEST` Allow performing more accurate hit (click) test. E.g. accounting for rounded corners
- :cpp:enumerator:`LV_OBJ_FLAG_IGNORE_LAYOUT` Make the Widget not positioned by the layouts
- :cpp:enumerator:`LV_OBJ_FLAG_FLOATING` Do not scroll the Widget when the parent scrolls and ignore layout
- :cpp:enumerator:`LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS` Enable sending ``LV_EVENT_DRAW_TASK_ADDED`` events
- :cpp:enumerator:`LV_OBJ_FLAG_OVERFLOW_VISIBLE` Do not clip the children's content to the parent's boundary
- :cpp:enumerator:`LV_OBJ_FLAG_FLEX_IN_NEW_TRACK` Start a new flex track on this item
- :cpp:enumerator:`LV_OBJ_FLAG_LAYOUT_1` Custom flag, free to use by layouts
- :cpp:enumerator:`LV_OBJ_FLAG_LAYOUT_2` Custom flag, free to use by layouts
- :cpp:enumerator:`LV_OBJ_FLAG_WIDGET_1` Custom flag, free to use by widget
- :cpp:enumerator:`LV_OBJ_FLAG_WIDGET_2` Custom flag, free to use by widget
- :cpp:enumerator:`LV_OBJ_FLAG_USER_1` Custom flag, free to use by user
- :cpp:enumerator:`LV_OBJ_FLAG_USER_2` Custom flag, free to use by user
- :cpp:enumerator:`LV_OBJ_FLAG_USER_3` Custom flag, free to use by user
- :cpp:enumerator:`LV_OBJ_FLAG_USER_4` Custom flag, free to use by user
Some examples:
.. code-block:: c
/* Hide on Widget */
lv_obj_add_flag(widget, LV_OBJ_FLAG_HIDDEN);
/* Make a Widget non-clickable */
lv_obj_remove_flag(widget, LV_OBJ_FLAG_CLICKABLE);
.. _lv_obj_events:
Base-Widget Events
******************
.. _widget_events:
Events from Input Devices
-------------------------
- :cpp:enumerator:`LV_EVENT_PRESSED` Widget has been pressed.
- :cpp:enumerator:`LV_EVENT_PRESSING` Widget is being pressed (sent continuously while pressing).
- :cpp:enumerator:`LV_EVENT_PRESS_LOST` Widget is still being pressed but slid cursor/finger off Widget.
- :cpp:enumerator:`LV_EVENT_SHORT_CLICKED` Widget was pressed for a short period of time, then released. Not sent if scrolled.
- :cpp:enumerator:`LV_EVENT_SINGLE_CLICKED` Sent for first short click within a small distance and short time.
- :cpp:enumerator:`LV_EVENT_DOUBLE_CLICKED` Sent for second short click within small distance and short time.
- :cpp:enumerator:`LV_EVENT_TRIPLE_CLICKED` Sent for third short click within small distance and short time.
- :cpp:enumerator:`LV_EVENT_LONG_PRESSED` Object has been pressed for at least `long_press_time`. Not sent if scrolled.
- :cpp:enumerator:`LV_EVENT_LONG_PRESSED_REPEAT` Sent after `long_press_time` in every `long_press_repeat_time` ms. Not sent if scrolled.
- :cpp:enumerator:`LV_EVENT_CLICKED` Sent on release if not scrolled (regardless to long press).
- :cpp:enumerator:`LV_EVENT_RELEASED` Sent in every cases when Widget has been released.
- :cpp:enumerator:`LV_EVENT_SCROLL_BEGIN` Scrolling begins. The event parameter is a pointer to the animation of the scroll. Can be modified.
- :cpp:enumerator:`LV_EVENT_SCROLL_THROW_BEGIN` Received when scrolling begins.
- :cpp:enumerator:`LV_EVENT_SCROLL_END` Scrolling ended.
- :cpp:enumerator:`LV_EVENT_SCROLL` Scrolling
- :cpp:enumerator:`LV_EVENT_GESTURE` A gesture is detected. Get gesture with `lv_indev_get_gesture_dir(lv_indev_active());`
- :cpp:enumerator:`LV_EVENT_KEY` A key is sent to Widget. Get key with `lv_indev_get_key(lv_indev_active());`
- :cpp:enumerator:`LV_EVENT_FOCUSED` Widget received focus,
- :cpp:enumerator:`LV_EVENT_DEFOCUSED` Widget's focus has been lost.
- :cpp:enumerator:`LV_EVENT_LEAVE` Widget's focus has been lost but is still selected.
- :cpp:enumerator:`LV_EVENT_HIT_TEST` Perform advanced hit-testing.
Special Events
--------------
- :cpp:enumerator:`LV_EVENT_VALUE_CHANGED` when the :cpp:enumerator:`LV_OBJ_FLAG_CHECKABLE` flag is
enabled and the Widget was clicked (on transition to/from the checked state)
Drawing Events
--------------
- :cpp:enumerator:`LV_EVENT_DRAW_MAIN` Performing drawing of main part
- :cpp:enumerator:`LV_EVENT_DRAW_MAIN_BEGIN` Starting drawing of main part
- :cpp:enumerator:`LV_EVENT_DRAW_MAIN_END` Finishing drawing of main part
- :cpp:enumerator:`LV_EVENT_DRAW_POST` Perform the post draw phase (when all children are drawn)
- :cpp:enumerator:`LV_EVENT_DRAW_POST_BEGIN` Starting the post draw phase (when all children are drawn)
- :cpp:enumerator:`LV_EVENT_DRAW_POST_END` Finishing the post draw phase (when all children are drawn)
Other Events
------------
- :cpp:enumerator:`LV_EVENT_DELETE` Object is being deleted
- :cpp:enumerator:`LV_EVENT_CHILD_CHANGED` Child was removed, added, or its size, position were changed
- :cpp:enumerator:`LV_EVENT_CHILD_CREATED` Child was created, always bubbles up to all parents
- :cpp:enumerator:`LV_EVENT_CHILD_DELETED` Child was deleted, always bubbles up to all parents
- :cpp:enumerator:`LV_EVENT_SIZE_CHANGED` Object coordinates/size have changed
- :cpp:enumerator:`LV_EVENT_STYLE_CHANGED` Object's style has changed
- :cpp:enumerator:`LV_EVENT_LAYOUT_CHANGED` A child's position has changed due to a layout recalculation (when container has flex or grid layout style)
- :cpp:enumerator:`LV_EVENT_GET_SELF_SIZE` Get internal size of a widget
.. admonition:: Further Reading
Learn more about :ref:`events`.
.. _lv_obj_keys:
Keys
****
If :cpp:enumerator:`LV_OBJ_FLAG_CHECKABLE` is enabled, :cpp:enumerator:`LV_KEY_RIGHT` and
:cpp:enumerator:`LV_KEY_UP` make the Widget checked, and :cpp:enumerator:`LV_KEY_LEFT` and
:cpp:enumerator:`LV_KEY_DOWN` make it unchecked.
If :cpp:enumerator:`LV_OBJ_FLAG_SCROLLABLE` is enabled, but the Widget is not editable
(as declared by the widget class), the arrow keys (:cpp:enumerator:`LV_KEY_UP`,
:cpp:enumerator:`LV_KEY_DOWN`, :cpp:enumerator:`LV_KEY_LEFT`, :cpp:enumerator:`LV_KEY_RIGHT`) scroll the Widget.
If the Widget can only scroll vertically, :cpp:enumerator:`LV_KEY_LEFT` and
:cpp:enumerator:`LV_KEY_RIGHT` will scroll up/down instead, making it compatible with
an encoder input device. See :ref:`Input devices overview <indev>` for
more on encoder behaviors and the edit mode.
.. admonition:: Further Reading
Learn more about :ref:`indev_keys`.
.. _widget_names:
Names
*****
When a widget is created, its reference can be stored in an :cpp:expr:`lv_obj_t *` pointer
variable. To use this widget in multiple places in the code, the variable can be passed
as a function parameter or made a global variable. However, this approach has some drawbacks:
- Using global variables is not clean and generally not recommended.
- It's not scalable. Passing references to 20 widgets as function parameters is not ideal.
- It's hard to track whether a widget still exists or has been deleted.
Setting names
-------------
To address these issues, LVGL introduces a powerful widget naming system that can be enabled
by setting ``LV_USE_OBJ_NAME`` in ``lv_conf.h``.
A custom name can be assigned using :cpp:expr:`lv_obj_set_name(obj, "name")` or
:cpp:expr:`lv_obj_set_name_static(obj, "name")`. The "static" variant means the passed name
must remain valid while the widget exists, as only the pointer is stored. Otherwise, LVGL will
allocate memory to store a copy of the name.
If a name ends with ``#``, LVGL will automatically replace it with an index based on the
number of siblings with the same base name. If no name is provided, the default is
``<widget_type>_#``.
Below is an example showing how manually and automatically assigned names are resolved:
- Main ``lv_obj`` container named ``"cont"``: "cont"
- ``lv_obj`` container named ``"header"``: "header"
- ``lv_label`` with no name: "lv_label_0"
- ``lv_label`` named ``"title"``: "title"
- ``lv_label`` with no name: "lv_label_1" (It's the third label, but custom-named widgets are not counted)
- ``lv_obj`` container named ``"buttons"``:
- ``lv_button`` with no name: "lv_button_0"
- ``lv_button`` named ``"second_button"``: "second_button"
- ``lv_button`` with no name: "lv_button_1"
- ``lv_button`` named ``lv_button_#``: "lv_button_2"
- ``lv_button`` named ``mybtn_#``: "mybtn_0"
- ``lv_button`` with no name: "lv_button_2"
- ``lv_button`` named ``mybtn_#``: "mybtn_1"
- ``lv_button`` named ``mybtn_#``: "mybtn_2"
- ``lv_button`` named ``mybtn_#``: "mybtn_3"
Finding widgets
---------------
Widgets can be found by name in two ways:
1. **Get a direct child by name** using :cpp:func:`lv_obj_get_child_by_name(parent, "child_name")`.
Example:
``lv_obj_get_child_by_name(header, "title")``
You can also use a "path" to find nested children:
``lv_obj_get_child_by_name(cont, "buttons/mybtn_2")``
2. **Find a descendant at any level** using :cpp:func:`lv_obj_find_by_name(parent, "child_name")`.
Example:
``lv_obj_find_by_name(cont, "mybtn_1")``
Note that ``"mybtn_1"`` is a child of ``"buttons"``, not directly of ``"cont"``.
This is useful when you want to ignore hierarchy and search by name alone.
Since both functions start searching from a specific parent, its possible to have multiple widget
subtrees with identical names under different parents.
For example, if ``my_listitem_create(parent)`` creates a widget named ``"list_item_#"``
with children like ``"icon"``, ``"title"``, ``"ok_button"``, and ``"lv_label_0"``,
and it's called 10 times, a specific ``"ok_button"`` can be found like this:
.. code-block:: c
lv_obj_t * item = lv_obj_find_by_name(lv_screen_active(), "list_item_5");
lv_obj_t * ok_btn = lv_obj_find_by_name(item, "ok_button");
// Or
lv_obj_t * ok_btn = lv_obj_get_child_by_name(some_list_container, "list_item_5/ok_button");
Names are resolved **when they are retrieved**, not when they are set.
This means the indices always reflect the current state of the widget tree
at the time the name is used.
.. _widget_snapshot:
Snapshot
********
A snapshot image can be generated for a Widget together with its
children. Check details in :ref:`snapshot`.
Example
*******
.. include:: ../../examples/widgets/obj/index.rst
.. _lv_obj_api:
API
***

View File

@@ -0,0 +1,624 @@
.. _coord:
============================
Positions, Sizes and Layouts
============================
Overview
********
Similar to many other parts of LVGL, the concept of setting the
coordinates was inspired by CSS. LVGL has by no means a complete
implementation of CSS but a comparable subset is implemented (sometimes
with minor adjustments).
In short this means:
- Explicitly set coordinates are stored in styles (position, size, layouts, etc.)
- support min-width, max-width, min-height, max-height
- have pixel, percentage, and "content" units
- x=0; y=0 coordinate means the top-left corner of the parent plus the left/top padding plus border width
- width/height means the full size, the "content area" is smaller with padding and border width
- a subset of flexbox and grid layouts are supported
.. _coord_units:
Length Units
************
When passing "length units" (a.k.a. "distance units" or "size units") as arguments to
functions that modify position, size, etc., to make layout of your UI convenient, you
have a choice of several different types of units you can use.
:pixels: Specify size as pixels: an integer value <
:c:macro:`LV_COORD_MAX` always means pixels. E.g.
:cpp:expr:`lv_obj_set_x(btn, 10)`.
:percentage: Specify size as a percentage of the size of the Widget's
parent or of itself, depending on the property.
:cpp:expr:`lv_pct(value)` converts ``value`` to a percentage.
E.g. :cpp:expr:`lv_obj_set_width(btn, lv_pct(50))`. If you want
to avoid the overhead of the call to :cpp:func:`lv_pct`, you can
also use the macro :cpp:expr:`LV_PCT(x)` to mean the same thing.
Note that when you use this feature, your value is *stored as a
percent* so that if/when the size of the parent container (or
other positioning factor) changes, this style value dynamically
retains its meaning.
:contained content: Specify size as a function of the Widget's children. The macro
:c:macro:`LV_SIZE_CONTENT`: passed as a size value has special
meaning: it means to set the width and/or height of a Widget
just large enough to include all of its children. This is
similar to ``auto`` in CSS. E.g.
:cpp:expr:`lv_obj_set_width(btn, LV_SIZE_CONTENT)`.
:inches: Specify size as 1/160-th portion of an inch as if it were pixels
on a 160-DPI display, even though a display may have a different
DPI. Use :cpp:expr:`lv_dpx(n)` or :cpp:expr:`LV_DPX(n)` to do
this. Examples:
+----+-----+----------------------------+
| n | DPI | Computed Pixels |
+====+=====+============================+
| 40 | 320 | 80 pixels to make 1/4 inch |
+----+-----+----------------------------+
| 40 | 160 | 40 pixels to make 1/4 inch |
+----+-----+----------------------------+
| 40 | 130 | 33 pixels to make 1/4 inch |
+----+-----+----------------------------+
| 80 | 130 | 66 pixels to make 1/2 inch |
+----+-----+----------------------------+
See DPI under :ref:`display_attributes`.
.. _boxing_model:
Boxing Model
************
LVGL follows CSS's `border-box <https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing>`__
model. A Widget's "box" is built from the following parts:
:bounding box: the width/height of the elements.
:border width: the width of the border.
:padding: space between the sides of the Widget and its children.
:margin: space outside of the Widget (considered only by some layouts)
:content: the content area which is the size of the bounding box reduced by the border width and padding.
.. image:: /_static/images/boxmodel.png
:alt: The box models of LVGL: The content area is smaller than the bounding box with the padding and border width
The border is drawn inside the bounding box. Inside the border LVGL
keeps a "padding margin" when placing a Widget's children.
The outline is drawn outside the bounding box.
.. _coord_notes:
Important Notes
***************
This section describes special cases in which LVGL's behavior might be
unexpected.
.. _coord_postponed_coordinate_calculation:
Postponed coordinate calculation
--------------------------------
LVGL doesn't recalculate all the coordinate changes immediately. This is
done to improve performance. Instead, the Widgets are marked as "dirty"
and before redrawing the screen LVGL checks if there are any "dirty"
Widgets. If so it refreshes their position, size and layout.
In other words, if you need to get the coordinate of a Widget and the
coordinates were just changed, LVGL needs to be forced to recalculate
the coordinates. To do this call :cpp:func:`lv_obj_update_layout`.
The size and position might depend on the parent or layout. Therefore
:cpp:func:`lv_obj_update_layout` recalculates the coordinates of all Widgets on
the screen of ``obj``.
.. _coord_removing styles:
Removing styles
---------------
As it's described in the :ref:`coord_using_styles` section,
coordinates can also be set via style properties. To be more precise,
under the hood every style coordinate related property is stored as a
style property. If you use :cpp:expr:`lv_obj_set_x(widget, 20)` LVGL saves ``x=20``
in the local style of the Widget.
This is an internal mechanism and doesn't matter much as you use LVGL.
However, there is one case in which you need to be aware of the
implementation. If the style(s) of a Widget are removed by
.. code-block:: c
lv_obj_remove_style_all(widget)
or
.. code-block:: c
lv_obj_remove_style(widget, NULL, LV_PART_MAIN);
the earlier set coordinates will be removed as well.
For example:
.. code-block:: c
/* The size of obj1 will be set back to the default in the end */
lv_obj_set_size(widget1, 200, 100); /* Now obj1 has 200;100 size */
lv_obj_remove_style_all(widget1); /* It removes the set sizes */
/* widget2 will have 200;100 size in the end */
lv_obj_remove_style_all(widget2);
lv_obj_set_size(widget2, 200, 100);
.. _positioning_widgets:
Positioning Widgets
*******************
Direct
------
To simply set the x and y coordinates of a Widget use:
.. code-block:: c
lv_obj_set_x(widget, 10); /* Separate... */
lv_obj_set_y(widget, 20);
lv_obj_set_pos(widget, 10, 20); /* Or in one function */
By default, the x and y coordinates are measured from the top left
corner of the parent's content area. For example if the parent has five
pixels of padding on every side the above code will place ``obj`` at
(15, 25) because the content area starts after the padding.
Percentage values are calculated from the parent's content area size.
.. code-block:: c
lv_obj_set_x(btn, lv_pct(10)); //x = 10 % of parent content area width
Alignment
---------
Inside parent widget
~~~~~~~~~~~~~~~~~~~~
In many cases it is more convenient to tell LVGL to align your object relative to
an "anchor" in its parent *other* than its upper left corner. To establish
that "anchor", call :cpp:expr:`lv_obj_set_align(widget, LV_ALIGN_...)`. After
that call, that "anchor" will be remembered until another one is established.
In other words, every futire x and y setting for that Widget will be relative to the
that "anchor".
Example: Position Widget (10,20) px relative to the center of its parent:
.. code-block:: c
lv_obj_set_align(widget, LV_ALIGN_CENTER);
lv_obj_set_pos(widget, 10, 20);
/* Or combine the above in one function... */
lv_obj_align(widget, LV_ALIGN_CENTER, 10, 20);
9 convenient "anchors" can be used with these functions:
- :cpp:enumerator:`LV_ALIGN_TOP_LEFT`
- :cpp:enumerator:`LV_ALIGN_TOP_MID`
- :cpp:enumerator:`LV_ALIGN_TOP_RIGHT`
- :cpp:enumerator:`LV_ALIGN_BOTTOM_LEFT`
- :cpp:enumerator:`LV_ALIGN_BOTTOM_MID`
- :cpp:enumerator:`LV_ALIGN_BOTTOM_RIGHT`
- :cpp:enumerator:`LV_ALIGN_LEFT_MID`
- :cpp:enumerator:`LV_ALIGN_RIGHT_MID`
- :cpp:enumerator:`LV_ALIGN_CENTER`
See illustration below to visualize what these mean.
It's quite common to align a child to the center of its parent,
therefore a dedicated function exists:
.. code-block:: c
lv_obj_center(widget);
//Has the same effect
lv_obj_align(widget, LV_ALIGN_CENTER, 0, 0);
If the parent's size changes, the set alignment and position of the
children is updated automatically.
Relative to another Widget
~~~~~~~~~~~~~~~~~~~~~~~~~~
Alternately, you can choose an "anchor" on another Widget.
.. code-block:: c
lv_obj_align_to(widget, reference_widget, align, x, y);
where ``align`` can be done of the following:
- :cpp:enumerator:`LV_ALIGN_OUT_TOP_LEFT`
- :cpp:enumerator:`LV_ALIGN_OUT_TOP_MID`
- :cpp:enumerator:`LV_ALIGN_OUT_TOP_RIGHT`
- :cpp:enumerator:`LV_ALIGN_OUT_BOTTOM_LEFT`
- :cpp:enumerator:`LV_ALIGN_OUT_BOTTOM_MID`
- :cpp:enumerator:`LV_ALIGN_OUT_BOTTOM_RIGHT`
- :cpp:enumerator:`LV_ALIGN_OUT_LEFT_TOP`
- :cpp:enumerator:`LV_ALIGN_OUT_LEFT_MID`
- :cpp:enumerator:`LV_ALIGN_OUT_LEFT_BOTTOM`
- :cpp:enumerator:`LV_ALIGN_OUT_RIGHT_TOP`
- :cpp:enumerator:`LV_ALIGN_OUT_RIGHT_MID`
- :cpp:enumerator:`LV_ALIGN_OUT_RIGHT_BOTTOM`
Example: to horizontally center a label 10 pixels above a button:
.. code-block:: c
lv_obj_align_to(label, btn, LV_ALIGN_OUT_TOP_MID, 0, -10);
Note that, unlike with :cpp:func:`lv_obj_align`, :cpp:func:`lv_obj_align_to`
does not remember the "anchor" used, and so will not automatically reposition
the aligned widget if the reference widget later moves.
The following illustration shows the meaning of each "anchor" mentioned above.
.. image:: /_static/images/align.png
.. _coord_size:
Size
****
Sizing the simple way
---------------------
The width and the height of a Widget can be set easily as well:
.. code-block:: c
lv_obj_set_width(widget, 200); /* Separate... */
lv_obj_set_height(widget, 100);
lv_obj_set_size(widget, 200, 100); /* Or in one function */
Percentage values are calculated based on the parent's content area
size. For example to set the Widget's height to the screen height:
.. code-block:: c
lv_obj_set_height(widget, lv_pct(100));
The size settings support a special value: :c:macro:`LV_SIZE_CONTENT`. It means
the Widget's size in the respective direction will be set to the size of
its children. Note that only children on the right and bottom sides will
be considered and children on the top and left remain cropped. This
limitation makes the behavior more predictable.
Widgets with :cpp:enumerator:`LV_OBJ_FLAG_HIDDEN` or :cpp:enumerator:`LV_OBJ_FLAG_FLOATING` will be
ignored by the :c:macro:`LV_SIZE_CONTENT` calculation.
The above functions set the size of a Widget's bounding box but the
size of the content area can be set as well. This means a Widget's
bounding box will be enlarged with the addition of padding.
.. code-block:: c
lv_obj_set_content_width(widget, 50); /* The actual width: padding left + 50 + padding right */
lv_obj_set_content_height(widget, 30); /* The actual width: padding top + 30 + padding bottom */
The size of the bounding box and the content area can be retrieved with
the following functions:
.. code-block:: c
int32_t w = lv_obj_get_width(widget);
int32_t h = lv_obj_get_height(widget);
int32_t content_w = lv_obj_get_content_width(widget);
int32_t content_h = lv_obj_get_content_height(widget);
.. _extending_click_area:
Extending the click area
------------------------
By default, Widgets can be clicked only within their bounding area. However,
especially with small Widgets, it can be helpful to make a Widget's "clickable" area
larger. You can do this with :cpp:expr:`lv_obj_set_ext_click_area(widget, size)`.
.. _coord_using_styles:
Using styles
************
Under the hood the position, size and alignment properties are style
properties. The above described "simple functions" hide the style
related code for the sake of simplicity and set the position, size, and
alignment properties in the :ref:`local styles <style_local>` of the Widget.
However, using styles to set the coordinates has some great advantages:
- It makes it easy to set the width/height/etc. for several Widgets
together. E.g. make all the sliders 100x10 pixels sized.
- It also makes possible to modify the values in one place.
- The values can be partially overwritten by other styles. For example
``style_btn`` makes the Widget ``100x50`` by default but adding
``style_full_width`` overwrites only the width of the Widget.
- The Widget can have different position or size depending on state.
E.g. 100 px wide in :cpp:enumerator:`LV_STATE_DEFAULT` but 120 px
in :cpp:enumerator:`LV_STATE_PRESSED`.
- Style transitions can be used to make the coordinate changes smooth.
Here are some examples to set a Widget's size using a style:
.. code-block:: c
static lv_style_t style;
lv_style_init(&style);
lv_style_set_width(&style, 100);
lv_obj_t * btn = lv_button_create(lv_screen_active());
lv_obj_add_style(btn, &style, LV_PART_MAIN);
As you will see below there are some other great features of size and
position setting. However, to keep the LVGL API lean, only the most
common coordinate setting features have a "simple" version and the more
complex features can be used via styles.
.. _coord_translation:
Translation
***********
Let's say the there are 3 buttons next to each other. Their position is
set as described above. Now you want to move a button up a little when
it's pressed.
One way to achieve this is by setting a new Y coordinate for the pressed
state:
.. code-block:: c
static lv_style_t style_normal;
lv_style_init(&style_normal);
lv_style_set_y(&style_normal, 100);
static lv_style_t style_pressed;
lv_style_init(&style_pressed);
lv_style_set_y(&style_pressed, 80);
lv_obj_add_style(btn1, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn1, &style_pressed, LV_STATE_PRESSED);
lv_obj_add_style(btn2, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn2, &style_pressed, LV_STATE_PRESSED);
lv_obj_add_style(btn3, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn3, &style_pressed, LV_STATE_PRESSED);
This works, but it's not really flexible because the pressed coordinate
is hard-coded. If the buttons are not at y=100, ``style_pressed`` won't
work as expected. Translations can be used to solve this:
.. code-block:: c
static lv_style_t style_normal;
lv_style_init(&style_normal);
lv_style_set_y(&style_normal, 100);
static lv_style_t style_pressed;
lv_style_init(&style_pressed);
lv_style_set_translate_y(&style_pressed, -20);
lv_obj_add_style(btn1, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn1, &style_pressed, LV_STATE_PRESSED);
lv_obj_add_style(btn2, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn2, &style_pressed, LV_STATE_PRESSED);
lv_obj_add_style(btn3, &style_normal, LV_STATE_DEFAULT);
lv_obj_add_style(btn3, &style_pressed, LV_STATE_PRESSED);
Translation is applied from the current position of the Widget.
Percentage values can be used in translations as well. The percentage is
relative to the size of the Widget (and not to the size of the parent).
For example :cpp:expr:`lv_pct(50)` will move the Widget with half of its
width/height.
The translation is applied after the layouts are calculated. Therefore,
even laid out Widgets' position can be translated.
The translation actually moves the Widget. That means it makes the
scrollbars and :c:macro:`LV_SIZE_CONTENT` sized Widgets react to the position
change.
.. _coord_transformation:
Transformation
**************
Similarly to position, a Widget's size can be changed relative to the
current size as well. The transformed width and height are added on both
sides of the Widget. This means a 10 px transformed width makes the
Widget 2x10 pixels wider.
Unlike position translation, the size transformation doesn't make the
Widget "really" larger. In other words scrollbars, layouts, and
:c:macro:`LV_SIZE_CONTENT` will not react to the transformed size. Hence, size
transformation is "only" a visual effect.
This code enlarges a button when it's pressed:
.. code-block:: c
static lv_style_t style_pressed;
lv_style_init(&style_pressed);
lv_style_set_transform_width(&style_pressed, 10);
lv_style_set_transform_height(&style_pressed, 10);
lv_obj_add_style(btn, &style_pressed, LV_STATE_PRESSED);
.. _coord_min_max_size:
Min and Max size
----------------
Similarly to CSS, LVGL also supports ``min-width``, ``max-width``,
``min-height`` and ``max-height``. These are limits preventing a
Widget's size from becoming smaller/larger than these values. They are
especially useful if the size is set by percentage or
:c:macro:`LV_SIZE_CONTENT`.
.. code-block:: c
static lv_style_t style_max_height;
lv_style_init(&style_max_height);
lv_style_set_max_height(&style_max_height, 200);
lv_obj_set_height(widget, lv_pct(100));
lv_obj_add_style(widget, &style_max_height, LV_STATE_DEFAULT); //Limit the height to 200 px
Percentage values can be used as well which are relative to the size of
the parent's content area.
.. code-block:: c
static lv_style_t style_max_height;
lv_style_init(&style_max_height);
lv_style_set_max_height(&style_max_height, lv_pct(50));
lv_obj_set_height(widget, lv_pct(100));
lv_obj_add_style(widget, &style_max_height, LV_STATE_DEFAULT); //Limit the height to half parent height
.. _coord_layout:
Layout
******
Layout overview
---------------
Layouts can update the position and size of a Widget's children. They
can be used to automatically arrange the children into a line or column,
or in much more complicated forms.
The position and size set by the layout overwrites the "normal" x, y,
width, and height settings.
There is only one function that is the same for every layout:
:cpp:func:`lv_obj_set_layout` ``(widget, <LAYOUT_NAME>)`` sets the layout on a Widget.
For further settings of the parent and children see the documentation of
the given layout.
Built-in layouts
----------------
LVGL comes with two very powerful layouts:
* Flexbox: arrange Widgets into rows or columns, with support for wrapping and expanding items.
* Grid: arrange Widgets into fixed positions in 2D table.
Both are heavily inspired by the CSS layouts with the same name.
Layouts are described in detail in their own section of documentation.
Flags
-----
There are some flags that can be used on Widgets to affect how they
behave with layouts:
- :cpp:enumerator:`LV_OBJ_FLAG_HIDDEN` Hidden Widgets are ignored in layout calculations.
- :cpp:enumerator:`LV_OBJ_FLAG_IGNORE_LAYOUT` The Widget is simply ignored by the layouts. Its coordinates can be set as usual.
- :cpp:enumerator:`LV_OBJ_FLAG_FLOATING` Same as :cpp:enumerator:`LV_OBJ_FLAG_IGNORE_LAYOUT` but the Widget with :cpp:enumerator:`LV_OBJ_FLAG_FLOATING` will be ignored in :c:macro:`LV_SIZE_CONTENT` calculations.
These flags can be added/removed with :cpp:expr:`lv_obj_add_flag(widget, FLAG)` and :cpp:expr:`lv_obj_remove_flag(widget, FLAG)`
Adding new layouts
------------------
LVGL can be freely extended by a custom layout like this:
.. code-block:: c
uint32_t MY_LAYOUT;
...
MY_LAYOUT = lv_layout_register(my_layout_update, &user_data);
...
void my_layout_update(lv_obj_t * widget, void * user_data)
{
/* Will be called automatically if it's required to reposition/resize the children of "obj" */
}
Custom style properties can be added which can be retrieved and used in
the update callback. For example:
.. code-block:: c
uint32_t MY_PROP;
...
LV_STYLE_MY_PROP = lv_style_register_prop();
...
static inline void lv_style_set_my_prop(lv_style_t * style, uint32_t value)
{
lv_style_value_t v = {
.num = (int32_t)value
};
lv_style_set_prop(style, LV_STYLE_MY_PROP, v);
}
.. _coord_example:
Examples
********
.. _coord_api:
API
***

View File

@@ -0,0 +1,300 @@
.. _events:
======
Events
======
Events are triggered in LVGL when something happens which might be
interesting to the user, e.g. when a Widget:
- is clicked
- is scrolled
- has its value changed
- is redrawn, etc.
Besides Widgets, events can registered from displays and input devices as well.
It is not detailed below, but you can do this by changing the prefix of the functions
from ``lv_obj_`` to ``lv_display_`` or ``lv_indev_``.
.. _adding_events_to_a_widget:
Adding Events to a Widget
*************************
The user can assign callback functions to a widget to process events.
In practice, it looks like this:
.. code-block:: c
lv_obj_t * btn = lv_button_create(lv_screen_active());
lv_obj_add_event_cb(btn, my_event_cb, LV_EVENT_CLICKED, user_data); /* Assign an event callback */
...
static void my_event_cb(lv_event_t * event)
{
printf("Clicked\n");
}
In the example :cpp:enumerator:`LV_EVENT_CLICKED` means that only the click event will
call ``my_event_cb``. See the :ref:`list of event codes <events_codes>` for
all the options. :cpp:enumerator:`LV_EVENT_ALL` can be used to receive all events.
The last parameter of :cpp:func:`lv_obj_add_event_cb` is a pointer to any custom
data that will be available in the event. NULL may be passed for this argument if
there is no need to use that data when the event is processed. You can retrieve the
pointer passed when setting the callback function like this:
.. code-block:: c
my_user_data_t * user_data;
...
user_data = lv_event_get_user_data(e);
More events can be added to a Widget, like this:
.. code-block:: c
lv_obj_add_event_cb(widget, my_event_cb_1, LV_EVENT_CLICKED, NULL);
lv_obj_add_event_cb(widget, my_event_cb_2, LV_EVENT_PRESSED, NULL);
lv_obj_add_event_cb(widget, my_event_cb_3, LV_EVENT_ALL, NULL); /* No filtering, receive all events */
Even the same event callback can be used on a Widget with different
``user_data``. For example:
.. code-block:: c
lv_obj_add_event_cb(widget, increment_on_click, LV_EVENT_CLICKED, &num1);
lv_obj_add_event_cb(widget, increment_on_click, LV_EVENT_CLICKED, &num2);
The events will be called in the order as they were added.
Other Widgets can use the same *event callback*.
In the very same way, events can be attached to input devices and displays like this:
.. code-block:: c
lv_display_add_event_cb(disp, event_cb, LV_EVENT_RESOLUTION_CHANGED, NULL);
lv_indev_add_event_cb(indev, event_cb, LV_EVENT_CLICKED, NULL);
Removing Event(s) from Widgets
******************************
.. code-block:: c
uint32_t i;
uint32_t event_cnt = lv_obj_get_event_count(widget);
for(i = 0; i < event_cnt; i++) {
lv_event_dsc_t * event_dsc = lv_obj_get_event_dsc(widget, i);
if(lv_event_dsc_get_cb(event_dsc) == some_event_cb) {
lv_obj_remove_event(widget, i);
break;
}
}
.. _events_codes:
Event Codes
***********
The event codes can be grouped into these categories: - Input device
events - Drawing events - Other events - Special events - Custom events
All Widgets (such as Buttons/Labels/Sliders etc.) regardless their type
receive the *Input device*, *Drawing* and *Other* events.
However, the *Special events* are specific to a particular widget type.
See the :ref:`widgets' documentation <widgets>` to learn when they
are sent,
*Custom events* are added by the user and are never sent by LVGL.
The following event codes exist:
Input Device Events
-------------------
- :cpp:enumerator:`LV_EVENT_PRESSED`: Widget has been pressed
- :cpp:enumerator:`LV_EVENT_PRESSING`: Widget is being pressed (called continuously while pressing)
- :cpp:enumerator:`LV_EVENT_PRESS_LOST`: Widget is still being pressed but slid cursor/finger off Widget
- :cpp:enumerator:`LV_EVENT_SHORT_CLICKED`: Widget was pressed for a short period of time, and then released without scrolling.
- :cpp:enumerator:`LV_EVENT_SINGLE_CLICKED`: Widget was pressed for a short period of time, and then released without scrolling, for the first time in a click streak. A click streak refers to multiple short clicks within a short period of time and a small distance.
- :cpp:enumerator:`LV_EVENT_DOUBLE_CLICKED`: Widget was pressed for a short period of time, and then released without scrolling, for the second time in a click streak.
- :cpp:enumerator:`LV_EVENT_TRIPLE_CLICKED`: Widget was pressed for a short period of time, and then released without scrolling, for the third time in a click streak.
- :cpp:enumerator:`LV_EVENT_LONG_PRESSED`: Widget has been pressed for at least `long_press_time`. Not called if scrolled.
- :cpp:enumerator:`LV_EVENT_LONG_PRESSED_REPEAT`: Called after `long_press_time` in every `long_press_repeat_time` ms. Not called if scrolled.
- :cpp:enumerator:`LV_EVENT_CLICKED`: Called on release if not scrolled (regardless of long press)
- :cpp:enumerator:`LV_EVENT_RELEASED`: Called in every cases when Widget has been released
- :cpp:enumerator:`LV_EVENT_SCROLL_BEGIN`: Scrolling begins. The event parameter is a pointer to the animation of the scroll. Can be modified
- :cpp:enumerator:`LV_EVENT_SCROLL_THROW_BEGIN`:
- :cpp:enumerator:`LV_EVENT_SCROLL_END`: Scrolling ends
- :cpp:enumerator:`LV_EVENT_SCROLL`: Scrolling
- :cpp:enumerator:`LV_EVENT_GESTURE`: A gesture is detected. Get the gesture with :cpp:expr:`lv_indev_get_gesture_dir(lv_indev_active())`
- :cpp:enumerator:`LV_EVENT_KEY`: A key is sent to Widget. Get the key with :cpp:expr:`lv_indev_get_key(lv_indev_active())`
- :cpp:enumerator:`LV_EVENT_FOCUSED`: Widget received focus
- :cpp:enumerator:`LV_EVENT_DEFOCUSED`: Widget is defocused
- :cpp:enumerator:`LV_EVENT_LEAVE`: Widget is defocused but still selected
- :cpp:enumerator:`LV_EVENT_HIT_TEST`: Perform advanced hit-testing
- :cpp:enumerator:`LV_EVENT_INDEV_RESET`: Indev has been reset
- :cpp:enumerator:`LV_EVENT_HOVER_OVER`: Indev hover over Widget
- :cpp:enumerator:`LV_EVENT_HOVER_LEAVE`: Indev hover leave Widget
Drawing Events
--------------
- :cpp:enumerator:`LV_EVENT_COVER_CHECK`: Check if Widget fully covers an area. The event parameter is :cpp:type:`lv_cover_check_info_t` ``*``.
- :cpp:enumerator:`LV_EVENT_REFR_EXT_DRAW_SIZE`: Get the required extra draw area around Widget (e.g. for shadow). The event parameter is :cpp:type:`int32_t` ``*`` to store the size.
- :cpp:enumerator:`LV_EVENT_DRAW_MAIN_BEGIN`: Starting the main drawing phase
- :cpp:enumerator:`LV_EVENT_DRAW_MAIN`: Perform the main drawing
- :cpp:enumerator:`LV_EVENT_DRAW_MAIN_END`: Finishing the main drawing phase
- :cpp:enumerator:`LV_EVENT_DRAW_POST_BEGIN`: Starting the post draw phase (when all children are drawn)
- :cpp:enumerator:`LV_EVENT_DRAW_POST`: Perform the post draw phase (when all children are drawn)
- :cpp:enumerator:`LV_EVENT_DRAW_POST_END`: Finishing the post draw phase (when all children are drawn)
- :cpp:enumerator:`LV_EVENT_DRAW_TASK_ADDED`: Adding a draw task
.. attention::
In drawing-event callback functions the rendering
sequence has already begun, and during this period, making changes to any
Widget's attributes, styles, or creating/deleting widgets is forbidden. If you attempt to do so,
LVGL will generate an assertion failure with a message
indicating that invalidating an area is not allowed during rendering.
Special Events
--------------
- :cpp:enumerator:`LV_EVENT_VALUE_CHANGED`: Widget's value has changed (i.e. slider moved)
- :cpp:enumerator:`LV_EVENT_INSERT`: A text is inserted to Widget. The event data is ``char *`` being inserted.
- :cpp:enumerator:`LV_EVENT_REFRESH`: Notify Widget to refresh something on it (for the user)
- :cpp:enumerator:`LV_EVENT_READY`: A process has finished
- :cpp:enumerator:`LV_EVENT_CANCEL`: A process has been cancelled
Other Events
------------
- :cpp:enumerator:`LV_EVENT_CREATE`: Widget is being created
- :cpp:enumerator:`LV_EVENT_DELETE`: Widget is being deleted
- :cpp:enumerator:`LV_EVENT_CHILD_CHANGED`: Child was removed, added, or its size, position were changed
- :cpp:enumerator:`LV_EVENT_CHILD_CREATED`: Child was created, always bubbles up to all parents
- :cpp:enumerator:`LV_EVENT_CHILD_DELETED`: Child was deleted, always bubbles up to all parents
- :cpp:enumerator:`LV_EVENT_SCREEN_UNLOAD_START`: A screen unload started, fired immediately when scr_load is called
- :cpp:enumerator:`LV_EVENT_SCREEN_LOAD_START`: A screen load started, fired when the screen change delay is expired
- :cpp:enumerator:`LV_EVENT_SCREEN_LOADED`: A screen was loaded
- :cpp:enumerator:`LV_EVENT_SCREEN_UNLOADED`: A screen was unloaded
- :cpp:enumerator:`LV_EVENT_SIZE_CHANGED`: Widget coordinates/size have changed
- :cpp:enumerator:`LV_EVENT_STYLE_CHANGED`: Widget's style has changed
- :cpp:enumerator:`LV_EVENT_LAYOUT_CHANGED`: The children position has changed due to a layout recalculation
- :cpp:enumerator:`LV_EVENT_GET_SELF_SIZE`: Get the internal size of a widget
Display Events
--------------
.. include:: ../main-modules/display/display_events_list.txt
Custom Events
-------------
Any number of custom event codes can be registered by
``uint32_t MY_EVENT_1 =`` :cpp:func:`lv_event_register_id`
They can be sent to any Widget with
:cpp:expr:`lv_obj_send_event(widget, MY_EVENT_1, &some_data)`
Refresh Event
-------------
:cpp:enumerator:`LV_EVENT_REFRESH` is a special event because it's designed to let the
user notify a Widget to refresh itself. Some examples:
- notify a label to refresh its text according to one or more variables (e.g. current time)
- refresh a label when the language changes
- enable a button if some conditions are met (e.g. the correct PIN is entered)
- add/remove styles to/from a Widget if a limit is exceeded, etc
Sending Events Manually
***********************
To manually send events to a Widget, use
``lv_obj_send_event(widget, <EVENT_CODE>, &some_data)``.
For example, this can be used to manually close a message box by
simulating a button press (although there are simpler ways to do this):
.. code-block:: c
/* Simulate the press of the first button (indexes start from zero) */
uint32_t btn_id = 0;
lv_obj_send_event(mbox, LV_EVENT_VALUE_CHANGED, &btn_id);
The same works for display and input devices with
``lv_display_send_event(widget, <EVENT_CODE>, &some_data)`` and
``lv_indev_send_event(widget, <EVENT_CODE>, &some_data)``.
Fields of lv_event_t
********************
:cpp:type:`lv_event_t` is the only parameter passed to the event callback and it
contains all data about the event. The following values can be gotten from it:
- :cpp:expr:`lv_event_get_code(e)`: get the event code
- :cpp:expr:`lv_event_get_current_target(e)`: get Widget to which an event was sent. I.e. the Widget whose event handler is being called.
- :cpp:expr:`lv_event_get_target(e)`: get Widget that originally triggered the event (different from :cpp:func:`lv_event_get_target` if :ref:`event bubbling <event_bubbling>` is enabled)
- :cpp:expr:`lv_event_get_user_data(e)`: get the pointer passed as the last parameter of :cpp:func:`lv_obj_add_event_cb`.
- :cpp:expr:`lv_event_get_param(e)`: get the parameter passed as the last parameter of :cpp:func:`lv_obj_send_event_cb`
.. _event_bubbling:
Event Bubbling
**************
If :cpp:expr:`lv_obj_add_flag(widget, LV_OBJ_FLAG_EVENT_BUBBLE)` is enabled all
events will be sent to a Widget's parent as well. If the parent also has
:cpp:enumerator:`LV_OBJ_FLAG_EVENT_BUBBLE` enabled the event will be sent to its
parent, and so on.
The *target* parameter of the event is always the current target Widget,
not the original Widget. To get the original target call
:cpp:expr:`lv_event_get_target_obj(e)` in the event handler.
.. _events_examples:
Examples
********
.. include:: ../../examples/event/index.rst
.. _events_api:
API
***
.. API equals:
lv_cover_check_info_t
lv_display_add_event_cb
lv_event_get_code
lv_event_get_target_obj
lv_event_get_user_data
lv_event_register_id
LV_EVENT_RENDER_START
lv_event_t
lv_indev_active
lv_indev_add_event_cb
lv_indev_get_gesture_dir
lv_indev_get_key
lv_obj_add_event_cb
lv_obj_add_flag
lv_obj_get_event_count
lv_obj_remove_event
lv_obj_send_event

View File

@@ -0,0 +1,18 @@
.. _common_widget_features:
======================
Common Widget Features
======================
The following details apply to all types of Widgets.
.. toctree::
:maxdepth: 3
basics
coordinates
layers
styles/index
events
layouts/index
scrolling

View File

@@ -0,0 +1,87 @@
.. _layers:
======
Layers
======
When the term "layer" is used in LVGL documentation, it may refer to one of several
things:
1. for Widgets, the :ref:`layers_creation` creates a natural layering of Widgets;
2. in the context of pixel rendering (drawing), there are :ref:`draw_layers`;
3. permanent :ref:`display_screen_layers` are part of each :ref:`display` object.
#1 is covered below. #2 and #3 are covered in :ref:`draw_layers` and
:ref:`display_screen_layers` respectively.
.. _layers_creation:
Order of Creation
*****************
By default, LVGL draws new Widgets on top of old Widgets.
For example, assume we add a button to a parent Widget named button1 and
then another button named button2. Then button1 (along with its child
Widget(s)) will be in the background and can be covered by button2 and
its children.
.. image:: /_static/images/layers.png
.. code-block:: c
/* Create a screen */
lv_obj_t * scr = lv_obj_create(NULL);
lv_screen_load(scr); /* Load the screen */
/* Create 2 buttons */
lv_obj_t * btn1 = lv_button_create(scr); /* Create the first button on the screen */
lv_obj_set_pos(btn1, 60, 40); /* Set the position of the first button */
lv_obj_t * btn2 = lv_button_create(scr); /* Create the second button on the screen */
lv_obj_set_pos(btn2, 180, 80); /* Set the position of the second button */
/* Add labels to the buttons */
lv_obj_t * label1 = lv_label_create(btn1); /* Create a label on the first button */
lv_label_set_text(label1, "Button 1"); /* Set the text of the label */
lv_obj_t * label2 = lv_label_create(btn2); /* Create a label on the second button */
lv_label_set_text(label2, "Button 2"); /* Set the text of the label */
/* Delete the second label */
lv_obj_delete(label2);
.. _layers_order:
Changing Order
--------------
There are four explicit ways to bring a Widget to the foreground:
- Use :cpp:expr:`lv_obj_move_foreground(widget)` to bring a Widget to the foreground.
Similarly, use :cpp:expr:`lv_obj_move_background(widget)` to move it to the background.
- Use :cpp:expr:`lv_obj_move_to_index(widget, idx)` to move a Widget to a given index in the order of children.
- ``0``: background
- ``child_num - 1``: foreground
- ``< 0``: count from the top, to move forward (up): :cpp:expr:`lv_obj_move_to_index(widget, lv_obj_get_index(widget) - 1)`
- Use :cpp:expr:`lv_obj_swap(widget1, widget2)` to swap the relative layer position of two Widgets.
- When :cpp:expr:`lv_obj_set_parent(widget, new_parent)` is used, ``widget`` will be on the foreground of ``new_parent``.
.. _layers_api:
API
***
.. API equals:
lv_obj_move_foreground
lv_obj_move_to_index
lv_obj_swap
lv_obj_set_parent

View File

@@ -0,0 +1,231 @@
.. _flex:
====
Flex
====
Overview
********
The Flexbox (or Flex for short) is a subset of `CSS Flexbox`_ behaviors.
It can arrange items (child Widgets) into rows or columns (tracks), handle wrapping,
adjust the spacing between items and tracks, handle *grow* to make
item(s) fill remaining space with respect to min/max width and
height.
To make a Widget a Flex container call
:cpp:expr:`lv_obj_set_layout(widget, LV_LAYOUT_FLEX)`.
Note that the Flex layout feature of LVGL needs to be globally enabled
with :c:macro:`LV_USE_FLEX` in ``lv_conf.h``.
Terms
*****
- **tracks**: rows or columns
- **main direction**: row or column, the direction in which multiple items are
placed first
- **cross direction**: the direction perpendicular to the **main direction**
- **wrap**: if there is no more space in the track, a new track is started
- **grow**: if set on an item it will "grow" to fill the remaining space in
the track. The available space will be distributed among items
respective to their grow value (larger value means more space)
- **gap**: the space between rows and columns or the items on a track
See `CSS Flexbox`_ for illustrations showing the meanings of these terms.
Simple Interface
****************
Use the following functions to set and control the Flex layout on any parent Widget.
.. note::
The parent Widget must be a Flex container for these styles to take effect.
The functions below cause the parent Widget to become a Flex container if it is
not already.
.. _flex_flow:
Flex flow
---------
:cpp:expr:`lv_obj_set_flex_flow(widget, flex_flow)`
The possible values for ``flex_flow`` are:
- :cpp:enumerator:`LV_FLEX_FLOW_ROW`: Place the children in a row without wrapping
- :cpp:enumerator:`LV_FLEX_FLOW_COLUMN`: Place the children in a column without wrapping
- :cpp:enumerator:`LV_FLEX_FLOW_ROW_WRAP`: Place the children in a row with wrapping
- :cpp:enumerator:`LV_FLEX_FLOW_COLUMN_WRAP`: Place the children in a column with wrapping
- :cpp:enumerator:`LV_FLEX_FLOW_ROW_REVERSE`: Place the children in a row without wrapping but in reversed order
- :cpp:enumerator:`LV_FLEX_FLOW_COLUMN_REVERSE`: Place the children in a column without wrapping but in reversed order
- :cpp:enumerator:`LV_FLEX_FLOW_ROW_WRAP_REVERSE`: Place the children in a row with wrapping but in reversed order
- :cpp:enumerator:`LV_FLEX_FLOW_COLUMN_WRAP_REVERSE`: Place the children in a column with wrapping but in reversed order
These values cause the Widget's layout behavior to model `CSS Flexbox`_ behavior
by combining flex-direction_ and flex-wrap_ as defined under flex-flow_.
.. _flex_align:
Flex align
----------
To manage placement of children use
:cpp:expr:`lv_obj_set_flex_align(widget, main_place, cross_place, track_cross_place)`
which makes the parent Widget model the Flex-container behavior defined `here
<justify-content_>`_.
- ``main_place`` determines how to distribute the items in their track
on the main axis. E.g. flush the items to the right on
:cpp:enumerator:`LV_FLEX_FLOW_ROW_WRAP`. (It's called
justify-content_ in CSS.)
- ``cross_place`` determines how to distribute the items in their track
on the cross axis. E.g. if the items have different height, align them
against the bottom of the track. (It's called align-items_ in CSS.)
- ``track_cross_place`` determines how to distribute the tracks (It's
called align-content_ in CSS.)
The possible values are:
- :cpp:enumerator:`LV_FLEX_ALIGN_START`: means left when direction is horizontal, top when vertical (default)
- :cpp:enumerator:`LV_FLEX_ALIGN_END`: means right when direction is horizontal, bottom when vertical
- :cpp:enumerator:`LV_FLEX_ALIGN_CENTER`: simply center with respect to direction
- :cpp:enumerator:`LV_FLEX_ALIGN_SPACE_EVENLY`: items are distributed so
that the spacing between any two items (and the space to the edges) is
equal. Does not apply to ``track_cross_place``.
- :cpp:enumerator:`LV_FLEX_ALIGN_SPACE_AROUND`: items are evenly
distributed in the track with equal space around them. Note that
visually the spaces are not equal since all the items have equal space
on both sides. This makes the space between items double the space
between edge items and the container's edge. Does not apply to
``track_cross_place``.
- :cpp:enumerator:`LV_FLEX_ALIGN_SPACE_BETWEEN`: items are evenly distributed in
the track with no space before and after first and last items. Does not apply
to ``track_cross_place``.
See justify-content_, align-items_ and align-content_ for illustrations of these values.
.. _flex_grow:
Flex grow
---------
Flex grow can be used to make one or more children fill available space in the track.
When more than one child Widget have non-zero grow values, all available space will
be distributed in proportion to their respective grow values. For example, if there
is 400 px space remaining and 3 child Widgets with non-zero grow values:
- ``A`` with grow = 1
- ``B`` with grow = 1
- ``C`` with grow = 2
``A`` and ``B`` will occupy 100 px, and ``C`` will occupy 200 px.
Flex grow can be set on a child Widget with
:cpp:expr:`lv_obj_set_flex_grow(child, value)`. ``value`` needs to be >=
1 or 0 to disable grow on the child.
See flex-grow_ for an illustration of this behavior.
.. _flex_style:
Style Interface
***************
All Flex-related values are style properties under the hood so you
can use them as you would any other style property.
The following flex related style properties exist:
- :cpp:enumerator:`FLEX_FLOW`
- :cpp:enumerator:`FLEX_MAIN_PLACE`
- :cpp:enumerator:`FLEX_CROSS_PLACE`
- :cpp:enumerator:`FLEX_TRACK_PLACE`
- :cpp:enumerator:`FLEX_GROW`
.. _flex_padding:
Internal padding
----------------
To modify the minimum space flexbox inserts between Widgets, the
following functions can be used to set the flex container padding style:
- :cpp:func:`lv_style_set_pad_row` sets padding between rows.
- :cpp:func:`lv_style_set_pad_column` sets padding between columns.
These can, for example, be used if you do not want any padding between
Widgets: :cpp:expr:`lv_style_set_pad_column(&row_container_style, 0)`
.. _flex_other:
Other Features
**************
RTL
---
If the base direction of the container is set the
:cpp:enumerator:`LV_BASE_DIR_RTL` the meaning of
:cpp:enumerator:`LV_FLEX_ALIGN_START` and
:cpp:enumerator:`LV_FLEX_ALIGN_END` is swapped on ``ROW`` layouts. I.e.
``START`` will mean right.
The items on ``ROW`` layouts, and tracks of ``COLUMN`` layouts will be
placed from right to left.
New track
---------
You can force Flex to put an item into a new line with
:cpp:expr:`lv_obj_add_flag(child, LV_OBJ_FLAG_FLEX_IN_NEW_TRACK)`.
.. admonition:: Further Reading
Learn more about `CSS Flexbox`_.
.. _flex_examples:
Examples
********
.. include:: ../../../examples/layouts/flex/index.rst
.. Hyperlinks
.. _css flexbox: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
.. _flex-direction: https://css-tricks.com/snippets/css/a-guide-to-flexbox/#aa-flex-direction
.. _flex-wrap: https://css-tricks.com/snippets/css/a-guide-to-flexbox/#aa-flex-wrap
.. _flex-flow: https://css-tricks.com/snippets/css/a-guide-to-flexbox/#aa-flex-flow
.. _justify-content: https://css-tricks.com/snippets/css/a-guide-to-flexbox/#aa-justify-content
.. _align-items: https://css-tricks.com/snippets/css/a-guide-to-flexbox/#aa-align-items
.. _align-content: https://css-tricks.com/snippets/css/a-guide-to-flexbox/#aa-align-content
.. _flex-grow: https://css-tricks.com/snippets/css/a-guide-to-flexbox/#aa-flex-grow
.. _flex_api:
API
***

View File

@@ -0,0 +1,227 @@
.. _grid:
====
Grid
====
Overview
********
The Grid layout is a subset of `CSS Grid`_ layout.
It can arrange items (child Widgets) into a 2D "table" that has rows and columns
(tracks). An item can span multiple columns or rows. The
track's size can be set in pixels, to the largest item
(:c:macro:`LV_GRID_CONTENT`), or to a fraction of the available free space
(i.e. `Free [FR] Units <fr units_>`_) to distribute free space proportionally.
To make a Widget a Grid container call :cpp:expr:`lv_obj_set_layout(widget, LV_LAYOUT_GRID)`.
Note that the Grid layout feature of LVGL needs to be globally enabled
with :c:macro:`LV_USE_GRID` in ``lv_conf.h``.
Terms
*****
- **tracks**: rows or columns
- **free (FR) units**: if a track's size is set in ``FR units`` it will grow
to fill the remaining space in the parent Widget (container), in proportion with
other tracks that have non-zero FR-unit values.
- **gap**: the space between rows and columns or the items on a track
Simple Interface
****************
With the following functions you can cause any parent Widget to have Grid-layout behaviors.
.. note::
As with Flex containers, the parent Widget must be a Grid container for these
styles to take effect. The functions below cause the parent Widget to become a
Grid container if it is not already.
.. _grid_descriptors:
Grid descriptors
----------------
First you need to describe the size of rows and columns. It can be done
by declaring 2 arrays and the track sizes in them. The last element must
be :c:macro:`LV_GRID_TEMPLATE_LAST`.
For example:
.. code-block:: c
static int32_t column_dsc[] = {100, 400, LV_GRID_TEMPLATE_LAST}; /* 2 columns with 100- and 400-px width */
static int32_t row_dsc[] = {100, 100, 100, LV_GRID_TEMPLATE_LAST}; /* 3 100-px tall rows */
To set the descriptors on a parent use
:cpp:expr:`lv_obj_set_grid_dsc_array(widget, col_dsc, row_dsc)`.
Besides settings the sizes in pixels, you can use two special
values:
- :c:macro:`LV_GRID_CONTENT` sets size to fit the largest child on this track
- :cpp:expr:`LV_GRID_FR(X)` determines what portion of the remaining space
should be used by this track. Larger values means larger space.
.. _grid_items:
Grid items
----------
By default, a Widget's children are not added to the grid. They need to be
added manually to a cell.
To do this call
:cpp:expr:`lv_obj_set_grid_cell(child, column_align, column_pos, column_span, row_align, row_pos, row_span)`.
``column_align`` and ``row_align`` determine how to align the child Widget
in its cell. Possible values are:
- :cpp:enumerator:`LV_GRID_ALIGN_START`: means left when direction is horizontal and top when vertical (default)
- :cpp:enumerator:`LV_GRID_ALIGN_END`: means right when direction is horizontal and bottom when vertical
- :cpp:enumerator:`LV_GRID_ALIGN_CENTER`: simply center ``column_pos`` and ``row_pos``
means the zero-based index of the cell in which the item should be placed.
``column_span`` and ``row_span`` means how many tracks should be occupied
from the start cell. Must be ``>= 1``.
.. _grid_align:
Grid align
----------
If there is some empty space, items (Widgets) in Grid tracks can be aligned in several ways:
- :cpp:enumerator:`LV_GRID_ALIGN_START`: means left when direction is horizontal and top when vertical. (default)
- :cpp:enumerator:`LV_GRID_ALIGN_END`: means right when direction is horizontal and bottom when vertical
- :cpp:enumerator:`LV_GRID_ALIGN_CENTER`: simply center
- :cpp:enumerator:`LV_GRID_ALIGN_SPACE_EVENLY`: items are distributed so that the spacing
between any two items (and the space to the edges) is equal. Not applies to ``track_cross_place``.
- :cpp:enumerator:`LV_GRID_ALIGN_SPACE_AROUND`: items are
evenly distributed in the track with equal space around them. Note that
visually the spaces aren't equal, since all the items have equal space
on both sides. This makes the space between items double the space
between edge items and the container's edge. Does not apply to ``track_cross_place``.
- :cpp:enumerator:`LV_GRID_ALIGN_SPACE_BETWEEN`: items are
evenly distributed in the track with first and last items next to container's edges.
Does not apply to ``track_cross_place``.
To set the track's alignment use
:cpp:expr:`lv_obj_set_grid_align(widget, column_align, row_align)`.
.. _grid_subgrid:
Sub grid
--------
If you set the column and/or row grid descriptors of a widget to ``NULL`` it will use
the grid descriptor(s) from it's parent.
For example if you create a grid item that spans columns 2..6 columns and rows 1..3
of the grid, the grid item will occupy 5 columns and 4 rows with the corresponding
track size from the parent Grid container.
This way even if a wrapper item is used in the grid, it can be made "transparent"
from the grid's point of view.
Limitations:
- The sub-grid is resolved only to a depth of 1 level. That is, a grid can have a
sub-grid child, but that sub-grid cannot have another sub-grid.
- ``LV_GRID_CONTENT`` tracks on the grid are not handled in the sub-grid, only in its
own grid.
The sub-grid feature works the same as in CSS. For further information, see
`CSS Subgrid`_.
.. _grid_style:
Style Interface
***************
All the Grid-related values are style properties under the hood so you
can use them as you would any other style property.
The following Grid-related style properties exist:
- :cpp:enumerator:`GRID_COLUMN_DSC_ARRAY`
- :cpp:enumerator:`GRID_ROW_DSC_ARRAY`
- :cpp:enumerator:`GRID_COLUMN_ALIGN`
- :cpp:enumerator:`GRID_ROW_ALIGN`
- :cpp:enumerator:`GRID_CELL_X_ALIGN`
- :cpp:enumerator:`GRID_CELL_COLUMN_POS`
- :cpp:enumerator:`GRID_CELL_COLUMN_SPAN`
- :cpp:enumerator:`GRID_CELL_Y_ALIGN`
- :cpp:enumerator:`GRID_CELL_ROW_POS`
- :cpp:enumerator:`GRID_CELL_ROW_SPAN`
.. _grid_padding:
Internal padding
----------------
To modify the minimum space Grid inserts between Widgets, the following
properties can be set on the Grid container style:
- :cpp:func:`lv_style_set_pad_row` sets padding between rows.
- :cpp:func:`lv_style_set_pad_column` sets padding between columns.
.. _grid_other:
Other features
**************
RTL
---
If the base direction of the container is set to :cpp:enumerator:`LV_BASE_DIR_RTL`,
the meaning of :cpp:enumerator:`LV_GRID_ALIGN_START` and :cpp:enumerator:`LV_GRID_ALIGN_END` is
swapped. I.e. ``START`` will mean right-most.
The columns will be placed from right to left.
.. admonition:: Further Reading
- Learn more about `CSS Grid`_ layout.
- Learn more about `CSS Subgrid`_ layout.
.. _grid_examples:
Example
*******
.. include:: ../../../examples/layouts/grid/index.rst
.. Hyperlinks
.. _css grid: https://css-tricks.com/snippets/css/complete-guide-grid/
.. _fr units: https://css-tricks.com/introduction-fr-css-unit/
.. _css subgrid: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Subgrid
.. _grid_api:
API
***

View File

@@ -0,0 +1,12 @@
.. _layouts:
=======
Layouts
=======
.. toctree::
:maxdepth: 2
flex
grid

View File

@@ -0,0 +1,328 @@
.. _scrolling:
=========
Scrolling
=========
Overview
********
In LVGL scrolling works very intuitively: if a Widget is outside its
parent content area (the size without padding), the parent becomes
scrollable and scrollbar(s) will appear. That's it.
Any Widget can be scrollable including :ref:`base_widget`, ``lv_image``,
``lv_button``, ``lv_meter``, etc
The Widget can either be scrolled horizontally or vertically in one
stroke; diagonal scrolling is not possible.
Scrollbar
---------
Mode
~~~~
Scrollbars are displayed according to the configured ``scrollbar-mode``. The
following modes are available:
- :cpp:enumerator:`LV_SCROLLBAR_MODE_OFF`: Never show the scrollbars
- :cpp:enumerator:`LV_SCROLLBAR_MODE_ON`: Always show the scrollbars
- :cpp:enumerator:`LV_SCROLLBAR_MODE_ACTIVE`: Show scroll bars while a Widget is being scrolled
- :cpp:enumerator:`LV_SCROLLBAR_MODE_AUTO`: Show scroll bars when the content is large enough to be scrolled
:cpp:expr:`lv_obj_set_scrollbar_mode(widget, LV_SCROLLBAR_MODE_...)` sets the scrollbar mode on a Widget.
Styling
~~~~~~~
A Scrollbar is a dedicated part of a Widget, called
:cpp:enumerator:`LV_PART_SCROLLBAR`. For example, a scrollbar can turn to red like
this:
.. code-block:: c
static lv_style_t style_red;
lv_style_init(&style_red);
lv_style_set_bg_color(&style_red, lv_color_red());
...
lv_obj_add_style(widget, &style_red, LV_PART_SCROLLBAR);
A Widget goes to the :cpp:enumerator:`LV_STATE_SCROLLED` state while it's being
scrolled. This allows adding different styles to the Widget that will be effective
while it is being scrolled. For example, this code makes the scrollbar blue while
the Widget is being scrolled:
.. code-block:: c
static lv_style_t style_blue;
lv_style_init(&style_blue);
lv_style_set_bg_color(&style_blue, lv_color_blue());
...
lv_obj_add_style(widget, &style_blue, LV_STATE_SCROLLED | LV_PART_SCROLLBAR);
If the base direction of the :cpp:enumerator:`LV_PART_SCROLLBAR` is RTL
(:c:macro:`LV_BASE_DIR_RTL`) the vertical scrollbar will be placed on the left.
Note that, the ``base_dir`` style property is inherited. Therefore, it
can be set directly on the :cpp:enumerator:`LV_PART_SCROLLBAR` part of a Widget, or
on the Widget's LV_PART_MAIN part, or that of any of its parents, to make a scrollbar
inherit the base direction.
``pad_left/right/top/bottom`` sets the spacing around the scrollbars,
``width`` sets the scrollbar's width and ``length`` sets the scrollbar's length:
If `length` is not set or left at `0` the scrollbar's length will be set automatically according to the length of the content.
.. code-block:: c
static lv_style_t style_scrollbar;
lv_style_init(&style_scrollbar);
lv_style_set_pad_left(&style_scrollbar, 2);
lv_style_set_pad_right(&style_scrollbar, 2);
lv_style_set_pad_top(&style_scrollbar, 2);
lv_style_set_pad_bottom(&style_scrollbar, 2);
lv_style_set_width(&style_scrollbar, 10);
lv_style_set_length(&style_scrollbar, 50);
...
lv_obj_add_style(widget, &style_scrollbar, LV_PART_SCROLLBAR);
The minimum length of the scrollbar is fixed to 10, while its maximum length is limited by the
Widget's height or width, depending on whether the scrollbar is vertical or horizontal. Any length value
set outside of these limits will automatically result in a length fixed to either limit.
.. _scroll_events:
Scrolling Events
----------------
The following events are emitted as part of scrolling:
- :cpp:enumerator:`LV_EVENT_SCROLL_BEGIN`: Signals that scrolling has begun. The
event parameter is ``NULL`` or an ``lv_anim_t *`` with a scroll animation
descriptor that can be modified if required.
- :cpp:enumerator:`LV_EVENT_SCROLL_END`: Signals that scrolling has ended.
- :cpp:enumerator:`LV_EVENT_SCROLL`: Signals that the scrolling position changed;
triggered on every position change.
Features of Scrolling
*********************
Besides, managing "normal" scrolling there are many interesting and
useful additional features.
Scrollable
----------
It is possible to make a Widget non-scrollable with
:cpp:expr:`lv_obj_remove_flag(widget, LV_OBJ_FLAG_SCROLLABLE)`.
Non-scrollable Widgets can still propagate the scrolling (chain) to
their parents.
The direction in which scrolling happens can be controlled by
:cpp:expr:`lv_obj_set_scroll_dir(widget, LV_DIR_...)`.
The following values can be used for the direction:
- :cpp:enumerator:`LV_DIR_TOP`: only scroll up
- :cpp:enumerator:`LV_DIR_LEFT`: only scroll left
- :cpp:enumerator:`LV_DIR_BOTTOM`: only scroll down
- :cpp:enumerator:`LV_DIR_RIGHT`: only scroll right
- :cpp:enumerator:`LV_DIR_HOR`: only scroll horizontally
- :cpp:enumerator:`LV_DIR_VER`: only scroll vertically
- :cpp:enumerator:`LV_DIR_ALL`: scroll any directions
OR-ed values are also possible. E.g. :cpp:expr:`LV_DIR_TOP | LV_DIR_LEFT`.
Scroll chaining
---------------
If a Widget can't be scrolled further (e.g. its content has reached the
bottom-most position), additional scrolling is propagated to its parent.
If the parent can be scrolled in that direction than it will be scrolled
instead. It continues propagating up the Widget's parent hierarchy up to
the :ref:`Screen <screens>`.
The propagation on scrolling is called "scroll chaining" and it can be
enabled/disabled with ``LV_OBJ_FLAG_SCROLL_CHAIN_HOR/VER`` flag. If
chaining is disabled the propagation stops on the Widget and the
parent(s) won't be scrolled.
Scroll momentum
---------------
When the user scrolls a Widget and releases it, LVGL can emulate
inertial momentum for the scrolling. It's like the Widget was "thrown"
and scrolling slows down smoothly.
Scroll momentum can be enabled/disabled with the
:cpp:enumerator:`LV_OBJ_FLAG_SCROLL_MOMENTUM` flag.
Elastic scroll
--------------
Normally a Widget can't be scrolled past the extremities of its
content. That is, the top side of the content can't be below the top side
of the Widget, and vice versa for the bottom side.
However, with :cpp:enumerator:`LV_OBJ_FLAG_SCROLL_ELASTIC` a fancy effect is added
when the user "over-scrolls" the content. The scrolling slows down, and
the content can be scrolled inside the Widget. When the Widget is
released the content scrolled in it is animated back to the closest valid
position.
Snapping
--------
The children of a Widget can be snapped according to specific rules
when scrolling ends. Children can be made snappable individually with
the :cpp:enumerator:`LV_OBJ_FLAG_SNAPPABLE` flag.
A Widget can align snapped children in four ways:
- :cpp:enumerator:`LV_SCROLL_SNAP_NONE`: Snapping is disabled. (default)
- :cpp:enumerator:`LV_SCROLL_SNAP_START`: Align the children to the left/top side of a scrolled Widget
- :cpp:enumerator:`LV_SCROLL_SNAP_END`: Align the children to the right/bottom side of a scrolled Widget
- :cpp:enumerator:`LV_SCROLL_SNAP_CENTER`: Align the children to the center of a scrolled Widget
Snap alignment is set with
:cpp:expr:`lv_obj_set_scroll_snap_x(widget, LV_SCROLL_SNAP_...)` and
:cpp:expr:`lv_obj_set_scroll_snap_y(widget, LV_SCROLL_SNAP_...)`.
This is what happens under the hood:
1. user scrolls and releases a Widget;
2. LVGL calculates where the scroll would end considering scroll momentum;
3. LVGL finds the nearest scroll point;
4. LVGL scrolls to the snap point with an animation.
Scroll one
----------
The "scroll one" feature tells LVGL to allow scrolling only one
snappable child at a time. This requires making the children snappable
and setting scroll snap alignment to something other than
:cpp:enumerator:`LV_SCROLL_SNAP_NONE`.
This feature can be enabled by the :cpp:enumerator:`LV_OBJ_FLAG_SCROLL_ONE` flag.
Scroll on focus
---------------
Imagine that there are a lot of Widgets in a group that are on a scrollable
Widget. Pressing the "Tab" button moves focus to the next Widget but it might
be outside the visible area of the scrollable Widget. If the "scroll on
focus" feature is enabled LVGL will automatically scroll Widgets to
bring the child Widget with focus into view. The scrolling happens recursively
therefore even nested scrollable Widgets are handled properly. The
Widget will be scrolled into view even if it is on a different page of a
tabview.
Scrolling Programmatically
**************************
The following API functions allow programmatic scrolling of Widgets:
- ``lv_obj_scroll_by(widget, x, y, LV_ANIM_ON/OFF)`` scroll by ``x`` and ``y`` values
- ``lv_obj_scroll_to(widget, x, y, LV_ANIM_ON/OFF)`` scroll to bring the given coordinate to the top left corner
- ``lv_obj_scroll_to_x(widget, x, LV_ANIM_ON/OFF)`` scroll to bring the given coordinate to the left side
- ``lv_obj_scroll_to_y(widget, y, LV_ANIM_ON/OFF)`` scroll to bring the given coordinate to the top side
From time to time you may need to retrieve the *scroll position* of a
scrollable Widget, either to restore it later, or to dynamically display some
elements according to its current scroll position. Here is an example to illustrate
how to combine scroll event and store the scroll-top position.
.. code-block:: c
static int scroll_value = 0;
static void store_scroll_top_value_event_cb(lv_event_t* e) {
lv_obj_t * scr = lv_event_get_target(e);
scroll_value = lv_obj_get_scroll_top(scr);
printf("%d pixels are scrolled above top edge of display.\n", scroll_value);
}
lv_obj_t * scr = lv_obj_create(NULL);
lv_obj_add_event_cb(scr, store_scroll_top_value_event_cb, LV_EVENT_SCROLL, NULL);
Scroll coordinates can be retrieved from different axes with these functions:
- :cpp:expr:`lv_obj_get_scroll_x(widget)` Pixels scrolled past left edge of Widget's view window.
- :cpp:expr:`lv_obj_get_scroll_y(widget)` Pixels scrolled past top of Widget's view window.
- :cpp:expr:`lv_obj_get_scroll_top(widget)` Identical to :cpp:expr:`lv_obj_get_scroll_y(widget)`
- :cpp:expr:`lv_obj_get_scroll_bottom(widget)` Pixels scrolled past bottom of Widget's view window.
- :cpp:expr:`lv_obj_get_scroll_left(widget)` Identical to :cpp:expr:`lv_obj_get_scroll_x(widget)`.
- :cpp:expr:`lv_obj_get_scroll_right(widget)` Pixels scrolled past right edge of Widget's view window.
Setting scroll position can be done with these functions:
- :cpp:expr:`lv_obj_scroll_by(widget, dx, dy, anim_enable)` Scroll by given amount of pixels.
- :cpp:expr:`lv_obj_scroll_by_bounded(widget, dx, dy, animation_enable)` Scroll by given amount of pixels.
- :cpp:expr:`lv_obj_scroll_to(widget, x, y, animation_enable)` Scroll to given coordinate on Widget.
- :cpp:expr:`lv_obj_scroll_to_x(widget, x, animation_enable)` Scroll to X coordinate on Widget.
- :cpp:expr:`lv_obj_scroll_to_y(widget, y, animation_enable)` Scroll to Y coordinate on Widget.
- :cpp:expr:`lv_obj_scroll_to_view(widget, animation_enable)` Scroll ``obj``'s parent Widget until ``obj`` becomes visible.
- :cpp:expr:`lv_obj_scroll_to_view_recursive(widget, animation_enable)` Scroll ``obj``'s parent Widgets recursively until ``obj`` becomes visible.
Self Size
*********
Self size is a property of a Widget. Normally, the user shouldn't use
this parameter but if a custom widget is created it might be useful.
In short, self size establishes the size of a Widget's content. To
understand it better take the example of a table. Let's say it has 10
rows each with 50 px height. So the total height of the content is 500
px. In other words the "self height" is 500 px. If the user sets only
200 px height for the table LVGL will see that the self size is larger
and make the table scrollable.
This means not only the children can make a Widget scrollable but a
larger self size will as well.
LVGL uses the :cpp:enumerator:`LV_EVENT_GET_SELF_SIZE` event to get the self size of
a Widget. Here is an example to see how to handle the event:
.. code-block:: c
if(event_code == LV_EVENT_GET_SELF_SIZE) {
lv_point_t * p = lv_event_get_param(e);
/* If x or y < 0 then it doesn't need to be calculated now. */
if(p->x >= 0) {
p->x = 200; /* Set or calculate self width. */
}
if(p->y >= 0) {
p->y = 50; /* Set or calculate self height. */
}
}
.. _scroll_example:
Examples
********
.. include:: ../../examples/scroll/index.rst
.. _scroll_api:
API
***

View File

@@ -0,0 +1,11 @@
.. _styles:
======
Styles
======
.. toctree::
:maxdepth: 2
styles
style-properties

View File

@@ -0,0 +1,598 @@
.. _styles_overview:
===============
Styles Overview
===============
Styles are used to set the appearance of Widgets. Styles in LVGL are
heavily inspired by CSS. The concept in a nutshell is that a
style is an :cpp:type:`lv_style_t` variable which can hold properties like
border width, font, text color and so on. It's similar to a ``class`` in CSS.
- Styles can be assigned to Widgets to change their appearance. Upon
assignment, the target part (pseudo-element_ in CSS) and target state
(pseudo-class_ in CSS) can be specified. For example one can add
``style_blue`` to the knob of a slider when it's in pressed state.
- The same style can be used by any number of Widgets.
- Styles can be cascaded which means multiple styles may be assigned to a Widget and
each style can have different properties. Therefore, not all properties
have to be specified in a style. LVGL will search for a property until a
style defines it or use a default value if it's not specified by any of the
styles. For example ``style_btn`` can result in a default gray button
and ``style_btn_red`` can add only a ``background-color=red`` to
overwrite the background color.
- The most recently added style has higher precedence. This means if a property
is specified in two styles the newest style in the Widget will be used.
- Some properties (e.g. text color) can be inherited from a parent(s) if it's not specified in a Widget.
- Widgets can also have :ref:`local styles <style_local>` with higher precedence than "normal" styles.
- Unlike CSS (where pseudo-classes_ describe different states, e.g. ``:focus``),
in LVGL a property is assigned to a given state.
- Transitions can be applied when the Widget changes state.
.. _style_states:
States
******
The Widgets can be in the combination of the following states:
- :cpp:enumerator:`LV_STATE_DEFAULT`: (0x0000) Normal, released state
- :cpp:enumerator:`LV_STATE_CHECKED`: (0x0001) Toggled or checked state
- :cpp:enumerator:`LV_STATE_FOCUSED`: (0x0002) Focused via keypad or encoder or clicked via touchpad/mouse
- :cpp:enumerator:`LV_STATE_FOCUS_KEY`: (0x0004) Focused via keypad or encoder but not via touchpad/mouse
- :cpp:enumerator:`LV_STATE_EDITED`: (0x0008) Edit by an encoder
- :cpp:enumerator:`LV_STATE_HOVERED`: (0x0010) Hovered by mouse
- :cpp:enumerator:`LV_STATE_PRESSED`: (0x0020) Being pressed
- :cpp:enumerator:`LV_STATE_SCROLLED`: (0x0040) Being scrolled
- :cpp:enumerator:`LV_STATE_DISABLED`: (0x0080) Disabled state
- :cpp:enumerator:`LV_STATE_USER_1`: (0x1000) Custom state
- :cpp:enumerator:`LV_STATE_USER_2`: (0x2000) Custom state
- :cpp:enumerator:`LV_STATE_USER_3`: (0x4000) Custom state
- :cpp:enumerator:`LV_STATE_USER_4`: (0x8000) Custom state
A Widget can be in a combination of states such as being focused and
pressed at the same time. This is represented as :cpp:expr:`LV_STATE_FOCUSED | LV_STATE_PRESSED`.
A style can be added to any state or state combination. For example,
setting a different background color for the default and pressed states.
If a property is not defined in a state the best matching state's
property will be used. Typically this means the property with
:cpp:enumerator:`LV_STATE_DEFAULT` is used.˛ If the property is not set even for the
default state the default value will be used. (See later)
Since :cpp:enumerator:`LV_PART_MAIN` and :cpp:enumerator:`LV_STATE_DEFAULT` both
have zero values, you can simply pass ``0`` as the ``selector`` argument instead of
``LV_PART_MAIN | LV_STATE_DEFAULT`` as a shortcut when adding styles to an object.
What does the "best matching state's property" mean?
----------------------------------------------------
States have a precedence which is shown by their value (see in the above list).
A higher value means higher precedence. To determine which state's
property to use let's take an example. Imagine the background color is
defined like this:
- :cpp:enumerator:`LV_STATE_DEFAULT`: white
- :cpp:enumerator:`LV_STATE_PRESSED`: gray
- :cpp:enumerator:`LV_STATE_FOCUSED`: red
1. Initially the Widget is in the default state, so it's a simple case:
the property is perfectly defined in the Widget's current state as
white.
2. When the Widget is pressed there are 2 related properties: default
with white (default is related to every state) and pressed with gray.
The pressed state has 0x0020 precedence which is higher than the
default state's 0x0000 precedence, so gray color will be used.
3. When the Widget has focus the same thing happens as in pressed state
and red color will be used. (Focused state has higher precedence than
default state).
4. When the Widget has focus and pressed both gray and red would work,
but the pressed state has higher precedence than focused so gray
color will be used.
5. It's possible to set e.g. rose color for :cpp:expr:`LV_STATE_PRESSED | LV_STATE_FOCUSED`.
In this case, this combined state has 0x0020 + 0x0002 = 0x0022 precedence, which is higher than
the pressed state's precedence so rose color would be used.
6. When the Widget is in the checked state there is no property to set
the background color for this state. So for lack of a better option,
the Widget remains white from the default state's property.
Some practical notes:
- The precedence (value) of states is quite intuitive, and it's something the
user would expect naturally. Example: if a Widget has focus the user will still
want to see if it's pressed, therefore the pressed state has a higher
precedence. If the focused state had a higher precedence it would overwrite
the pressed color.
- If you want to set a property for all states (e.g. red background color)
just set it for the default state. If the Widget can't find a property
for its current state it will fall back to the default state's property.
- Use ORed states to describe the properties for complex cases (e.g.
pressed + checked + focused).
- It might be a good idea to use different
style elements for different states. For example, finding background
colors for released, pressed, checked + pressed, focused, focused +
pressed, focused + pressed + checked, etc. states is quite difficult.
Instead, for example, use the background color for pressed and checked
states and indicate the focused state with a different border color.
.. _style_cascading:
Cascading Styles
****************
It's not required to set all the properties in one style. It's possible
to add more styles to a Widget and have the latter added style modify
or extend appearance. For example, create a general gray button style
and create a new one for red buttons where only the new background color
is set.
This is much like in CSS when used classes are listed like
``<div class=".btn .btn-red">``.
Styles added later have precedence over ones set earlier. So in the
gray/red button example above, the normal button style should be added
first and the red style second. However, the precedence of the states
are still taken into account. So let's examine the following case:
- the basic button style defines dark-gray color for the default state and
light-gray color for the pressed state
- the red button style defines the background color as red only in the default state
In this case, when the button is released (it's in default state) it
will be red because a perfect match is found in the most recently added
style (red). When the button is pressed the light-gray color is a better
match because it describes the current state perfectly, so the button
will be light-gray.
.. _style_inheritance:
Inheritance
***********
Some properties (typically those related to text) can be inherited from
the parent Widget's styles. Inheritance is applied only if the given
property is not set in the Widget's styles (even in default state). In
this case, if the property is inheritable, the property's value will be
searched up the parent hierarchy until a Widget specifies a value for the
property. The parents will use their own state to determine the value.
So if a button is pressed, and the text color comes from a parent, the
pressed text color will be used.
.. _style_parts:
Parts
*****
Widgets can be composed of *parts* which may each have their own styles.
The following predefined parts exist in LVGL:
- :cpp:enumerator:`LV_PART_MAIN`: (0x000000) A background like rectangle
- :cpp:enumerator:`LV_PART_SCROLLBAR`: (0x010000) The scrollbar(s)
- :cpp:enumerator:`LV_PART_INDICATOR`: (0x020000) Indicator, e.g. for slider, bar, switch, or the tick box of the checkbox
- :cpp:enumerator:`LV_PART_KNOB`: (0x030000) Like a handle to grab to adjust a value
- :cpp:enumerator:`LV_PART_SELECTED`: (0x040000) Indicate the currently selected option or section
- :cpp:enumerator:`LV_PART_ITEMS`: (0x050000) Used if the widget has multiple similar elements (e.g. table cells)
- :cpp:enumerator:`LV_PART_CURSOR`: (0x060000) Mark a specific place e.g. Text Area's or chart's cursor
- :cpp:enumerator:`LV_PART_CUSTOM_FIRST`: (0x080000) Custom part identifiers can be added starting from here.
- :cpp:enumerator:`LV_PART_ANY`: (0x0F0000) Special value can be used in some functions to target all parts.
For example a :ref:`Slider <lv_slider>` has three parts:
- Main (background)
- Indicator
- Knob
This means all three parts of the slider can have their own styles. See
later how to add styles to Widgets and parts.
Since :cpp:enumerator:`LV_PART_MAIN` and :cpp:enumerator:`LV_STATE_DEFAULT` both
have zero values, you can simply pass ``0`` as the ``selector`` argument instead of
``LV_PART_MAIN | LV_STATE_DEFAULT`` as a shortcut when adding styles to an object.
.. _style_initialize:
Initialize Styles and Set/Get Properties
****************************************
Styles are stored in :cpp:type:`lv_style_t` variables. Style variables should be
``static``, global or dynamically allocated. In other words they cannot
be local variables in functions which are destroyed when the function
exits. Before using a style it should be initialized with
:cpp:expr:`lv_style_init(&my_style)`. After initializing a style, properties can
be added or changed.
Property set functions looks like this:
``lv_style_set_<property_name>(&style, <value>);`` For example:
.. code-block:: c
static lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_color_hex(0x115588));
lv_style_set_bg_opa(&style_btn, LV_OPA_50);
lv_style_set_border_width(&style_btn, 2);
lv_style_set_border_color(&style_btn, lv_color_black());
static lv_style_t style_btn_red;
lv_style_init(&style_btn_red);
lv_style_set_bg_color(&style_btn_red, lv_palette_main(LV_PALETTE_RED));
lv_style_set_bg_opa(&style_btn_red, LV_OPA_COVER);
To remove a property use:
.. code-block:: c
lv_style_remove_prop(&style, LV_STYLE_BG_COLOR);
To get a property's value from a style:
.. code-block:: c
lv_style_value_t v;
lv_result_t res = lv_style_get_prop(&style, LV_STYLE_BG_COLOR, &v);
if(res == LV_RESULT_OK) { /* Found */
do_something(v.color);
}
:cpp:union:`lv_style_value_t` has 3 fields, only one of which will apply, depending
on the type of property it is applied to:
- :cpp:member:`num`: for integer, boolean and opacity properties
- :cpp:member:`color`: for color properties
- :cpp:member:`ptr`: for pointer properties
To reset a style (free all its data) use:
.. code-block:: c
lv_style_reset(&style);
Styles can be built as ``const`` as well to save RAM:
.. code-block:: c
const lv_style_const_prop_t style1_props[] = {
LV_STYLE_CONST_WIDTH(50),
LV_STYLE_CONST_HEIGHT(50),
LV_STYLE_CONST_PROPS_END
};
LV_STYLE_CONST_INIT(style1, style1_props);
Later ``const`` style can be used like any other style but (obviously)
new properties cannot be added.
.. _style_add_remove:
Add and remove styles to a widget
*********************************
A style on its own has no effect until it is added (assigned) to a Widget.
Add styles
----------
To add a style to a Widget use
``lv_obj_add_style(widget, &style, <selector>)``. ``<selector>`` is an
OR-ed value of parts and state to which the style should be added. Some
examples:
- :cpp:expr:`LV_PART_MAIN | LV_STATE_DEFAULT`
- :cpp:enumerator:`LV_STATE_PRESSED`: The main part in pressed state. :cpp:enumerator:`LV_PART_MAIN` can be omitted
- :cpp:enumerator:`LV_PART_SCROLLBAR`: The scrollbar part in the default state. :cpp:enumerator:`LV_STATE_DEFAULT` can be omitted.
- :cpp:expr:`LV_PART_SCROLLBAR | LV_STATE_SCROLLED`: The scrollbar part when the Widget is being scrolled
- :cpp:expr:`LV_PART_INDICATOR | LV_STATE_PRESSED | LV_STATE_CHECKED` The indicator part when the Widget is pressed and checked at the same time.
Using :cpp:func:`lv_obj_add_style`:
.. code-block:: c
lv_obj_add_style(btn, &style_btn, 0); /* Default button style */
lv_obj_add_style(btn, &btn_red, LV_STATE_PRESSED); /* Overwrite only some colors to red when pressed */
Replace styles
--------------
To replace a specific style of a Widget use
:cpp:expr:`lv_obj_replace_style(widget, old_style, new_style, selector)`. This
function will only replace ``old_style`` with ``new_style`` if the
``selector`` matches the ``selector`` used in ``lv_obj_add_style``. Both
``old_style`` and ``new_style`` must not be ``NULL``. Separate functions exist for
adding and removing styles. If the combination of
``old_style`` and ``selector`` exists multiple times in ``obj``\ 's
styles, all occurrences will be replaced. The return value of the
function indicates whether at least one successful replacement took
place.
Using :cpp:func:`lv_obj_replace_style`:
.. code-block:: c
lv_obj_add_style(btn, &style_btn, 0); /* Add a button style */
lv_obj_replace_style(btn, &style_btn, &new_style_btn, 0); /* Replace the button style with a different one */
Remove styles
-------------
To remove all styles from a Widget use :cpp:expr:`lv_obj_remove_style_all(widget)`.
To remove specific styles use
:cpp:expr:`lv_obj_remove_style(widget, style, selector)`. This function will remove
``style`` only if the ``selector`` matches with the ``selector`` used in
:cpp:func:`lv_obj_add_style`. ``style`` can be ``NULL`` to check only the
``selector`` and remove all matching styles. The ``selector`` can use
the :cpp:enumerator:`LV_STATE_ANY` and :cpp:enumerator:`LV_PART_ANY` values to remove the style from
any state or part.
Reporting style changes
-----------------------
If a style which is already assigned to a Widget changes (i.e. a
property is added or changed), the Widgets using that style should be
notified. There are 3 options to do this:
1. If you know that the changed properties can be applied by a simple redraw
(e.g. color or opacity changes) just call :cpp:expr:`lv_obj_invalidate(widget)`
or :cpp:expr:`lv_obj_invalidate(lv_screen_active())`.
2. If more complex style properties were changed or added, and you know which
Widget(s) are affected by that style call :cpp:expr:`lv_obj_refresh_style(widget, part, property)`.
To refresh all parts and properties use :cpp:expr:`lv_obj_refresh_style(widget, LV_PART_ANY, LV_STYLE_PROP_ANY)`.
3. To make LVGL check all Widgets to see if they use a style and refresh them
when needed, call :cpp:expr:`lv_obj_report_style_change(&style)`. If ``style``
is ``NULL`` all Widgets will be notified about a style change.
Get a style property's value on a Widget
----------------------------------------
To get the final value of a style's property considering
- cascading,
- inheritance,
- local styles and transitions (see below)
property "get" functions like this can be used: ``lv_obj_get_style_<property_name>(widget, <part>)``.
These functions use the Widget's current state and if no better candidate exists they return the default value.
For example:
.. code-block:: c
lv_color_t color = lv_obj_get_style_bg_color(btn, LV_PART_MAIN);
.. _style_local:
Local Styles
************
In addition to "normal" styles, Widgets can also store local styles.
This concept is similar to inline styles in CSS
(e.g. ``<div style="color:red">``) with some modification.
Local styles are like normal styles, but they can't be shared among
other Widgets. If used, local styles are allocated automatically, and
freed when the Widget is deleted. They are useful to add local
customization to a Widget.
Unlike in CSS, LVGL local styles can be assigned to states
(pseudo-classes_) and parts (pseudo-elements_).
To set a local property use functions like
``lv_obj_set_style_<property_name>(widget, <value>, <selector>);`` For example:
.. code-block:: c
lv_obj_set_style_bg_color(slider, lv_color_red(), LV_PART_INDICATOR | LV_STATE_FOCUSED);
.. _style_properties_overview:
Style Properties Overview
*************************
For the full list of style properties click :ref:`here <style_properties>`.
.. _typical bg props:
Typical background properties
-----------------------------
In documentation of widgets you will see sentences like "The
_____ Widget uses the typical background style properties". These "typical
background properties" are the properties being referred to:
- Background
- Border
- Outline
- Shadow
- Padding
- Width and height transformation
- X and Y translation
See :ref:`boxing_model` for the meanings of these terms.
.. _style_transitions:
Transitions
***********
By default, when a Widget changes state (e.g. it's pressed) the new
properties from the new state are set immediately. However, with
transitions it's possible to play an animation on state change. For
example, on pressing a button its background color can be animated to
the pressed color over 300 ms.
The parameters of the transitions are stored in the styles. It's
possible to set
- the time of the transition
- the delay before starting the transition
- the animation path (also known as the timing or easing function)
- the properties to animate
The transition properties can be defined for each state. For example,
setting a 500 ms transition time in the default state means that when
the Widget goes to the default state a 500 ms transition time is
applied. Setting a 100 ms transition time in the pressed state causes a
100 ms transition when going to the pressed state. This example
configuration results in going to the pressed state quickly and then
going back to default slowly.
To describe a transition an :cpp:struct:`lv_transition_dsc_t` variable needs to be
initialized and added to a style:
.. code-block:: c
/* Only its pointer is saved so must static, global or dynamically allocated */
static const lv_style_prop_t trans_props[] = {
LV_STYLE_BG_OPA, LV_STYLE_BG_COLOR,
0, /* End marker */
};
static lv_style_transition_dsc_t trans1;
lv_style_transition_dsc_init(&trans1, trans_props, lv_anim_path_ease_out, duration_ms, delay_ms);
lv_style_set_transition(&style1, &trans1);
.. _style_opacity_blend_modes_transformations:
Opacity, Blend Modes and Transformations
****************************************
If the ``opa``, ``blend_mode``, ``transform_angle``, or
``transform_zoom`` properties are set to a non-default value LVGL
creates a snapshot of the widget and its children in order to
blend the whole widget with the set opacity, blend mode and
transformation properties.
These properties have this effect only on the ``MAIN`` part of the
widget.
The created snapshot is called "intermediate layer" or simply "layer".
If only ``opa`` and/or ``blend_mode`` is set to a non-default value LVGL
can build the layer from smaller chunks. The size of these chunks can be
configured by the following properties in ``lv_conf.h``:
- :cpp:enumerator:`LV_LAYER_SIMPLE_BUF_SIZE`: [bytes] the optimal target buffer size. LVGL will try to allocate this size of memory.
- :cpp:enumerator:`LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE`: [bytes] used if :cpp:enumerator:`LV_LAYER_SIMPLE_BUF_SIZE` couldn't be allocated.
If transformation properties were also used the layer cannot be
rendered in chunks, but one larger memory block needs to be allocated. The
required memory depends on the angle, zoom and pivot parameters, and the
size of the area to redraw, but it's never larger than the size of the
widget (including the extra draw size used for shadow, outline, etc).
If the widget can fully cover the area to redraw, LVGL creates an RGB
layer (which is faster to render and uses less memory). If the opposite
case ARGB rendering needs to be used, a widget might not cover its area
if it has radius, ``bg_opa < 255``, has shadow, outline, etc.
The click area of the widget is also transformed accordingly.
.. _style_color_filter:
Color Filter
************
TODO
.. _style_themes:
Themes
******
Themes are a collection of styles. If there is an active theme LVGL
applies it to every newly-created widget. This will give a default appearance
to the UI which can then be modified by adding further styles.
Every display can have a different theme. For example, you could have a
colorful theme on a TFT and monochrome theme on a secondary monochrome
display.
To set a theme for a display, two steps are required:
1. Initialize a theme
2. Assign the initialized theme to a display.
Theme initialization functions can have different prototypes. This
example shows how to set the "default" theme:
.. code-block:: c
lv_theme_t * th = lv_theme_default_init(display, /* Use DPI, size, etc. from this display */
LV_COLOR_PALETTE_BLUE, /* Primary and secondary palette */
LV_COLOR_PALETTE_CYAN,
false, /* Dark theme? False = light theme. */
&lv_font_montserrat_10, /* Small, normal, large fonts */
&lv_font_montserrat_14,
&lv_font_montserrat_18);
lv_display_set_theme(display, th); /* Assign theme to display */
The included themes are enabled in ``lv_conf.h``. If the default theme
is enabled by :c:macro:`LV_USE_THEME_DEFAULT` LVGL automatically initializes
and sets it when a display is created.
Extending themes
----------------
Built-in themes can be extended. If a custom theme is created, a parent
theme can be selected. The parent theme's styles will be added before
the custom theme's styles. Any number of themes can be chained this way.
E.g. default theme -> custom theme -> dark theme.
:cpp:expr:`lv_theme_set_parent(new_theme, base_theme)` extends the
``base_theme`` with the ``new_theme``.
There is an example of this below.
.. _styles_example:
Examples
********
.. include:: ../../../examples/styles/index.rst
.. Hyperlinks
.. _pseudo-elements:
.. _pseudo-element: https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors#pseudo-classes_and_pseudo-elements
.. _pseudo-classes:
.. _pseudo-class: https://developer.mozilla.org/en-US/docs/Glossary/Pseudo-class
.. _styles_api:
API
***
.. API equals: lv_style_init, lv_style_t, lv_obj_add_style, LV_PART_MAIN, LV_STATE_DEFAULT
.. API startswith: lv_style_set_, lv_obj_set_

View File

@@ -0,0 +1,91 @@
.. _gdb_plugin:
===========
GDB Plug-In
===========
Debugging LVGL with GDB
-----------------------
To facilitate debugging LVGL with GDB, a GDB plugin is provided. This plugin
can be found in the ``lvgl/scripts/gdb`` directory. The GDB plugin can be used
with any target where GDB is available. For example, you can use it to debug a
device connected to a PC via JLink, which provides a GDB server. Additionally,
if your device crashes and you have a core dump, you can use GDB to analyze the
core dump. To load the LVGL GDB plugin within GDB's command line, type the
following command:
``source lvgl/scripts/gdb/gdbinit.py``
Example of usage:
.. code:: bash
(gdb) source lvgl/scripts/gdb/gdbinit.py
(gdb) dump obj -L 2
obj@0x60700000dd10 (0,0,799,599)
tabview@0x608000204ca0 (0,0,799,599)
obj@0x607000025da0 (0,0,799,69)
obj@0x607000025e80 (0,70,799,599)
obj@0x60700002bd70 (743,543,791,591)
btn@0x60700002c7f0 (747,547,787,587)
keyboard@0x60d0000f7040 (0,300,799,599)
dropdown-list@0x608000205420 (0,0,129,129)
label@0x60d0000f7ba0 (22,22,56,39)
(gdb)
The plugin provides the following commands.
- ``dump obj``: Dump the object tree.
- ``info style``: Show the object's style.
- ``info draw_unit``: Display all current drawing unit information.
Dump obj tree
-------------
``dump obj``: Dump the object tree.
``dump obj -L 2``: Dump the object tree with a depth of 2.
``dump obj -a 0x60700000dd10``: Dump the object tree starting from the specified address.
Show obj's style
----------------
This command can dump the object's local style, since style value is a union, it's displayed in all possible formats.
``info style address_of_obj``: Show the object's style.
Example:
.. code:: bash
(gdb) info style 0x60700000dd10
32 = {num = 90, ptr = 0x5a, color = {blue = 90 'Z', green = 0 '\000', red = 0 '\000'}}
158 = {num = 32767, ptr = 0x7fff, color = {blue = 255 '\377', green = 127 '\177', red = 0 '\000'}}
(gdb) p lv_global->disp_default->act_scr
$4 = (lv_obj_t *) 0x60700000dd10
(gdb) info style $4
32 = {num = 90, ptr = 0x5a, color = {blue = 90 'Z', green = 0 '\000', red = 0 '\000'}}
158 = {num = 32767, ptr = 0x7fff, color = {blue = 255 '\377', green = 127 '\177', red = 0 '\000'}}
(gdb)
Connect to Debugger
-------------------
This command provides the ability to connect and debug GDB Python Script using IDE.
Connect to ``PyCharm`` / ``VSCode`` / ``Eclipse(not support yet)``
``debugger -t pycharm``
``debugger -t vscode``
``debugger -t eclipse``
How to use it specifically, search ``pydevd_pycharm`` / ``debugpy`` for details.

View File

@@ -0,0 +1,13 @@
.. _debugging:
=========
Debugging
=========
.. toctree::
:maxdepth: 2
gdb_plugin
log
profiler
vg_lite_tvg

View File

@@ -0,0 +1,73 @@
.. _logging:
=======
Logging
=======
LVGL has a built-in *Log* module to inform the user about what is
happening in the library.
Log Level
*********
To enable logging, set :c:macro:`LV_USE_LOG` in ``lv_conf.h`` and set
:c:macro:`LV_LOG_LEVEL` to one of the following values:
- :c:macro:`LV_LOG_LEVEL_TRACE`: A lot of logs to give detailed information
- :c:macro:`LV_LOG_LEVEL_INFO`: Log important events
- :c:macro:`LV_LOG_LEVEL_WARN`: Log if something unwanted happened but didn't cause a problem
- :c:macro:`LV_LOG_LEVEL_ERROR`: Only critical issues, where the system may fail
- :c:macro:`LV_LOG_LEVEL_USER`: Only user messages
- :c:macro:`LV_LOG_LEVEL_NONE`: Do not log anything
The events which have a higher level than the set log level will be logged
as well. E.g. if you :c:macro:`LV_LOG_LEVEL_WARN`, errors will be also logged.
Printing Logs
*************
Logging with printf
-------------------
If your system supports ``printf``, you just need to enable
:c:macro:`LV_LOG_PRINTF` in ``lv_conf.h`` to send the logs with ``printf``.
Custom log function
-------------------
If you can't use ``printf`` or want to use a custom function to log, you
can register a "logger" callback with :cpp:func:`lv_log_register_print_cb`.
For example:
.. code-block:: c
void my_log_cb(lv_log_level_t level, const char * buf)
{
serial_send(buf, strlen(buf));
}
...
lv_log_register_print_cb(my_log_cb);
Add Logs
********
You can also use the log module via the
``LV_LOG_TRACE/INFO/WARN/ERROR/USER(text)`` or ``LV_LOG(text)``
functions. Here:
- ``LV_LOG_TRACE/INFO/WARN/ERROR/USER(text)`` append the following information to your ``text``
- Log Level
- \__FILE\_\_
- \__LINE\_\_
- \__func\_\_
- ``LV_LOG(text)`` is similar to ``LV_LOG_USER`` but has no extra information attached.
API
***

View File

@@ -0,0 +1,250 @@
.. _profiler:
========
Profiler
========
As the complexity of the application increases, performance issues such as low FPS and frequent cache misses
causing lag may arise. LVGL has internally set up some hooks for performance measurement to help developers
analyze and locate performance issues.
.. _profiler_introduction:
Introduction
************
LVGL has a built-in trace system to track and record the timestamps of important events that occur during runtime,
such as rendering events and user input events. These event timestamps serve as important metrics for performance analysis.
The trace system has a configurable record buffer that stores the names of event functions and their timestamps.
When the buffer is full, the trace system prints the log information through the provided user interface.
The output trace logs are formatted according to Android's `systrace <https://developer.android.com/topic/performance/tracing>`_
format and can be visualized using `Perfetto <https://ui.perfetto.dev>`_.
.. _profiler_usage:
Usage
*****
Configure profiler
^^^^^^^^^^^^^^^^^^
To enable the profiler, set :c:macro:`LV_USE_PROFILER` in ``lv_conf.h`` and configure the following options:
1. Enable the built-in profiler functionality by setting :c:macro:`LV_USE_PROFILER_BUILTIN`.
2. Buffer configuration: Set the value of :c:macro:`LV_PROFILER_BUILTIN_BUF_SIZE` to configure the buffer size. A larger buffer can store more trace event information, reducing interference with rendering. However, it also results in higher memory consumption.
3. Timestamp configuration: LVGL uses the :cpp:func:`lv_tick_get` function with a precision of 1ms by default to obtain timestamps when events occur. Therefore, it cannot accurately measure intervals below 1ms. If your system environment can provide higher precision (e.g., 1us), you can configure the profiler as follows:
- Recommended configuration in **UNIX** environments:
.. code-block:: c
#include <sys/syscall.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
static uint64_t my_get_tick_cb(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000 + ts.tv_nsec;
}
static int my_get_tid_cb(void)
{
return (int)syscall(SYS_gettid);
}
static int my_get_cpu_cb(void)
{
int cpu_id = 0;
syscall(SYS_getcpu, &cpu_id, NULL);
return cpu_id;
}
void my_profiler_init(void)
{
lv_profiler_builtin_config_t config;
lv_profiler_builtin_config_init(&config);
config.tick_per_sec = 1000000000; /* One second is equal to 1000000000 nanoseconds */
config.tick_get_cb = my_get_tick_cb;
config.tid_get_cb = my_get_tid_cb;
config.cpu_get_cb = my_get_cpu_cb;
lv_profiler_builtin_init(&config);
}
- Recommended configuration in **Arduino** environments:
.. code-block:: c
static uint64_t my_get_tick_cb(void)
{
/* Use the microsecond time stamp provided by Arduino */
return micros();
}
void my_profiler_init(void)
{
lv_profiler_builtin_config_t config;
lv_profiler_builtin_config_init(&config);
config.tick_per_sec = 1000000; /* One second is equal to 1000000 microseconds */
config.tick_get_cb = my_get_tick_cb;
lv_profiler_builtin_init(&config);
}
4. Log output configuration: LVGL uses the :cpp:func:`LV_LOG` interface by default to output trace information. If you want to use another interface to output log information (e.g., file stream), you can redirect the log output using the following code:
.. code-block:: c
static void my_log_print_cb(const char * buf)
{
printf("%s", buf);
}
void my_profiler_init(void)
{
lv_profiler_builtin_config_t config;
lv_profiler_builtin_config_init(&config);
... /* other configurations */
config.flush_cb = my_log_print_cb;
lv_profiler_builtin_init(&config);
}
Run the test scenario
^^^^^^^^^^^^^^^^^^^^^
Run the UI scenario that you want to measure, such as scrolling a scrollable page up and down or entering/exiting an application.
Process the logs
^^^^^^^^^^^^^^^^
Save the output log as `my_trace.txt`, use `trace_filter.py` for filtering and preprocessing:
.. code-block:: bash
./lvgl/scripts/trace_filter.py my_trace.txt
or
.. code-block:: bash
python3 ./lvgl/scripts/trace_filter.py my_trace.txt
You will obtain a processed text file named `trace.systrace`, which roughly contains the following content:
.. code-block:: text
# tracer: nop
#
LVGL-1 [0] 2892.002993: tracing_mark_write: B|1|lv_timer_handler
LVGL-1 [0] 2892.002993: tracing_mark_write: B|1|_lv_display_refr_timer
LVGL-1 [0] 2892.003459: tracing_mark_write: B|1|refr_invalid_areas
LVGL-1 [0] 2892.003461: tracing_mark_write: B|1|lv_draw_rect
LVGL-1 [0] 2892.003550: tracing_mark_write: E|1|lv_draw_rect
LVGL-1 [0] 2892.003552: tracing_mark_write: B|1|lv_draw_rect
LVGL-1 [0] 2892.003556: tracing_mark_write: E|1|lv_draw_rect
LVGL-1 [0] 2892.003560: tracing_mark_write: B|1|lv_draw_rect
LVGL-1 [0] 2892.003573: tracing_mark_write: E|1|lv_draw_rect
...
Import the processed `trace.systrace` file into `Perfetto <https://ui.perfetto.dev>`_ and wait for it to be parsed.
Performance analysis
^^^^^^^^^^^^^^^^^^^^
If the log parsing is successful, you will see the following screen:
.. image:: /_static/images/perfetto_ui.png
In the Perfetto UI, use the :kbd:`A` or :kbd:`D` keys to pan the timeline horizontally
and the :kbd:`W` or :kbd:`S` keys to zoom in or out on the timeline.
Use the mouse to move the focus and click on functions on the timeline to observe their execution time.
Add Measurement Point
*********************
Users can add their own measured functions:
.. code-block:: c
void my_function_1(void)
{
LV_PROFILER_BEGIN;
do_something();
LV_PROFILER_END;
}
void my_function_2(void)
{
LV_PROFILER_BEGIN_TAG("do_something_1");
do_something_1();
LV_PROFILER_END_TAG("do_something_1");
LV_PROFILER_BEGIN_TAG("do_something_2");
do_something_2();
LV_PROFILER_END_TAG("do_something_2");
}
.. _profiler_custom_implementation:
Custom profiler implementation
******************************
If you wish to use a profiler method provided by your operating system, you can modify the following configurations in ``lv_conf.h``:
- :c:macro:`LV_PROFILER_INCLUDE`: Provides a header file for the profiler function.
- :c:macro:`LV_PROFILER_BEGIN`: Profiler start point function.
- :c:macro:`LV_PROFILER_END`: Profiler end point function.
- :c:macro:`LV_PROFILER_BEGIN_TAG`: Profiler start point function with custom tag.
- :c:macro:`LV_PROFILER_END_TAG`: Profiler end point function with custom tag.
Taking `NuttX <https://github.com/apache/nuttx>`_ RTOS as an example:
.. code-block:: c
#define LV_PROFILER_INCLUDE "nuttx/sched_note.h"
#define LV_PROFILER_BEGIN sched_note_begin(NOTE_TAG_ALWAYS)
#define LV_PROFILER_END sched_note_end(NOTE_TAG_ALWAYS)
#define LV_PROFILER_BEGIN_TAG(str) sched_note_beginex(NOTE_TAG_ALWAYS, str)
#define LV_PROFILER_END_TAG(str) sched_note_endex(NOTE_TAG_ALWAYS, str)
.. _profiler_faq:
FAQ
***
Perfetto log parsing fails
^^^^^^^^^^^^^^^^^^^^^^^^^^
Please check the completeness of the logs. If the logs are incomplete, it may be due to the following reasons:
1. Serial port reception errors caused by a high baud rate. You need to reduce the baud rate.
2. Data corruption caused by other thread logs inserted during the printing of trace logs. You need to disable the log output of other threads or refer to the configuration above to use a separate log output interface.
3. Make sure that the string passed in by :c:macro:`LV_PROFILER_BEGIN_TAG` or :c:macro:`LV_PROFILER_END_TAG` is not a local variable on the stack or a string in shared memory, because currently only the string address is recorded and the content is not copied.
Function execution time displayed as 0s in Perfetto
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If the function execution time is lower than the precision of the timestamps, this situation can occur. You can refer to the configuration instructions above to use a higher precision timestamp.
Significant stuttering occurs during profiling
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When the buffer used to store trace events becomes full, the profiler will output all the data in the buffer, which can cause UI blocking and stuttering during the output. You can optimize this by taking the following measures:
1. Increase the value of :c:macro:`LV_PROFILER_BUILTIN_BUF_SIZE`. A larger buffer can reduce the frequency of log printing, but it also consumes more memory.
2. Optimize the execution time of log printing functions, such as increasing the serial port baud rate or improving file writing speed.
Trace logs are not being output
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If the trace logs are not automatically printed when the buffer is not full, you can try the following methods to force the log output:
1. Reduce the value of :c:macro:`LV_PROFILER_BUILTIN_BUF_SIZE` to fill the buffer more quickly and trigger automatic printing.
2. Manually call or use a timer to call the :cpp:func:`lv_profiler_builtin_flush` function to force the log output.

View File

@@ -0,0 +1,27 @@
.. _vg_lite_tvg:
=================
VG-Lite Simulator
=================
LVGL integrates a VG-Lite simulator based on ThorVG.
Its purpose is to simplify the debugging of VG-Lite adaptation and reduce the time of debugging and locating problems on hardware devices.
It has been integrated into the CI automated compilation and testing process to ensure that the VG-Lite rendering backend can be fully tested after each PR modification.
How It Works
************
Sort out the APIs in the ``vg_lite.h`` header file provided by the vendor, re-implement the APIs using `ThorVG <https://github.com/thorvg/thorvg>`_,
and simulate the same rendering images as the real hardware on the simulator.
Configuration
*************
1. Enable VG-Lite rendering backend, see :ref:`vg_lite`.
2. Enable ThorVG and turn on the configuration :c:macro:`LV_USE_THORVG_INTERNAL` or :c:macro:`LV_USE_THORVG_EXTERNAL`.
It is recommended to use the internal ThorVG library to ensure uniform rendering results.
3. Enable :c:macro:`LV_USE_VG_LITE_THORVG` and set :c:macro:`LV_DRAW_BUF_ALIGN` to 64. The rest of the options can remain default.
Make sure :c:macro:`LV_VG_LITE_USE_GPU_INIT` is enabled, because the thorvg drawing context needs to be initialized before it can be used.

View File

@@ -0,0 +1,20 @@
.. _reference:
=======
Details
=======
.. toctree::
:maxdepth: 2
../examples
integration/index
common-widget-features/index
widgets/index
main-modules/index
auxiliary-modules/index
libs/index
debugging/index
../contributing/index
../CHANGELOG
../API/index

View File

@@ -0,0 +1,28 @@
.. _building_lvgl:
=============
Building LVGL
=============
Make and CMake
**************
LVGL also supports ``make`` and ``CMake`` build systems out of the box.
To add LVGL to your Makefile based build system add these lines to your
main Makefile:
.. code-block:: make
LVGL_DIR_NAME ?= lvgl #The name of the lvgl folder (change this if you have renamed it)
LVGL_DIR ?= ${shell pwd} #The path where the lvgl folder is
include $(LVGL_DIR)/$(LVGL_DIR_NAME)/lvgl.mk
For integration with CMake take a look this section of the
:ref:`Documentation <integrating_lvgl_cmake>`.
Managed builds
**************
TODO

View File

@@ -0,0 +1,154 @@
.. _configuration:
=============
Configuration
=============
.. _lv_conf:
lv_conf.h
*********
Creating lv_conf.h
------------------
When setting up your project for the first time, copy ``lvgl/lv_conf_template.h`` to
``lv_conf.h`` next to the ``lvgl`` folder. Change the first ``#if 0`` to ``1`` to
enable the file's content and set the :c:macro:`LV_COLOR_DEPTH` define to align with
the color depth used by your display panel. See comments in ``lv_conf.h`` for
details.
The layout of the files should look like this::
lvgl/
lv_conf.h
other files and folders in your project
Alternatively, ``lv_conf.h`` can be copied to another place but then you
should add the :c:macro:`LV_CONF_INCLUDE_SIMPLE` define to your compiler
options (e.g. ``-DLV_CONF_INCLUDE_SIMPLE`` for GCC compiler) and set the
include path manually (e.g. ``-I../include/gui``). In this case LVGL
will attempt to include ``lv_conf.h`` simply with ``#include "lv_conf.h"``.
You can even use a different name for ``lv_conf.h``. The custom path can
be set via the :c:macro:`LV_CONF_PATH` define. For example
``-DLV_CONF_PATH="/home/joe/my_project/my_custom_conf.h"``. If this define
is set :c:macro:`LV_CONF_SKIP` is assumed to be ``0``. Please notice,
when defining the :c:macro:`LV_CONF_PATH`, you need to make sure it is
defined as a string, otherwise a build error will be raised.
If :c:macro:`LV_CONF_SKIP` is defined, LVGL will not try to include
``lv_conf.h``. Instead you can pass the config defines using build
options. For example ``"-DLV_COLOR_DEPTH=32 -DLV_USE_BUTTON=1"``. Unset
options will get a default value which is the same as the content of
``lv_conf_template.h``.
.. _configuration_settings:
Configuration Settings
----------------------
Once the ``lv_conf.h`` file is in place, you can modify this header to configure
LVGL's behavior, disable unused modules and features, adjust the size of buffers, etc.
The comments in ``lv_conf.h`` explain the meaning of each setting. Be sure
to at least set :c:macro:`LV_COLOR_DEPTH` according to your display's color
depth. Note that the examples and demos explicitly need to be enabled
in ``lv_conf.h`` if you need them.
TODO: Add all things related to ``lv_conf.h`` file and its contents.
Multiple Instances of LVGL
~~~~~~~~~~~~~~~~~~~~~~~~~~
It is possible to run multiple, independent instances of LVGL. To enable its
multi-instance feature, set :c:macro:`LV_GLOBAL_CUSTOM` in ``lv_conf.h``
and provide a custom function to :cpp:func:`lv_global_default` using ``__thread`` or
``pthread_key_t``. It will allow running multiple LVGL instances by storing LVGL's
global variables in TLS (Thread-Local Storage).
For example:
.. code-block:: c
lv_global_t * lv_global_default(void)
{
static __thread lv_global_t lv_global;
return &lv_global;
}
Kconfig
*******
LVGL can also be configured using Kconfig. For now, this is only available using cmake.
Under the hood, it uses ``kconfiglib`` Kconfig python port to be able to use it across different platforms.
The ``kconfiglib`` offers the python API and some CLI commands. Here is a list of some useful commands:
- ``menuconfig``: Opens a console menu interface to modify the configuration values.
- ``guiconfig`` (needs ``tkinter``): Opens a graphical interface to modify the configuration values.
- ``savedefconfig``: Saves the current .config as a defconfig, listing only non-default values.
- ``alldefconfig``: Creates a .config with all default values.
- ``genconfig``: Generates a C header from the config, following ``autoconf.h`` format.
Prerequisites
-------------
Install the prerequisites using ``scripts/install_prerequisites.sh/bat``.
Create the configuration (.config)
----------------------------------
At this point, the ``menuconfig`` command should be available:
.. code-block:: shell
cd <lvgl_repo>
menuconfig
Make changes to the config and exit using `Esc` or `Q`, and save your configuration. The ``.config`` file is
now created and list the configuration values.
Configuring with cmake
----------------------
Once the ``.config`` is created, run cmake with the ``-DLV_USE_KCONFIG=ON`` flag:
.. code-block:: shell
cd <lvgl_repo>
cmake -B build -DLV_USE_KCONFIG=ON
cmake --build build
To use a ``defconfig`` file, one can use the ``-DLV_DEFCONFIG_PATH=<path_to_defconfig>`` flag:
.. code-block:: shell
cd <lvgl_repo>
cmake -B build -DLV_USE_KCONFIG=ON -DLV_DEFCONFIG_PATH=<path_to_defconfig>
cmake --build build
Some defconfigs are available in ``configs/defconfigs`` folder.
Saving a defconfig
------------------
One can save a defconfig using the ``savedefconfig`` command:
.. code-block:: shell
cd <lvgl_repo>
menuconfig # make your changes to the default config
savedefconfig
cp defconfig configs/defconfigs/my_custom_defconfig # save it where you want
# Then use it to build LVGL
cmake -B build -DLV_USE_KCONFIG=ON -DLV_DEFCONFIG_PATH=configs/defconfigs/my_custom_defconfig
cmake --build build

View File

@@ -0,0 +1,162 @@
.. _connecting_lvgl:
================================
Connecting LVGL to Your Hardware
================================
.. _initializing_lvgl:
Initializing LVGL
*****************
After you have:
- :ref:`acquired LVGL <getting_lvgl>`,
- added the appropriate LVGL files to your project, and
- :ref:`created a lv_conf.h file <lv_conf>` for your project,
you will need to complete a few more steps to get your project up and running with LVGL.
1. Initialize LVGL once early during system execution by calling :cpp:func:`lv_init`.
This needs to be done before making any other LVGL calls.
2. Initialize your drivers.
3. Connect the :ref:`tick_interface`.
4. Connect the :ref:`display_interface`.
5. Connect the :ref:`indev_interface`.
6. Drive LVGL time-related tasks by calling :cpp:func:`lv_timer_handler` every few
milliseconds to manage LVGL timers. See :ref:`timer_handler` for different ways
to do this.
7. Optionally set a theme with :cpp:func:`lv_display_set_theme`.
8. Thereafter #include "lvgl/lvgl.h" in source files wherever you need to use LVGL
functions.
.. _tick_interface:
Tick Interface
**************
LVGL needs awareness of what time it is (i.e. elapsed time in milliseconds) for
all of its tasks for which time is a factor: refreshing displays, reading user
input, firing events, animations, etc.
.. image:: /_static/images/intro_data_flow.png
:scale: 75 %
:alt: LVGL Data Flow
:align: center
There are two ways to provide this information to LVGL:
1. Supply LVGL with a callback function to retrieve elapsed system milliseconds by
calling :cpp:expr:`lv_tick_set_cb(my_get_milliseconds)`.
:cpp:expr:`my_get_milliseconds()` needs to return the number of milliseconds
elapsed since system start up. Many platforms have built-in functions that can
be used as they are. For example:
- SDL: ``lv_tick_set_cb(SDL_GetTicks);``
- Arduino: ``lv_tick_set_cb(my_tick_get_cb);``, where ``my_tick_get_cb`` is:
``static uint32_t my_tick_get_cb(void) { return millis(); }``
- FreeRTOS: ``lv_tick_set_cb(xTaskGetTickCount);``
- STM32: ``lv_tick_set_cb(HAL_GetTick);``
- ESP32: ``lv_tick_set_cb(my_tick_get_cb);``, where ``my_tick_get_cb`` is a
wrapper for ``esp_timer_get_time() / 1000;``
2. Call :cpp:expr:`lv_tick_inc(x)` periodically, where ``x`` is the elapsed
milliseconds since the last call. If :cpp:func:`lv_tick_inc` is called from an
ISR, it should be from either a high priority interrupt or an interrupt that
cannot be missed when the system is under high load.
.. note:: :cpp:func:`lv_tick_inc` is only one of two LVGL functions that may be
called from an interrupt if writing to a ``uint32_t`` value is atomic on your
platform. See below and the :ref:`threading` section to learn more.
Either way, the writing of the ``uint32_t`` Tick value must be :ref:`atomic <atomic>`,
which is usually the case with a 32- or 64-bit platform. If you are using a 16-bit
system (causing the update of the Tick value to not be atomic) and your platform uses
the Harvard instruction set, you can set a function like this as the callback passed
to :cpp:expr:`lv_tick_set_cb(my_get_milliseconds)`:
.. code-block:: c
/**
* @brief Safe read from 'elapsed_power_on_time_in_ms'
*/
uint32_t my_get_milliseconds()
{
register uint32_t u32result;
/* Disable priority 1-6 interrupts for 2 Fcys. */
__builtin_disi(2);
u32result = elapsed_power_on_time_in_ms; /* Cost: 2 Fcys */
/* Generally looks like this in assembly:
* mov elapsed_power_on_time_in_ms, W0
* mov 0x7898, W1
* requiring exactly 2 clock cycles.
* Now value is copied to register pair W0:W1
* where it can be written to any destination. */
return u32result;
}
Reliability
-----------
Advancing the tick value should be done in such a way that its timing is reliable and
not dependent on anything that consumes an unknown amount of time. For an example of
what *not* to do: this can "seem" to work, but LVGL's timing will be incorrect
because the execution time of :c:func:`lv_timer_handler` varies from call to call and
thus the delay it introduces cannot be known.
.. code-block:: c
// Bad idea
lv_timer_handler();
lv_tick_inc(5);
my_delay_ms(5);
.. _display_interface:
Display Interface
*****************
LVGL needs to be supplied with knowledge about each display panel you want it to use.
Specifically:
- its pixel format and size (:ref:`creating_a_display`),
- where to render pixels for it (:ref:`draw_buffers`), and
- how to send those rendered pixels to it (:ref:`flush_callback`).
See the respective links for how to supply LVGL with this knowledge.
.. _indev_interface:
Input-Device Interface
**********************
LVGL needs to know how to get input from all user-input devices that will be used in
your project. LVGL supports a wide variety of user-input devices:
- touch-screens,
- touch-pads,
- mice,
- crowns,
- encoders,
- keypads,
- keyboards,
- etc.
See :ref:`indev_creation` to see how to do this.
API
***
:ref:`lv_tick_h`

View File

@@ -0,0 +1,25 @@
.. _getting_lvgl:
============
Getting LVGL
============
LVGL is available on GitHub: https://github.com/lvgl/lvgl.
You can clone it or
`Download <https://github.com/lvgl/lvgl/archive/refs/heads/master.zip>`__
the latest version of the library from GitHub.
The graphics library itself is the ``lvgl`` directory. It contains several
directories but to use LVGL you only need the ``.c`` and ``.h`` files under
the ``src`` directory, plus ``lvgl/lvgl.h``, and ``lvgl/lv_version.h``.
Demos and Examples
------------------
The ``lvgl`` directory also contains an ``examples`` and a ``demos``
directory. If your project needs examples and/or demos, add the these
directories to your project. If ``make`` or :ref:`build_cmake` handle the
examples and demos directories, no extra action is required.

View File

@@ -0,0 +1,18 @@
.. _adding_lvgl_to_your_project:
===========================
Adding LVGL to Your Project
===========================
.. toctree::
:maxdepth: 2
getting_lvgl
building_lvgl
configuration
connecting_lvgl
timer_handler
threading
other_platforms

View File

@@ -0,0 +1,11 @@
.. _other_platforms:
=========================
Other Platforms and Tools
=========================
See :ref:`Integration <integration_index>` to see how to use LVGL on different
platforms. There, you will find many platform-specific descriptions e.g. for ESP32,
Arduino, NXP, RT-Thread, NuttX, etc.

View File

@@ -0,0 +1,287 @@
.. _threading:
========================
Threading Considerations
========================
.. _threading_definitions:
Definitions
***********
.. _thread:
Thread
In the context of this document, a thread is any sequence of CPU instructions.
In "bare-metal" implementations (i.e. no OS), threads include:
- the main thread executing a while(1) loop that runs the system, and
- interrupt service routines (ISRs).
When running under an OS, threads include:
- each task (or process),
- ISRs, and
- advanced OSes can have multiple "execution threads" within a processes.
.. _atomic operation:
Atomic Operation
If operation X is atomic, that means that any thread observing the operation will
see it either as not yet started, or as completed, and not in any state that is
partially completed.
If other threads can see the operation in a partially performed state, or
interfere with it, then operation X is not atomic.
If an atomic operation can fail, its implementation must return the the resource
back to the state before the operation was started. To other threads it must
appear as though the operation had not yet started.
.. _atomic data:
.. _atomic:
.. _non-atomic data:
Atomic Data
A datum (i.e. contents of a variable or data structure) is atomic if any thread
observing it will always see it in a consistent state, as if operations on it
have either not yet started, or have been successfully completed, and not in a
state that is partially changed or otherwise inconsistent.
When reading or writing a value is started and completed with 1 CPU instruction,
it is automatically atomic, since it can never been seen in an inconsistent
(partially-changed) state, even from a CPU interrupt or exception. With such
values, no special protection is required by programmers to ensure all threads
see it in a consistent state.
.. _lvgl_and_threads:
LVGL and Threads
****************
LVGL is **not thread-safe**.
That means it is the programmer's responsibility to see that no LVGL function is
called while another LVGL call is in progress in another thread. This includes calls
to :cpp:func:`lv_timer_handler`.
.. note::
Assuming the above is the case, it is safe to call LVGL functions in
- :ref:`event callbacks <events>`, and in
- :ref:`timer callbacks <timer>`
because the thread that drives both of these is the thread that calls
:cpp:func:`lv_timer_handler`.
Reason:
LVGL manages many complex data structures, and those structures are "system
resources" that must be protected from being "seen" by other threads in an
inconsistent state. A high percentage LVGL functions (functions that start with
``lv_``) either read from or change those data structures. Those that change them
place the data in an inconsistent state during execution (because such changes are
multi-step sequences), but return them to a consistent state before those functions
return. For this reason, execution of each LVGL function must be allowed to complete
before any other LVGL function is started.
.. _os_exception:
.. admonition:: Exceptions to the Above:
These two LVGL functions may be called from any thread:
- :cpp:func:`lv_tick_inc` (if writing to a ``uint32_t`` is atomic on your
platform; see :ref:`tick_interface` for more information) and
- :cpp:func:`lv_display_flush_ready` (:ref:`flush_callback` for more information)
The reason this is okay is that the LVGL data changed by them is :ref:`atomic <atomic>`.
If an interrupt MUST convey information to part of your application that calls
LVGL functions, set a flag or other atomic value that your LVGL-calling thread
(or an :ref:`LVGL Timer <timer>` you create) can read from and take action.
If you are using an OS, there are a few other options. See below.
Ensuring Time Updates are Atomic
--------------------------------
For LVGL's time-related tasks to be reliable, the time updates via the Tick Interface
must be reliable and the Tick Value must appear :ref:`atomic <atomic>` to LVGL. See
:ref:`tick_interface` for details.
.. _tasks:
Tasks
*****
Under an OS, it is common to have many threads of execution ("tasks" in some OSes)
performing services for the application. In some cases, such threads can acquire
data that should be shown (or otherwise reflected) in the user interface, and doing
so requires making LVGL calls to get that data (or change) shown.
Yet it still remains the programmer's responsibility to see that no LVGL function is
called while another LVGL call is in progress.
How do you do this?
.. _gateway thread:
Method 1: Use a Gateway Thread
-------------------------------
A "Gateway Thread" (or "Gateway Task" in some OSes) is a thread (task) that the
system designer designates to *exclusively* manage a system resource. An example is
management of a remote chip, such as an EEPROM or other device that always needs to
be brought into a consistent state before something new is started. Another example
is management of multiple devices on an I2C bus (or any data bus). In this case the
I2C bus is the "exclusively-managed resource", and having only one thread managing it
guarantees that each action started is allowed to complete before another action with
it is started.
LVGL's data structures are a system resource that requires such protection.
Using this method, creation, modification and deletion of all Widgets and other
LVGL resources (i.e. all LVGL function calls excluding the :ref:`exceptions
<os_exception>` mentioned above) are called by that thread. That means
that thread is also the ONLY caller of :cpp:func:`lv_timer_handler`. (See
:ref:`adding_lvgl_to_your_project` for more information.)
This ensures LVGL's data structures "appear" atomic_ (all threads using this data
"see" it in a consistent state) by the fact that no other threads are "viewing" those
data structures. This is enforced by programmer discipline that ensures the `Gateway
Thread`_ is the only thread making LVGL calls (excluding the :ref:`exceptions
<os_exception>` mentioned above).
If `atomic data`_ relevant to the user interface is updated in another thread (i.e.
by another task or in an interrupt), the thread calling LVGL functions can read that
data directly without worry that it is in an inconsistent state. (To avoid
unnecessary CPU overhead, a mechanism can be provided [such as a flag raised by the
updating thread] so that the user interface is only updated when it will result in a
change visible to the end user.)
If `non-atomic data`_ relevant to the user interface is updated in another thread
(i.e. by another task or in an interrupt), an alternate (and safe) way of convey that
data to the thread calling LVGL functions is to pass a private copy of that data to
that thread via a QUEUE or other OS mechanism that protects that data from being seen
in an inconsistent state.
Use of a `Gateway Thread`_ avoids the CPU-overhead (and coding overhead) of using a
MUTEX to protect LVGL data structures.
Method 2: Use a MUTEX
----------------------
A MUTEX stands for "MUTually EXclusive" and is a synchronization primitive that
protects the state of a system resource from being modified or accessed by multiple
threads of execution at once. In other words, it makes data so protected "appear"
atomic (all threads using this data "see" it in a consistent state). Most OSes
provide MUTEXes.
The system designer assigns a single MUTEX to product a single system resource. Once
assigned, that MUTEX performs such protection by programmers:
1. acquiring the MUTEX (a.k.a. locking it) before accessing or modifying that
resource, and
2. releasing the MUTEX (a.k.a. unlocking it) after that access or modification
is complete.
If a thread attempts to acquire (lock) the MUTEX while another thread "owns" it,
that thread waits on the other thread to release (unlock) it before it is allowed
to continue execution.
To be clear: this must be done *both* by threads that READ from that resource, and
threads that MODIFY that resource.
If a MUTEX is used to protect LVGL data structures, that means *every* LVGL function
call (or group of function calls) must be preceded by #1, and followed by #2,
including calls to :cpp:func:`lv_timer_handler`.
.. note::
If your OS is integrated with LVGL (the macro :c:macro:`LV_USE_OS` has a value
other than ``LV_OS_NONE`` in ``lv_conf.h``) you can use :cpp:func:`lv_lock()` and
:cpp:func:`lv_unlock()` to perform #1 and #2.
When this is the case, :cpp:func:`lv_timer_handler` calls :cpp:func:`lv_lock()`
and :cpp:func:`lv_unlock()` internally, so you do not have to bracket your
calls to :cpp:func:`lv_timer_handler` with them.
If your OS is NOT integrated with LVGL, then these calls either return
immediately with no effect, or are optimized away by the linker.
To enable :cpp:func:`lv_lock()` and :cpp:func:`lv_unlock()`, set ``LV_USE_OS``
to a value other than ``LV_OS_NONE``.
This pseudocode illustrates the concept of using a MUTEX:
.. code-block:: c
void lvgl_thread(void)
{
while(1) {
uint32_t time_till_next;
time_till_next = lv_timer_handler(); /* lv_lock/lv_unlock is called internally */
if(time_till_next == LV_NO_TIMER_READY) time_till_next = LV_DEF_REFR_PERIOD; /*try again soon because the other thread can make the timer ready*/
thread_sleep(time_till_next); /* sleep for a while */
}
}
void other_thread(void)
{
/* You must always hold (lock) the MUTEX while calling LVGL functions. */
lv_lock();
lv_obj_t *img = lv_image_create(lv_screen_active());
lv_unlock();
while(1) {
lv_lock();
/* Change to next image. */
lv_image_set_src(img, next_image);
lv_unlock();
thread_sleep(2000);
}
}
.. _sleep_management:
Sleep Management
****************
The MCU can go to sleep when no user input has been received for a certain period.
In this case, the main ``while(1)`` could look like this:
.. code-block:: c
while(1) {
/* Normal operation (no sleep) in < 1 sec inactivity */
if(lv_display_get_inactive_time(NULL) < 1000) {
lv_timer_handler();
}
/* Sleep after 1 sec inactivity */
else {
timer_stop(); /* Stop the timer where lv_tick_inc() is called */
sleep(); /* Sleep the MCU */
}
my_delay_ms(5);
}
You should also add the following lines to your input device read
function to signal a wake-up (press, touch, click, etc.) has happened:
.. code-block:: c
lv_tick_inc(LV_DEF_REFR_PERIOD); /* Force task execution on wake-up */
timer_start(); /* Restart timer where lv_tick_inc() is called */
lv_timer_handler(); /* Call `lv_timer_handler()` manually to process the wake-up event */
In addition to :cpp:func:`lv_display_get_inactive_time` you can check
:cpp:func:`lv_anim_count_running` to see if all animations have finished.

View File

@@ -0,0 +1,107 @@
.. _timer_handler:
=============
Timer Handler
=============
To drive the timers of LVGL you need to call :cpp:func:`lv_timer_handler`
periodically in one of the following:
- *while(1)* of *main()* function, or
- an OS task periodically. (See :ref:`lvgl_and_threads`.)
.. image:: /_static/images/intro_data_flow.png
:scale: 75 %
:alt: LVGL Data Flow
:align: center
Example:
.. code-block:: c
while(1) {
uint32_t time_till_next = lv_timer_handler();
if(time_till_next == LV_NO_TIMER_READY) time_till_next = LV_DEF_REFR_PERIOD; /*handle LV_NO_TIMER_READY. Another option is to `sleep` for longer*/
my_delay_ms(time_till_next);
}
If you want to use :cpp:func:`lv_timer_handler` in a super-loop, a helper
function :cpp:func:`lv_timer_handler_run_in_period` is provided to simplify
supplying LVGL with time awareness:
.. code-block:: c
while(1) {
...
lv_timer_handler_run_in_period(5); /* run lv_timer_handler() every 5ms */
...
}
Or use the sleep time automatically calculated by LVGL:
.. code-block:: c
while(1) {
...
lv_timer_periodic_handler();
...
}
In an OS environment, you can use it together with the **delay** or
**sleep** provided by OS to release CPU whenever possible:
.. code-block:: c
while (1) {
uint32_t time_till_next = lv_timer_handler();
if(time_till_next == LV_NO_TIMER_READY) time_till_next = LV_DEF_REFR_PERIOD; /*handle LV_NO_TIMER_READY. Another option is to `sleep` for longer*/
os_delay_ms(time_till_next); /* delay to avoid unnecessary polling */
}
See :ref:`timer` section to learn more about timers.
.. _timer_handler_no_timer_ready:
When No Timers Are Ready
************************
:c:func:`lv_timer_handler` will return :c:macro:`LV_NO_TIMER_READY` (``UINT32_MAX``)
if there are no running timers. This can happen if there are no indevs or they are disabled with `lv_indev_enable()`, running
animations, or running user-created timers. :c:func:`lv_timer_handler` will continue
to return :c:macro:`LV_NO_TIMER_READY` until there is a running timer. Display
timers will stay paused when there is no reason to refresh.
:c:func:`lv_timer_handler` should be called after something is created, deleted, or
modified so that a refresh will be performed if necessary. In practice this means
waiting without a timeout for some external event. After the
external events are received and handled, :c:func:`lv_timer_handler` should be
called again.
.. code-block:: c
while (1) {
uint32_t time_till_next = lv_timer_handler();
int timeout;
/* Wait forever for events upon LV_NO_TIMER_READY, because there
* is no reason to call lv_timer_handler sooner. */
if(time_till_next == LV_NO_TIMER_READY) timeout = -1; /*infinite timeout*/
/*Otherwise, wait for events at least until the timeout expires.*/
else timeout = time_till_next;
if(poll(..., timeout)) {
/*Handle events before calling `lv_timer_handler` again.*/
}
}
If there is no external event source, you may choose to exit the loop or simply
delay for a long time.
If another :ref:`thread <threading>` is
calling LVGL functions, you may want to call :c:func:`lv_timer_handler` again
very soon to handle the effects of those other threads.
API
***

View File

@@ -0,0 +1,516 @@
.. _output_api_as_json_data:
=======================
Output API as JSON Data
=======================
As of 20-Jun-2024, LVGL comes packaged with a Python script
(``./scripts/gen_json/gen_json.py``) that reads the header files in LVGL and outputs
a more friendly JSON format for the API. This is done so that bindings that generate
code automatically will have an easy way to collect the needed information without
having to reinvent the wheel. JSON format was chosen because there are libraries for
reading JSON data in almost every programming language.
Requirements
************
- Python >= 3.10
- Pycparser >= 2.22: Python Library for reading C preprocessor output
- PyMSVC >= 0.4.0: Python library for using the MSVC Compiler
- A C compiler: gcc for Linux, clang for OSX and MSVC for Windows
- Doxygen: used to read Doxygen comments (the API documentation) from the header files.
Usage
*****
Command-Line Options
--------------------
- ``--output-path``: output directory for JSON file. If one is not supplied then it
will be output to stdout.
- ``--lvgl-config``: path to lv_conf.h (including file name). If this is not set then
a config file will be generated that has the most common LVGL options turned on.
- ``--develop``: leaves the files generated in the temporary folder in place.
Examples
--------
Normal usage:
.. code-block:: shell
python ./scripts/gen_json/gen_json.py --output-path=json/output/directory --lvgl-config=path/to/lv_conf.h
If you want to run a subprocess from inside of a generation script and read the output from stdout:
.. code-block:: shell
python ./scripts/gen_json/gen_json.py --lvgl-config=path/to/lv_conf.h
Output Data
-----------
The contents of the output file is a large JSON object (``{...}``) with the following
key/value pairs (these are the keys):
.. parsed-literal::
{
"enums" : [...],
"functions" : [...],
"function_pointers": [...],
"structures" : [...],
"unions" : [...],
"variables" : [...],
"typedefs" : [...],
"forward_decls" : [...],
"macros" : [...]
}
As you can see, the value of each of these elements is an array. The elements in
each array are JSON objects, each with a structure unique to the type indicated by
the parent element name (e.g. "enums", "functions", etc.).
A key/value pair has been added to each object (key = "json_type") to make it possible
to pass an object to a generic function and have each object know its own type through
this field. The possible "json_type" values are:
- ``"array"``: The array type is used to identify arrays.
Fields:
- ``"dim"``: number of items in array
- ``"quals"``: array of qualifiers, IE "const"
- ``"type"``: This may or may not be available.
- ``"name"``: name of data type
- ``"field"``: This type is used to describe fields in structures and unions.
It is used in the ``"fields"`` array of the ``"struct"`` and ``"union"`` types
covered below.
Fields:
- ``"name"``: field name
- ``"type"``: data type
``"json_type"`` carries object type (e.g. "enum", "function", etc.) identifying the top-level group it comes from
- ``"bitsize"``: The number of bits for bit-fields, or ``null`` for normal field types.
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"arg"``: Describes a function argument
Fields:
- ``"name"``: argument name
- ``"type"``: data type
``"json_type"`` carries object type (e.g. "enum", "function", etc.) identifying the top-level group it comes from.
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"quals"``: array of any qualifiers present, e.g. "const"
- ``"forward_decl"``: Describes a forward declaration. There are structures in
LVGL that are considered to be private and that is what these desccribe.
Fields:
- ``"name"``: name of forward declaration
- ``"type"``: data type
``"json_type"`` carries object type (e.g. "enum", "function", etc.) identifying the top-level group it comes from.
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"quals"``: array of any qualifiers present, e.g. "const"
- ``"function_pointer"``: Describes a function pointer. These are used when
registering callback functions in LVGL.
Fields:
- ``"name"``: name of function pointer
- ``"type"``: function return type
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"args"``: array of ``"arg"`` objects described above
- ``"quals"``: array of any qualifiers present, e.g. "const"
- ``"variable"``: Describes a global variable.
Fields:
- ``"name"``: variable name
- ``"type"``: data type
``"json_type"`` carries object type (e.g. "enum", "function", etc.) identifying the top-level group it comes from.
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"quals"``: array of any qualifiers present, e.g. "const"
- ``"storage"``: array of any storage-class specifiers present (e.g. "auto", "static", "extern", etc.)
- ``"special_type"``: Currently only used to describe an ellipsis argument of a function.
Fields:
- ``"name"``: always "ellipsis"
- ``"primitive_type"``: Data type that does not begin with ``"lv_"`` and end with
``"_t"``. Compare to ``"lvgl_type"`` This includes struct, union, integral types
(e.g. int, unsigned int), etc..
Fields:
- ``"name"``: name of primitive type
- ``"enum"``: C enumerations
Fields:
- ``"name"``: If enumeration is the result of a ``typedef``, this field carries
the type name defined. Example: ``lv_align_t``. (Not always available.)
- ``"type"``: type of enumerators (always "int")
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"members"``: array of ``"enum_member"`` objects
- ``"enum_member"``: enumerator (enumeration value). This "json_type" is only found
in the ``"members"`` array of an ``"enum"`` object
Fields:
- ``"name"``: enumerator name
- ``"type"``: If enumeration is the result of a ``typedef``, this field carries
the type name defined. Example: ``lv_align_t``.
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"value"``: enumerator value
- ``"lvgl_type"``: Data type defined in LVGL (begins with ``"lv_"`` and ends with ``"_t"``.
Fields:
- ``"name"``: type name
- ``"quals"``: array of any qualifiers present, e.g. "const"
- ``"struct"``: C struct
Fields:
- ``"name"``: struct name (data type if defined by ``typedef``)
- ``"type"``: a "primitive_type" object {"name": "struct", "json_type": "primitive_type"}. (See definition above.)
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"fields"``: array of ``"field"`` objects (See definition above.)
- ``"quals"``: array of any qualifiers present, e.g. "const"
- ``"union"``: C union
Fields:
- ``"name"``: union name (data type if defined by ``typedef``)
- ``"type"``: a "primitive_type" object {"name": "union", "json_type": "primitive_type"}. (See definition above.)
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"fields"``: array of ``"field"`` elements.
- ``"quals"``: array of any qualifiers present, e.g. "const"
- ``"macro"``: C macro. There is limited information that can be
collected about macros and in most cases a binding will need to have these
statically added to a binding. It is more for collecting the docstrings than
anything else.
Fields:
- ``"name"``: macro name
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"ret_type"``: return type from a function. This is only going to be seen in the ``"type"``
element of a ``"function"`` type.
Fields:
- ``"type"``: data type
``"json_type"`` carries object type (e.g. "enum", "function", etc.) identifying the top-level group it comes from.
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"function"``: C function
Fields:
- ``"name"``: function name
- ``"type"``: A "ret_type" object. (See definition above.)
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"args"``: array of ``"arg"`` json types. (See definition above.)
- ``"stdlib_type"``: C integral type (int, unsigned int, float, etc.)
Fields:
- ``"name"``: type name
- ``"quals"``: array of any qualifiers present, e.g. "const"
- ``"unknown_type"``: This should not be seen. If it is then there needs to be
an adjustment made to the script. Please open an issue and let us know if you see this type.
Fields:
- ``"name"``: type name
- ``"quals"``: array of any qualifiers present, e.g. "const"
- ``"pointer"``: C pointer
Fields:
- ``"type"``: pointer type
``"json_type"`` carries object type (e.g. "enum", "function", etc.) identifying the top-level group it comes from.
- ``"quals"``: array of any qualifiers present, e.g. "const"
- ``"typedef"``: C type definition
Fields:
- ``"name"``: type name (e.g. ``lv_part_t``)
- ``"type"``: a "primitive_type" object {"name": "uint32_t", "json_type": "stdlib_type"}. (See definition above.)
``"json_type"`` carries object type (e.g. "enum", "function", etc.) identifying the top-level group it comes from.
- ``"docstring"``: string containing Doxygen-extracted documentation
- ``"quals"``: array of any qualifiers present, e.g. "const"
Here is a shortened example of what the output looks like.
.. code-block:: json
{
"enums":[
{
"name":"_lv_result_t",
"type":{
"name":"int",
"json_type":"primitive_type"
},
"json_type":"enum",
"docstring":"LVGL error codes. ",
"members":[
{
"name":"LV_RESULT_INVALID",
"type":{
"name":"_lv_result_t",
"json_type":"lvgl_type"
},
"json_type":"enum_member",
"docstring":"",
"value":"0x0"
},
{
"name":"LV_RESULT_OK",
"type":{
"name":"_lv_result_t",
"json_type":"lvgl_type"
},
"json_type":"enum_member",
"docstring":"",
"value":"0x1"
}
]
}
],
"functions":[
{
"name":"lv_version_info",
"type":{
"type":{
"type":{
"name":"char",
"json_type":"primitive_type",
"quals":[
"const"
]
},
"json_type":"pointer",
"quals":[]
},
"json_type":"ret_type",
"docstring":""
},
"json_type":"function",
"docstring":"",
"args":[
{
"name":null,
"type":{
"name":"void",
"json_type":"primitive_type",
"quals":[]
},
"json_type":"arg",
"docstring":"",
"quals":[]
}
]
}
],
"function_pointers":[
{
"name":"lv_tlsf_walker",
"type":{
"type":{
"name":"void",
"json_type":"primitive_type",
"quals":[]
},
"json_type":"ret_type",
"docstring":""
},
"json_type":"function_pointer",
"docstring":"",
"args":[
{
"name":"ptr",
"type":{
"type":{
"name":"void",
"json_type":"primitive_type",
"quals":[]
},
"json_type":"pointer",
"quals":[]
},
"json_type":"arg",
"docstring":""
},
{
"name":"size",
"type":{
"name":"size_t",
"json_type":"stdlib_type",
"quals":[]
},
"json_type":"arg",
"docstring":""
},
{
"name":"used",
"type":{
"name":"int",
"json_type":"primitive_type",
"quals":[]
},
"json_type":"arg",
"docstring":""
},
{
"name":"user",
"type":{
"type":{
"name":"void",
"json_type":"primitive_type",
"quals":[]
},
"json_type":"pointer",
"quals":[]
},
"json_type":"arg",
"docstring":""
}
],
"quals":[]
}
],
"structures":[
{
"name":"_lv_grad_cache_t",
"type":{
"name":"struct",
"json_type":"primitive_type"
},
"json_type":"struct",
"docstring":null,
"fields":[
{
"name":"color_map",
"type":{
"type":{
"name":"lv_color_t",
"json_type":"lvgl_type",
"quals":[]
},
"json_type":"pointer",
"quals":[]
},
"json_type":"field",
"bitsize":null,
"docstring":""
},
{
"name":"opa_map",
"type":{
"type":{
"name":"lv_opa_t",
"json_type":"lvgl_type",
"quals":[]
},
"json_type":"pointer",
"quals":[]
},
"json_type":"field",
"bitsize":null,
"docstring":""
},
{
"name":"size",
"type":{
"name":"uint32_t",
"json_type":"stdlib_type",
"quals":[]
},
"json_type":"field",
"bitsize":null,
"docstring":""
}
]
}
],
"unions":[],
"variables":[
{
"name":"lv_global",
"type":{
"name":"lv_global_t",
"json_type":"lvgl_type",
"quals":[]
},
"json_type":"variable",
"docstring":"",
"quals":[],
"storage":[
"extern"
]
}
],
"typedefs":[
{
"name":"lv_pool_t",
"type":{
"type":{
"name":"void",
"json_type":"primitive_type",
"quals":[]
},
"json_type":"pointer"
},
"json_type":"typedef",
"docstring":"",
"quals":[]
}
],
"forward_decls":[
{
"name":"lv_fragment_managed_states_t",
"type":{
"name":"struct",
"json_type":"primitive_type"
},
"json_type":"forward_decl",
"docstring":"",
"quals":[]
}
],
"macros":[
{
"name":"ZERO_MEM_SENTINEL",
"json_type":"macro",
"docstring":""
}
]
}

View File

@@ -0,0 +1,5 @@
===
Cpp
===
In progress: https://github.com/lvgl/lv_binding_cpp

View File

@@ -0,0 +1,13 @@
========
Bindings
========
.. toctree::
:maxdepth: 2
api_json
cpp
javascript
micropython
pikascript

View File

@@ -0,0 +1,122 @@
==========
JavaScript
==========
With `lv_binding_js <https://github.com/lvgl/lv_binding_js>`__ you can use LVGL from within JavaScript.
It uses React's virtual DOM concept to manipulate LVGL UI components, providing a familiar React-like
experience to users.
**Code**
**Code Running on Real Device**
Features
********
- Support all lvgl built-in components
- Fully support lvgl flex and grid style
- support most lvgl style, just write like html5 css
- support dynamic load image
- Fully support lvgl animation
Demo
****
See the `demo <https://github.com/lvgl/lv_binding_js/tree/master/demo>`__ folder
Building
********
The following are developer notes on how to build lvgljs on your native platform. They are not complete guides,
but include notes on the necessary libraries, compile flags, etc.
lvgljs
------
- `Build Notes for embedded Linux device <https://github.com/lvgl/lv_binding_js/blob/master/doc/build/build-device.md>`__
- `Build Notes for SDL Simulator (Linux and macOS) <https://github.com/lvgl/lv_binding_js/blob/master/doc/build/build-simulator.md>`__
JS Bundle
---------
- `JS Bundle build Notes <https://github.com/lvgl/lv_binding_js/blob/master/doc/build/js-bundle.md>`__
Components
**********
- `View <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/View.md>`__
- `Image <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Image.md>`__
- `Button <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Button.md>`__
- `Text <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Text.md>`__
- `Input <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Input.md>`__
- `Textarea <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Textarea.md>`__
- `Switch <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Switch.md>`__
- `Checkbox <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Checkbox.md>`__
- `Dropdownlist <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Dropdownlist.md>`__
- `ProgressBar <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/ProgressBar.md>`__
- `Line <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Line.md>`__
- `Roller <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Roller.md>`__
- `Keyboard <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Keyboard.md>`__
- `Calendar <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Calendar.md>`__
- `Chart <https://github.com/lvgl/lv_binding_js/blob/master/doc/component/Chart.md>`__
Font
****
- `Builtin-Symbol <https://github.com/lvgl/lv_binding_js/blob/master/doc/Symbol/symbol.md>`__
Animation
*********
- `Animation <https://github.com/lvgl/lv_binding_js/blob/master/doc/animate/animate.md>`__
Style
*****
.. include::https://github.com/lvgl/lv_binding_js/blob/master/doc/style/position-size-layout.md
- `position-size-layout <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/position-size-layout.md>`__
- `boxing-model <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/boxing-model.md>`__
- `color <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/color.md>`__
- `flex <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/flex.md>`__
- `grid <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/grid.md>`__
- `font <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/font.md>`__
- `opacity <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/opacity.md>`__
- `display <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/display.md>`__
- `background <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/background.md>`__
- `scroll <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/scroll.md>`__
- `shadow <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/shadow.md>`__
- `recolor <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/recolor.md>`__
- `line <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/line.md>`__
- `transition <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/transition.md>`__
- `transform <https://github.com/lvgl/lv_binding_js/blob/master/doc/style/transform.md>`__
JSAPI
*****
- `network <https://github.com/lvgl/lv_binding_js/blob/master/doc/jsapi/network.md>`__
- `filesystem <https://github.com/lvgl/lv_binding_js/blob/master/doc/jsapi/fs.md>`__
- `dimension <https://github.com/lvgl/lv_binding_js/blob/master/doc/jsapi/dimension.md>`__
Thanks
******
**lvgljs** depends on following excellent work:
- `lvgl <https://github.com/lvgl/lvgl>`__: Create beautiful UIs for any MCU, MPU and display type
- `QuickJS <https://bellard.org/quickjs/>`__: JavaScript engine
- `libuv <https://github.com/libuv/libuv>`__: platform abstraction layer
- `curl <https://github.com/curl/curl>`__: HTTP client
- `txiki.js <https://github.com/saghul/txiki.js>`__: Tiny JavaScript runtime

View File

@@ -0,0 +1,313 @@
.. _micropython:
===========
MicroPython
===========
What is MicroPython?
********************
`MicroPython <http://micropython.org/>`__ is Python for microcontrollers. Using
MicroPython, you can write Python3 code and run it even on a bare metal architecture
with limited resources. One of its powerful features is the ability to change the
behavior of a device by changing the Python code on removable (or internal) storage,
without having to change the device's firmware.
Highlights of MicroPython
-------------------------
:Compact: Fits and runs within just 256k of code space and 16k of RAM. No OS is
needed, although you can also run it with an OS, if you want.
:Compatible: Strives to be as compatible as possible with normal Python (known as CPython).
:Versatile: Supports many architectures (x86, x86-64, ARM, ARM Thumb, Xtensa).
:Interactive: No need for the compile-flash-boot cycle. With the REPL (interactive
prompt) you can type commands and execute them immediately, run scripts, etc.
:Popular: Many platforms are supported. The user base is growing larger. Notable forks:
- `MicroPython <https://github.com/micropython/micropython>`__
- `CircuitPython <https://github.com/adafruit/circuitpython>`__
- `MicroPython_ESP32_psRAM_LoBo <https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo>`__
:Embedded Oriented: Comes with modules specifically for embedded systems, such as the
`machine module <https://docs.micropython.org/en/latest/library/machine.html#classes>`__
for accessing low-level hardware (I/O pins, ADC, UART, SPI, I2C, RTC, Timers etc.)
--------------
Why MicroPython + LVGL?
***********************
MicroPython `does not have a good native high-level GUI library <https://forum.micropython.org/viewtopic.php?f=18&t=5543>`__.
LVGL is an `Object-Oriented Component Based <https://blog.lvgl.io/2018-12-13/extend-lvgl-objects>`__
high-level GUI library, which is a natural candidate to map into a higher level language, such as Python.
LVGL is implemented in C and its APIs are in C.
Here are some advantages of using LVGL in MicroPython:
------------------------------------------------------
- Develop GUI in Python, a very popular high level language. Use paradigms such as Object-Oriented Programming.
- Usually, GUI development requires multiple iterations to get things right. With C, each iteration consists of
**``Change code`` > ``Build`` > ``Flash`` > ``Run``**. In MicroPython it's just
**``Change code`` > ``Run``** ! You can even run commands interactively using the
`REPL <https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop>`__ (the interactive prompt)
MicroPython + LVGL could be used for:
-------------------------------------
- Fast GUI prototyping
- Shortening the cycle of changing and fine-tuning a GUI
- Modelling a GUI in a more abstract way by defining reusable composite Widgets,
taking advantage of Python's language features such as Inheritance, Closures, List
Comprehension, Generators, Exception Handling, Arbitrary Precision Integers and others.
- Make LVGL accessible to a larger audience. No need to know C to create a nice GUI
on an embedded system. This goes well with
`CircuitPython vision <https://learn.adafruit.com/welcome-to-circuitpython/what-is-circuitpython>`__.
CircuitPython was designed with education in mind, to make it easier for new or
inexperienced programmers to get started with embedded development.
- Creating tools to work with LVGL at a higher level (e.g. drag-and-drop designer).
--------------
What does it look like?
***********************
It's very much like the C API, but Object-Oriented for LVGL components.
Let's dive right into an example!
A simple example
----------------
.. code-block:: python
# Initialize
import display_driver
import lvgl as lv
# Create a button with a label
scr = lv.obj()
btn = lv.button(scr)
btn.align(lv.ALIGN.CENTER, 0, 0)
label = lv.label(btn)
label.set_text('Hello World!')
lv.screen_load(scr)
How Can I Use It?
*****************
Online Simulator
----------------
If you want to experiment with LVGL + MicroPython without downloading anything, you can use our online
simulator! It's a fully functional LVGL + MicroPython that runs entirely in the browser and allows you to
edit a python script and run it.
`Click here to experiment on the online simulator <https://sim.lvgl.io/>`__
Many :ref:`LVGL examples <examples>` are available also for MicroPython. Just click the link!
PC Simulator
------------
MicroPython is ported to many platforms. One notable port is to "Unix", which allows
you to build and run MicroPython (+LVGL) on a Linux machine. (On a Windows machine
you might need Virtual Box or WSL or MinGW or Cygwin etc.)
`Click here to learn more about building and running the Unix port. <https://github.com/lvgl/lv_micropython>`__
Embedded Platforms
------------------
In the end, the goal is to run it all on an embedded platform. Both MicroPython and LVGL can
be used on many embedded architectures. `lv_micropython <https://github.com/lvgl/lv_micropython>`__
is a fork of MicroPython+LVGL and currently supports Linux, ESP32, STM32 and RP2.
It can be ported to any other platform supported by MicroPython.
- You would also need display and input drivers. You can either use one of the
existing drivers provided with lv_micropython, or you can create your own
input/display drivers for your specific hardware.
- Drivers can be implemented either in C as a MicroPython module, or in pure Python.
**lv_micropython** already contains these drivers:
- Display drivers:
- SDL on Linux
- X11 on Linux
- ESP32 specific:
- ILI9341
- ILI9488
- GC9A01
- ST7789
- ST7735
- Generic (pure Python):
- ILI9341
- ST7789
- ST7735
- Input drivers:
- SDL
- X11
- XPT2046
- FT6X36
- ESP32 ADC with resistive touch
Where can I find more information?
**********************************
- ``lv_micropython`` `README <https://github.com/lvgl/lv_micropython>`__
- ``lv_binding_micropython`` `README <https://github.com/lvgl/lv_binding_micropython>`__
- The `LVGL micropython forum <https://forum.lvgl.io/c/micropython>`__ (Feel free to ask anything!)
- At MicroPython: `docs <http://docs.micropython.org/en/latest/>`__ and `forum <https://forum.micropython.org/>`__
- `Blog Post <https://blog.lvgl.io/2019-02-20/micropython-bindings>`__, a little outdated.
The MicroPython Binding is auto generated!
******************************************
- LVGL is a git submodule inside `lv_micropython <https://github.com/lvgl/lv_micropython>`__
(LVGL is a git submodule of `lv_binding_micropython <https://github.com/lvgl/lv_binding_micropython>`__
which is itself a submodule of `lv_micropython <https://github.com/lvgl/lv_micropython>`__).
- When building lv_micropython, the public LVGL C API is scanned and MicroPython API is auto-generated. That means that
lv_micropython provides LVGL API for **any** LVGL version, and generally does not require code changes as LVGL evolves.
LVGL C API Coding Conventions
-----------------------------
For a summary of coding conventions to follow see the :ref:`coding-style`.
.. _memory_management:
Memory Management
-----------------
- When LVGL runs in MicroPython, all dynamic memory allocations (:cpp:func:`lv_malloc`) are handled by MicroPython's memory
manager which is `garbage-collected <https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)>`__ (GC).
- To prevent GC from collecting memory prematurely, all dynamic allocated RAM must be reachable by the GC.
- GC is aware of most allocations, except from pointers to the `Data Segment <https://en.wikipedia.org/wiki/Data_segment>`__:
- Pointers which are global variables
- Pointers which are static global variables
- Pointers which are static local variables
Such pointers need to be defined in a special way to make them reachable by the GC.
Identify The Problem
~~~~~~~~~~~~~~~~~~~~
A problem occurs when an allocated memory's pointer (return value of :cpp:func:`lv_malloc`)
is stored only in either **global**, **static global** or **static local** pointer
variable and not as part of a previously allocated ``struct`` or other variable.
Solving the Problem
~~~~~~~~~~~~~~~~~~~
- Replace the global/static local var with :cpp:expr:`(LV_GLOBAL_DEFAULT()->_var)`
- Include ``lv_global.h`` on files that use ``LV_GLOBAL_DEFAULT``
- Add ``_var`` to ``lv_global_t`` on ``lv_global.h``
Example
~~~~~~~
Further Reading on Memory Management
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- `In the README <https://github.com/lvgl/lv_binding_micropython#memory-management>`__
- `In the Blog <https://blog.lvgl.io/2019-02-20/micropython-bindings#i-need-to-allocate-a-littlevgl-struct-such-as-style-color-etc-how-can-i-do-that-how-do-i-allocatedeallocate-memory-for-it>`__
.. _callbacks:
Callbacks
---------
In C a callback is just a function pointer. But in MicroPython we need to register a *MicroPython callable object* for each
callback. Therefore in the MicroPython binding we need to register both a function pointer and a MicroPython object for every callback.
Therefore we defined a **callback convention** for the LVGL C API that expects LVGL headers to be defined in a certain
way. Callbacks that are declared according to this convention allow the binding to register a MicroPython object
next to the function pointer when registering a callback, and access that object when the callback is called.
- The basic idea is that we have ``void * user_data`` field that is used automatically by the MicroPython Binding
to save the *MicroPython callable object* for a callback. This field must be provided when registering the function
pointer, and provided to the callback function itself.
- Although called "user_data", the user is not expected to read/write that field. Instead, the MicroPython glue code uses
``user_data`` to automatically keep track of the MicroPython callable object. The glue code updates it when the callback
is registered, and uses it when the callback is called in order to invoke a call to the original callable object.
There are a few options for defining a callback in LVGL C API:
- Option 1: ``user_data`` in a struct
- There's a struct that contains a field called ``void * user_data``
- A pointer to that struct is provided as the **first** argument of a callback registration function.
- A pointer to that struct is provided as the **first** argument of the callback itself.
- Option 2: ``user_data`` as a function argument
- A parameter called ``void * user_data`` is provided to the registration function as the **last** argument
- The callback itself receives ``void *`` as the **last** argument
- Option 3: both callback and ``user_data`` are struct fields
- The API exposes a struct with both function pointer member and ``user_data`` member
- The function pointer member receives the same struct as its **first** argument
In practice it's also possible to mix these options, for example provide a struct pointer when registering a callback
(option 1) and provide ``user_data`` argument when calling the callback (options 2),
**as long as the same** ``user_data`` **that was registered is passed to the callback when it's called**.
Examples
~~~~~~~~
- :cpp:type:`lv_anim_t` contains ``user_data`` field. :cpp:func:`lv_anim_set_path_cb` registers `path_cb` callback.
Both ``lv_anim_set_path_cb`` and :cpp:type:`lv_anim_path_cb_t` receive :cpp:type:`lv_anim_t` as their first argument
- ``path_cb`` field can also be assigned directly in the Python code because it's a member of :cpp:type:`lv_anim_t`
which contains ``user_data`` field, and :cpp:type:`lv_anim_path_cb_t` receive :cpp:type:`lv_anim_t` as its first argument.
- :cpp:func:`lv_imgfont_create` registers ``path_cb`` and receives ``user_data`` as the last argument.
The callback :cpp:type:`lv_imgfont_get_path_cb_t` also receives the ``user_data`` as the last argument.
.. _more-information-1:
Further Reading on Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- In the `Blog <https://blog.lvgl.io/2019-08-05/micropython-pure-display-driver#using-callbacks>`__
and in the `README <https://github.com/lvgl/lv_binding_micropython#callbacks>`__
- `[v6.0] Callback conventions #1036 <https://github.com/lvgl/lvgl/issues/1036>`__
- Various discussions: `here <https://github.com/lvgl/lvgl/pull/3294#issuecomment-1184895335>`__
and `here <https://github.com/lvgl/lvgl/issues/1763#issuecomment-762247629>`__
and `here <https://github.com/lvgl/lvgl/issues/316#issuecomment-467221587>`__

View File

@@ -0,0 +1,201 @@
==========
PikaScript
==========
What is PikaScript?
*******************
`PikaScript <https://github.com/pikasTech/pikascript>`__ is a Python interpreter designed specifically for
microcontrollers, and it supports a subset of the common Python3 syntax.
It's lighter than MicroPython, requiring only 32k of code space and 4k of RAM, which
means it can run on stm32f103c8 (blue-pill) or even stm32g030c8. On the other hand,
you can leave valuable space for more material or larger buffer areas.
It is simpler, out of the box, runs with no porting and configuration at all, does not depend on OS or file
system, has good support for popular IDEs for Windows platforms like Keil, IAR, RT-Thread-Studio, and of course,
supports Linux gcc development platforms.
It's smarter, with a unique C module mechanism that allows you to generate bindings automatically by simply
writing the API for the C module in Python, and you don't need to deal with the headache of writing any macros
or global tables manually. On the other hand, all C modules have sophisticated smart hints, even hinting at the types
of your arguments .
--------------
Why PikaScript + LVGL?
**********************
- PikaScript now supports the main features of LVGL8, and these APIs are fully compatible with MicroPython.
This means that you can continue to use already written code from MicroPython, but use less code space and RAM.
- Enjoy detailed code hints down to the parameter type for a better programming experience.
- Use a more convenient IDE, such as Visual-Studio-based simulation projects.
What Does It Look Like?
***********************
Here are some examples of using LVGL that PikaScript can already run. They are mainly
from the LVGL documentation examples.
LV_ARC
------
.. code-block:: python
import pika_lvgl as lv
import PikaStdLib
mem = PikaStdLib.MemChecker()
# Create an Arc
arc = lv.arc(lv.screen_active())
arc.set_end_angle(200)
arc.set_size(150, 150)
arc.center()
print('mem used max: %0.2f kB' % (mem.getMax()))
print('mem used now: %0.2f kB' % (mem.getNow()))
LV_BAR
------
.. code-block:: python
import pika_lvgl as lv
import PikaStdLib
mem = PikaStdLib.MemChecker()
bar1 = lv.bar(lv.screen_active())
bar1.set_size(200, 20)
bar1.center()
bar1.set_value(70, lv.ANIM.OFF)
print('mem used max: %0.2f kB' % (mem.getMax()))
print('mem used now: %0.2f kB' % (mem.getNow()))
LV_BTN
------
.. code-block:: python
import pika_lvgl as lv
import PikaStdLib
mem = PikaStdLib.MemChecker()
def event_cb_1(evt):
print('in evt1')
print('mem used now: %0.2f kB' % (mem.getNow()))
def event_cb_2(evt):
print('in evt2')
print('mem used now: %0.2f kB' % (mem.getNow()))
btn1 = lv.btn(lv.screen_active())
btn1.align(lv.ALIGN.TOP_MID, 0, 10)
btn2 = lv.btn(lv.screen_active())
btn2.align(lv.ALIGN.TOP_MID, 0, 50)
btn1.add_event_cb(event_cb_1, lv.EVENT.CLICKED, 0)
btn2.add_event_cb(event_cb_2, lv.EVENT.CLICKED, 0)
print('mem used max: %0.2f kB' % (mem.getMax()))
print('mem used now: %0.2f kB' % (mem.getNow()))
LV_CHECKBOX
-----------
.. code-block:: python
import pika_lvgl as lv
import PikaStdLib
mem = PikaStdLib.MemChecker()
cb = lv.checkbox(lv.screen_active())
cb.set_text("Apple")
cb.align(lv.ALIGN.TOP_LEFT, 0 ,0)
cb = lv.checkbox(lv.screen_active())
cb.set_text("Banana")
cb.add_state(lv.STATE.CHECKED)
cb.align(lv.ALIGN.TOP_LEFT, 0 ,30)
cb = lv.checkbox(lv.screen_active())
cb.set_text("Lemon")
cb.add_state(lv.STATE.DISABLED)
cb.align(lv.ALIGN.TOP_LEFT, 0 ,60)
cb = lv.checkbox(lv.screen_active())
cb.add_state(lv.STATE.CHECKED | lv.STATE.DISABLED)
cb.set_text("Melon")
cb.align(lv.ALIGN.TOP_LEFT, 0 ,90)
print('mem used max: %0.2f kB' % (mem.getMax()))
print('mem used now: %0.2f kB' % (mem.getNow()))
--------------
How Does It Work?
*****************
PikaScript has a unique C module smart binding tool.
Just write the Python interface in pika_lvgl.pyi (.pyi is a Python interface file)
.. code-block:: python
# pika_lvgl.pyi
class arc(lv_obj):
def set_end_angle(self, angle: int): ...
def set_bg_angles(self, start: int, end: int): ...
def set_angles(self, start: int, end: int): ...
Then PikaScript's pre-compiler can automatically bind the following C functions, simply by naming the functions
in the module_class_method format, without any additional work, and all binding and registration is done automatically.
.. code-block:: c
/* pika_lvgl_arc.c */
void pika_lvgl_arc_set_end_angle(PikaObj* self, int angle) {
lv_obj_t* lv_obj = obj_getPtr(self, "lv_obj");
lv_arc_set_end_angle(lv_obj, angle);
}
void pika_lvgl_arc_set_bg_angles(PikaObj *self, int start, int end){
lv_obj_t* lv_obj = obj_getPtr(self, "lv_obj");
lv_arc_set_bg_angles(lv_obj, start, end);
}
void pika_lvgl_arc_set_angles(PikaObj *self, int start, int end){
lv_obj_t* lv_obj = obj_getPtr(self, "lv_obj");
lv_arc_set_angles(lv_obj, start, end);
}
To use the module, just ``import pika_lvgl`` and the precompiler will automatically scan main.py and bind the
``pika_lvgl`` module.
.. code-block:: shell
$ ./rust-msc-latest-win10.exe
(pikascript) packages installed:
pikascript-core==v1.10.0
PikaStdLib==v1.10.0
PikaStdDevice==v1.10.0
(pikascript) pika compiler:
scanning main.py...
binding pika_lvgl.pyi...
The precompiler is written in Rust, runs on Windows and Linux, and is completely open source.
In addition to binding C modules, the precompiler compiles Python scripts to bytecode in the PC, reducing the
size of the script and increasing its speed.
--------------
How Can I Use It?
*****************
The simulation repository for Visual Studio is available at https://github.com/pikasTech/lv_pikascript .

View File

@@ -0,0 +1,10 @@
======
Boards
======
.. toctree::
:maxdepth: 2
toradex
riverdi
viewe

View File

@@ -0,0 +1,71 @@
=======
Riverdi
=======
`Riverdi <https://riverdi.com/>`__ specializes in making high quality
displays and the boards that carry them. They offer solutions that
range from simple display panels to intelligent displays, and
everything in between.
- STM32 Embedded Displays
- Single Board Computer Displays
- EVE Intelligent Displays
- HDMI Displays
- Evaluation Boards
- RGB, LVDS, MIPI DSI LCD Displays
STM32 Embedded Displays
***********************
The STM32 Embedded Displays have an STM32 MCU onboard which makes
it possible to embed an entire application into the device. LVGL
is well-suited to these boards.
lv_port_riverdi_stm32u5
-----------------------
`lv_port_riverdi_stm32u5 <https://github.com/lvgl/lv_port_riverdi_stm32u5>`_
is a port repo of LVGL which supports all 5-inch Riverdi STM32
Embedded Displays.
- `RVT50HQSNWC00-B <https://riverdi.com/product/5-inch-lcd-display-capacitive-touch-panel-optical-bonding-uxtouch-stm32u5-rvt50hqsnwc00-b>`_
- `RVT50HQSNWC00 <https://riverdi.com/product/5-inch-lcd-display-capacitive-touch-panel-air-bonding-uxtouch-stm32u5-rvt50hqsnwc00>`_
- `RVT50HQSFWCA0 <https://riverdi.com/product/5-inch-lcd-display-capacitive-touch-panel-air-bonding-atouch-frame-stm32u5-rvt50hqsfwca0>`_
- `RVT50HQSNWCA0 <https://riverdi.com/product/5-inch-lcd-display-capacitive-touch-panel-air-bonding-atouch-stm32u5-rvt50hqsnwca0>`_
- `RVT50HQSFWN00 <https://riverdi.com/product/5-inch-lcd-display-stm32u5-frame-rvt50hqsfwn00>`_
- `RVT50HQSNWN00 <https://riverdi.com/product/5-inch-lcd-display-stm32u5-rvt50hqsnwn00>`_
It natively supports the embedded NeoChrom GPU thanks to LVGL's support
for :ref:`Nema GFX <nema_gfx>`. NemaVG --- an extension to Nema's
base support --- is implemented by this MCU.
The MCU has an LCD driver peripheral which LVGL uses to update the
display. See the :ref:`LTDC <stm32 ltdc driver>` driver docs
for more info and how to customize its use.
Single-Board Computer Displays
******************************
The Single-Board Computer Displays are ready to use with
:ref:`Toradex <toradex>` Dahlia and Mallow carrier boards. In fact,
those carrier boards are compatible with all 34-pin Riverdi
MIPI-DSI displays.
Other Products
**************
The **EVE Intelligent Displays** feature a Bridgetek EVE graphics
controller IC so that the display can be controlled using
high-level drawing commands over a lower-bandwidth interface than
RGB, MIPI, etc.
The **HDMI Displays** sport an HDMI interface for streamlined
integration with PCs or SBCs. These displays are still low-profile
so they can be embedded into custom fixtures with ease.
The **Evaluation Boards** are affordable carrier boards for getting
Riverdi display panels up and running before designing or buying a
carrier board for a production application.

View File

@@ -0,0 +1,182 @@
.. _toradex:
.. |reg| unicode:: U+000AE .. REGISTERED SIGN
=======
Toradex
=======
About Toradex
*************
Toradex specializes in manufacturing high-quality embedded computing solutions
based on System on Module units (SoMs). These boards and modules are designed to
meet the needs of industrial and commercial applications, offering reliability,
longevity, and scalability. Leveraging industry-standard frameworks like Yocto
Project, Toradex enables customers to create tailored embedded systems with
ease, ensuring compatibility and flexibility.
Products Portfolio
******************
Toradex offers a wide range of system-on-modules (SoMs) and carrier boards
designed for various applications and industries. Here's a basic overview to
help you navigate the portfolio.
Aquila
------
Aquila is a new System on Module standard expanding the Toradex portfolio to
new heights, offering unparalleled performance for computationally intensive
CPU, machine learning, and vision workloads.
To power next-gen's AI requirements in industrial and other harsh environments,
Aquila introduces a proven connector: the 400-pin board-to-board connector,
making the board extra reliable, especially in these environments.
Aquila Computer on Module:
- `Aquila iMX95 <https://www.toradex.com/computer-on-modules/
aquila-arm-family/nxp-imx95>`_
- `Aquila AM69 <https://www.toradex.com/computer-on-modules/
aquila-arm-family/ti-am69>`_
Aquila Carrier Boards:
- `Clover <https://www.toradex.com/products/carrier-board/clover>`_
- `Aquila Development Board <https://www.toradex.com/products/carrier-board/
aquila-development-board-kit>`_
Apalis
------
Apalis is a scalable System on Module (SoM) / Computer on Module (CoM) family
that aims to provide high performance in a compact form factor. Apalis enables
the development of advanced and robust products within a short time and with
low risks. Apalis modules scale to the highest performance in the Toradex
product range, and they provide the most interfaces of any product line on the
314-pin MXM connectors. Apalis modules are suited to a larger number of
applications in such fields as healthcare, industrial automation, robotics,
smart cities and many more. Apalis modules are supported by Toradex software
offerings and the company's rich ecosystem of other products and services.
Apalis Computer on Module:
- `Apalis iMX8 <https://www.toradex.com/computer-on-modules/
apalis-arm-family/nxp-imx-8>`_
- `Apalis iMX6 <https://www.toradex.com/computer-on-modules/
apalis-arm-family/nxp-freescale-imx-6>`_
- `Apalis T30 <https://www.toradex.com/computer-on-modules/
apalis-arm-family/nvidia-tegra-3>`_
Apalis Carrier Boards:
- `Ixora <https://www.toradex.com/products/carrier-board/
ixora-carrier-board>`_
- `Apalis Evaluation Board <https://www.toradex.com/products/carrier-board/
ixora-carrier-board>`_
Colibri
-------
The Colibri Arm\ |reg| family consists of SODIMM-sized System on Modules (SoM) /
Computer on Modules (CoM). The family offers miniaturized modules based on NXP\ |reg|
i.MX 8X, i.MX 6ULL, i.MX 7, i.MX 6, Vybrid and NVIDIA\ |reg| Tegra SoCs. All these
modules are pin-compatible, ensuring scalability of applications developed on
them. The Colibri family delivers cost-performance-optimized designs. An
extensive range of Colibri-compatible carrier boards and accessories, such as
cameras and displays, is also available for easy product development on the
Colibri Arm family of modules.
Colibri Computer on Module:
- `Colibri iMX8X <https://www.toradex.com/computer-on-modules/
colibri-arm-family/nxp-imx-8x>`_
- `Colibri T30 <https://www.toradex.com/computer-on-modules/
colibri-arm-family/nvidia-tegra-3>`_
- `Colibri T20 <https://www.toradex.com/computer-on-modules/
colibri-arm-family/nvidia-tegra-2>`_
- `Colibri iMX6 <https://www.toradex.com/computer-on-modules/
colibri-arm-family/nxp-freescale-imx6>`_
- `Colibri iMX7 <https://www.toradex.com/computer-on-modules/
colibri-arm-family/nxp-freescale-imx7>`_
- `Colibri iMXiMX6ULL8X <https://www.toradex.com/computer-on-modules/
colibri-arm-family/nxp-imx6ull>`_
- `Colibri VF61 <https://www.toradex.com/computer-on-modules/
colibri-arm-family/nxp-freescale-vybrid-vf6xx>`_
- `Colibri VF50 <https://www.toradex.com/computer-on-modules/
colibri-arm-family/nxp-freescale-vybrid-vf5xx>`_
Colibri Carrier Boards:
- `Colibri Evaluation Board <https://www.toradex.com/products/carrier-board/
colibri-evaluation-board>`_
- `Iris Carrier Board <https://www.toradex.com/products/carrier-board/
iris-carrier-board>`_
- `Viola Carrier Board <https://www.toradex.com/products/carrier-board/
viola-carrier-board>`_
- `Aster Carrier Board <https://www.toradex.com/products/carrier-board/
aster-carrier-board>`_
Verdin
------
Verdin is a System on Module standard expanding on the successful Colibri and
Apalis form factors. It provides a revised, modern and capable interface set
and focuses on ease-of-use, robustness and being future-proof.
Verdin is similar sized to the Colibri but with a higher pin count. A DDR4
SODIMM edge connector provides a cost-effective, highly reliable, shock- and
vibration-resistant connection.
Verdin Computer on Module:
- `Verdin iMX8M Plus <https://www.toradex.com/computer-on-modules/
verdin-arm-family/nxp-imx-8m-plus>`_
- `Verdin iMX8M Mini <https://www.toradex.com/computer-on-modules/
verdin-arm-family/nxp-imx-8m-mini-nano>`_
- `Verdin AM62 <https://www.toradex.com/computer-on-modules/
verdin-arm-family/ti-am62>`_
- `i.MX 95 Verdin Evaluation Kit <https://www.toradex.com/
computer-on-modules/verdin-arm-family/nxp-imx95-evaluation-kit>`_
- `Verdin iMX95 <https://www.toradex.com/computer-on-modules/
verdin-arm-family/nxp-imx95-evaluation-kit#verdin-imx95>`_
Verdin Carrier Boards:
- `Verdin Development Board with HDMI Adapter <https://www.toradex.com/
products/carrier-board/verdin-development-board-kit>`_
- `Dahlia Carrier Board with HDMI Adapter <https://www.toradex.com/products/
carrier-board/dahlia-carrier-board-kit>`_
- `Yavia <https://www.toradex.com/products/carrier-board/yavia>`_
- `Mallow Carrier Board <https://www.toradex.com/products/carrier-board/
mallow-carrier-board>`_
- `Ivy Carrier Board <https://www.toradex.com/products/carrier-board/
ivy-carrier-board>`_
TorizonOS
*********
TorizonOS is a Linux-based operating system developed by Toradex, tailored for
embedded systems. It is designed to simplify the development, deployment, and
maintenance of applications, especially in industrial and IoT settings.
Combining ease of use with powerful features like containerization and
over-the-air (OTA) updates, TorizonOS helps developers accelerate
time-to-market while ensuring robust performance and security for embedded
applications.
A TorizonOS guide to develop an application using LVGL can be found in the
:ref:`torizon-os-section` section.
More information is provided in the `Torizon documentation <https://www.torizon.
io/torizon-os>`_.
Toradex Examples
****************
There are existing ready to use repositories available. Click `here <https://
github.com/lvgl?q=lv_port_toradex&type=all&language=&sort=>`_ to check them out.

View File

@@ -0,0 +1,68 @@
=====
Viewe
=====
`Viewe <https://viewedisplay.com/>`__ specializes in comprehensive display solutions
for more than 10 Years with remarkable capability of software and hardware R&D,
factory manufacturing and sales service. We not only offer one stop
`bare display and touch solutions <https://viewedisplay.com/display-solutions/>`_
but also `embedded smart displays <https://viewedisplay.com/smart-solutions/>`_
with standard productions and tailored customization to develop your concepts
to reality.
Products Portfolio:
*******************
- Embedded Smart Displays
- `IOT_AIOT Smart Display <https://viewedisplay.com/iot_aiot-smart-display/>`_
- `Uart Smart Display <https://viewedisplay.com/uart-smart-display/>`_
- `HDMI Display_Raspberry Pi Display <https://viewedisplay.com/hdmi-display-raspberry-pi-display/>`_
- Arduino Display
TFT | OLED Display
- `Sunlight Readable Display <https://viewedisplay.com/sunlight-readable-display/>`_
- `AMOLED Displays <https://viewedisplay.com/amoled-display-list/>`_
- `Transflective TFT Displays <https://viewedisplay.com/transflective-tft-display/>`_
- `Standard TFT Displays <https://viewedisplay.com/standard-display/>`_
- `Bar Display/Stretched TFT Display <https://viewedisplay.com/bar-display-stretched-display/>`_
- `Square TFT Display <https://viewedisplay.com/square-display/>`_
- `Round TFT Display <https://viewedisplay.com/round-display/>`_
IOT AIOT Smart Displays
***********************
The Viewe IOT AIOT Smart Displays are a series of displays that use ESP32
microcontrollers with integrated multi-modal interactions of GUI /voice/camera, ML/AI
edge computing, and seamless connectivity. UART/RS232/RS485/CAN Interfaces and
redundant IO interfaces are included to facilitate extended usage. LVGL/Arduino/ESP-IDF
is well-suited to these boards.
lv_port_viewe_7_espidf
----------------------
`lv_port_viewe_7_espidf <https://github.com/lvgl/lv_port_viewe_7_espidf>`_
is a port repo of LVGL which supports Viewe ESP32 S3 Smart Display 7.0 inch.
It is based on ESP-IDF and uses the ESP32-S3 microcontroller.
- `Viewe ESP32 S3 Smart Display <https://viewedisplay.com/product/esp32-7-inch-800x480-rgb-ips-tft-display-touch-screen-arduino-lvgl-uart/>`_
UART Smart Displays
*******************
The Viewe UART Smart Displays are a series of displays that use ESP32 and STM32
microcontrollers with UART/RS232/RS485/CAN Interface and have IO interface for
connecting to peripherals. LVGL is well-suited to these boards.
lv_port_viewe_7_espidf
----------------------
`lv_port_viewe_7_espidf <https://github.com/lvgl/lv_port_viewe_7_espidf>`_
is a port repo of LVGL which supports Viewe ESP32 S3 Smart Display 7.0 inch.
It is based on ESP-IDF and uses the ESP32-S3 microcontroller.
- `Viewe ESP32 S3 Smart Display <https://viewedisplay.com/product/esp32-7-inch-800x480-rgb-ips-tft-display-touch-screen-arduino-lvgl-uart/>`_

View File

@@ -0,0 +1,230 @@
.. _build_cmake:
=====
CMake
=====
Overview
********
CMake is a cross-platform build system generator. It is used to easily integrate a project/library into another project.
It also offers the possibility to configure the build with different options, to enable or disable components, or to
integrate custom scripts executions during the configuration/build phase.
LVGL includes CMake natively, which means that one can use it to configure and build LVGL directly or integrate it into an higher level cmake build.
This project uses CMakePresets to ensure an easy build.
Find out more on Cmake Presets here: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html
Prerequisites
-------------
You need to install
- CMake with GNU make or Ninja (for Linux builds). Be sure to add ninja/make to your PATH!
- The prerequisites listed in ``scripts/install-prerequisites.sh/bat``
- A python3 interpreter if you wish to use KConfig.
How to build this project using cmake
-------------------------------------
Build with Command line
~~~~~~~~~~~~~~~~~~~~~~~
The simplest way to build LVGL using cmake is to use the command line calls:
.. code-block:: bash
# Method 1
cd <lvgl_repo>
mkdir build
cd build
cmake .. # Configure phase
cmake --build . # Build phase
# Method 2
cd <lvgl_repo>
cmake -B build # Configure phase
cmake --build build # build phase
Build with cmake presets
~~~~~~~~~~~~~~~~~~~~~~~~
Another way to build this project is to use the provided CMakePresets.json or passing option using the command line.
The CMakePresets.json file describes some cmake configurations and build phase. It is a way to quickly use a set of
predefined cmake options.
For now, these configuration presets are available:
- ``windows-base``: A Windows configuration, using VS MSVC. Uses ``lv_conf.h`` as the configuration system.
- ``windows-kconfig``: A Windows configuration, using VS MSVC. Uses Kconfig as the configuration system.
- ``linux-base``: A Linux configuration, using Ninja and GCC. Uses ``lv_conf.h`` as the configuration system.
- ``linux-kconfig``: A Linux configuration, using Ninja and GCC. Uses Kconfig as the configuration system.
And these build presets:
- ``windows-base_dbg``: Windows Debug build.
- ``windows-base_rel``: Windows Release build.
- ``linux-base_dbg``: Linux Debug build.
- ``linux-base_rel``: Linux Release build.
Here is how to build using the presets:
.. code-block:: bash
cmake --preset windows-base
cmake --build --preset windows-base_dbg
ctest --preset windows-base_dbg
Build with IDE
~~~~~~~~~~~~~~
The recommended way for consuming CMakePresets is a CMakePresets aware IDE such as
- VS 2022
- VS Code
- CLion
Simply load this project into your IDE and select your desired preset and you are good to go.
Build with CMake GUI
~~~~~~~~~~~~~~~~~~~~
Open this project with CMake GUI and select your desired preset. When hitting the generate button,
CMake will create solution files (for VS) or Ninja Files (for Linux Ninja Build)
The following targets are available.
- lvgl (the actual library, required)
- lvgl_thorvg (an vector graphics extension, optional)
- lvgl_examples (example usages, optional)
- lvgl_demos (some demos, optional)
All optional targets can be disabled by setting the proper cache variables.
If you use cmake to install lvgl, 3 folders will be created.
- include/lvgl (contains all public headers)
- bin (contains all binaries (\*.dll))
- lib (contains all precompiled source files (\*.lib))
.. _integrating_lvgl_cmake:
Integrate LVGL to your project using cmake
------------------------------------------
The LVGL cmake system is made to be integrated into higher level projects. To do so, simply add this to your
project's ``CMakeLists.txt``.
This snippet adds LVGL and needs an ``lv_conf.h`` file present next to the lvgl folder:
.. code-block:: cmake
add_subdirectory(lvgl)
This snippet sets up LVGL and tells it which ``lv_conf.h`` file to use:
.. code-block:: cmake
set(LV_BUILD_CONF_PATH path/to/my_lv_conf.h)
add_subdirectory(lvgl)
This snippet sets up LVGL and points to the folder where ``lv_conf.h`` is located:
.. code-block:: cmake
set(LV_BUILD_CONF_DIR path/to/directory)
add_subdirectory(lvgl)
This snippet adds LVGL and specifies to use Kconfig as the configuration system:
.. code-block:: cmake
set(LV_BUILD_USE_KCONFIG ON)
add_subdirectory(lvgl)
This snippet adds LVGL and specify to use Kconfig as the configuration system and to use a specific defconfig:
.. code-block:: cmake
set(LV_BUILD_USE_KCONFIG ON)
set(LV_BUILD_DEFCONFIG_PATH path/to/my_defconfig)
add_subdirectory(lvgl)
To enable the demos and examples set these options:
.. code-block:: cmake
set(CONFIG_LV_BUILD_EXAMPLES ON)
set(CONFIG_LV_BUILD_DEMOS ON)
add_subdirectory(lvgl)
Below is a list of the available options/variables
.. list-table::
:header-rows: 1
:widths: 30 70
* - Variable/Option
- Type
- Description
* - LV_BUILD_CONF_PATH
- PATH
- Allows to set a custom path for ``lv_conf.h``
* - LV_BUILD_CONF_DIR
- PATH
- Allows to set a directory containing ``lv_conf.h``
* - LV_BUILD_USE_KCONFIG
- BOOLEAN
- When set KConfig is used as the configuration source. This option is disabled by default.
* - LV_BUILD_DEFCONFIG_PATH
- PATH
- Specify to use a .defconfig file instead of the current .config in a Kconfig setup.
* - LV_BUILD_LVGL_H_SYSTEM_INCLUDE
- BOOLEAN
- Enable if LVGL will be installed to the system or your build system uses a sysroot.
Turning this option on implies that the resources generated by the image generation script
will include ``lvgl.h`` as a system include. i.e: ``#include <lvgl.h>``.
This option is disabled by default.
* - LV_BUILD_LVGL_H_SIMPLE_INCLUDE
- BOOLEAN
- When enabled the resources will include ``lvgl.h`` as a simple include, this option
is enabled by default.
* - LV_BUILD_SET_CONFIG_OPTS
- BOOLEAN
- When enabled, this option runs a script that processes the ``lv_conf.h``/Kconfig
configuration using ``pcpp`` to generate corresponding ``CONFIG_LV_*`` and
``CONFIG_LV_BUILD_*`` CMake variables based on the contents of ``lv_conf_internal.h``.
This requires python3 with ``venv`` and ``pip`` or access to a working ``pcpp``.
If KConfig is used, this is enabled automatically.
* - CONFIG_LV_BUILD_DEMOS
- BOOLEAN
- When enabled builds the demos
* - CONFIG_LV_BUILD_EXAMPLES
- BOOLEAN
- When enabled builds the examples
* - CONFIG_LV_USE_THORVG_INTERNAL
- BOOLEAN
- When enabled the in-tree LVGL version of ThorVG is compiled
* - CONFIG_LV_USE_PRIVATE_API
- BOOLEAN
- When enabled the private headers ``*_private.h`` are installed on the system
.. note::
When ``LV_BUILD_SET_CONFIG_OPTS`` or ``LV_BUILD_USE_KCONFIG`` are enabled,
the options/variables beginning with the prefix ``CONFIG_*`` are automatically
set to the values found in ``lv_conf.h``

View File

@@ -0,0 +1,10 @@
=============
Build systems
=============
.. toctree::
:maxdepth: 2
make
cmake

View File

@@ -0,0 +1,7 @@
.. _build_make:
====
make
====
LVGL can be easily integrated into any Makefile project by adding ``include lvgl/lvgl.mk`` to the main ``Makefile``.

View File

@@ -0,0 +1,233 @@
.. _alif:
====
Alif
====
Alif Semiconductor is a next-generation chip vendor making chips that come in a
variety of configurations. Their chips are designed to excel at multiple aspects
at once. They offer combinations of performance, low power consumption,
security, and special functionality like AI. Many of their chips have two asymmetrical cores.
One core is typically high performance while the other is high efficiency.
Alif offers both microcontrollers and microprocessors.
LVGL on Alif Boards
*******************
This is a guide for getting started with LVGL on an Alif board. It specifically details
the all the steps needed to get the LVGL example project
`alif_m55-lvgl <https://github.com/alifsemi/alif_m55-lvgl>`__ running on the
`Alif E7 Devkit Gen2 <https://alifsemi.com/ensemble-e7-series/>`__; however, any project
based on the `Alif VS Code Template <https://github.com/alifsemi/alif_vscode-template>`__
has a nearly identical setup process
so this can be used as a general guide for those. There are other ways to compile for Alif boards
such as with Zephyr RTOS. See `Alif's GitHub repos <https://github.com/orgs/alifsemi/repositories>`__.
This guide is for Linux and Windows.
This project uses D/AVE 2D rendering acceleration with LVGL's D/AVE 2D :ref:`draw unit<draw>`.
Step-by-Step Guide
------------------
Install Visual Studio Code
~~~~~~~~~~~~~~~~~~~~~~~~~~
Install Visual Studio code. There are different ways of installing it depending on your platform.
`See here <https://code.visualstudio.com/docs/setup/setup-overview>`__.
.. note::
The remaining steps can **optionally** be done inside a Docker container. You can connect to the Docker container
as a VS Code remote dev container.
There should be two serial ports created upon connecting your Alif board. On Linux, they will be something
like ``/dev/ttyACM0`` and ``/dev/ttyACM1``. In the ``docker run`` command you use to create the dev
container, include ``--device /dev/ttyACM0 --device /dev/ttyACM1`` to give the container access to those
ports so you can flash from it.
Install the "Dev Containers" VS Code extension. Select your container from the "Remote Explorer" on the left
side panel.
Install Prerequisite tools
~~~~~~~~~~~~~~~~~~~~~~~~~~
Make sure these are installed in your environment. The VS Code extensions rely on these being present.
- ``git``
- ``curl``
- ``unzip``
Install the Alif SE Tools
~~~~~~~~~~~~~~~~~~~~~~~~~
Create an Alif account and download the tools from
`here <https://alifsemi.com/support/software-tools/ensemble/>`__ under "Alif Security Toolkit".
Extract it. The path where it was extracted will be needed later.
On Linux, extracting can be done by running the following
.. code-block:: shell
cd
tar -xf Downloads/APFW0002-app-release-exec-linux-SE_FW_1.101.00_DEV.tar
ls
pwd
Among the results of ``ls`` you should see ``app-release-exec-linux``. That, combined
with the output of ``pwd``, is the path you need to use later. I.e.,
``/home/you/app-release-exec-linux``.
Install J-Link Software (optional)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Download the latest stable version of the `J-Link Software <https://www.segger.com/downloads/jlink>`__.
Its installation path will be needed later.
Clone the ``alif_m55-lvgl`` Project
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: shell
git clone --recursive https://github.com/alifsemi/alif_m55-lvgl
Open ``alif_m55-lvgl`` in VS Code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Open the cloned repo in VS Code. For the VS Code extensions to work properly,
it's recommended to open the folder in VS Code instead of opening a
containing parent directory.
.. code-block:: shell
code alif_m55-lvgl
or navigate to **File \> Open Folder** in VS Code and open ``alif_m55-lvgl``.
If you are prompted to automatically install recommended extensions, click
"install" so you can skip the next step.
Install Required VS Code Extensions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Install the following VS Code extensions from the "Extensions" side panel
- Arm Tools Environment Manager
- Arm CMSIS Solution
- C/C++ Extension Pack
- Cortex-Debug (optional. needed for debugging)
Activate Environment
~~~~~~~~~~~~~~~~~~~~
If it hasn't happened automatically, Click "Arm Tools" on the bottom bar and then
click "Activate Environment" in the list that appears. It will install CMake,
ninja-build, a GCC ARM compiler, and cmsis-toolbox.
If you only see "Reactivate Environment" then it is likely already active.
Set the Paths of Installed Tools
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Press ctrl + shift + p. Type "preferences" and select the option
"Preferences: Open User Settings (JSON)" from the choices.
A ``settings.json`` will open. Note: if using a Docker container,
it's better to edit the JSON file in the project directory at
``.vscode/settings.json``.
You need to add some entries (at least ``"alif.setools.root"``)
to the JSON you see.
If your ``settings.json`` looks like this initially...
.. code-block:: json
{
"workbench.colorTheme": "Default Dark+",
"editor.renderWhitespace": "all",
}
... then it should look like this afterwards:
.. code-block:: json
{
"workbench.colorTheme": "Default Dark+",
"editor.renderWhitespace": "all",
"alif.setools.root" : "C:/alif-se-tools/app-release-exec",
"cortex-debug.JLinkGDBServerPath": "C:/Program Files/SEGGER/JLink/JLinkGDBServerCL.exe"
}
The above uses Windows paths as an example. A Linux path to the Alif SE Tools may look
something like ``"/home/you/app-release-exec-linux"``.
Configure the Board Variant
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Open the
`board.h file <https://github.com/alifsemi/alif_vscode-template/blob/ce5423dbd15f62cb0aa4462533a960d79a014f97/board/board.h#L23-L30>`__.
Identify your board variant in the list and set ``BOARD_ALIF_DEVKIT_VARIANT`` to the correct value.
You may also need to set ``BOARD_ILI9806E_PANEL_VARIANT`` if the default does not match yours.
Set Up the Build Context, Compile, and Flash
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Get to the "Manage Solution" view from the CMSIS Solution extension. You can reach
it by either clicking the gear icon on the bottom bar or by navigating to the CMSIS panel
on the left and clicking the gear at the top of that view. When it's open, it's a
graphical editor tab called "Manage Solution" with a gear icon.
**Important**
Under "Run and Debug" \> "Run Configuration" (column) \> "alif" (row), click the dropdown and select
"First time pack installation". Click the play icon at the top of the CMSIS left side panel.
In your terminal you should see CMSIS packs being installed. Wait for it to complete.
Now you can click the hammer icon next to the play icon to compile the project.
A few hundred files will be compiled. Wait for it to complete.
Open the dropdown from before and choose "Prepare program with Security Toolkit". Click the
play icon. It prepares some files as a prior step to flashing.
Open the dropdown again and choose "Program with Security Toolkit". Click the play icon.
If this is the first time, you will be prompted to choose which serial port to use to flash
the board. You can try ``/dev/ttyACM0``. If it was the wrong one, it will fail and you will
need to open the dropdown and choose "Program with Security Toolkit (select COM port)"
to override the previous one which was saved as default.
The LVGL benchmark should run on your Alif board after flashing completes.
More Info
---------
If there were any difficulties faced while following this guide, refer to these
Alif sources for more detailed steps.
- `Getting Started with VSCode CMSIS pack project <https://github.com/alifsemi/alif_vscode-template/blob/main/doc/getting_started.md>`__
- `VSCode Getting Started Template <https://github.com/alifsemi/alif_vscode-template/blob/main/README.md>`__
You can download the "Alif Security Toolkit Quick Start Guide" from https://alifsemi.com/support/software-tools/ensemble/ ,
assuming you have created an account, to learn how to use the Alif SE Tools to perform
low-level manipulations on your board.
HP and HE Cores and Optimized Build
-----------------------------------
In the "Manage Solution" view explained in the guide, there is an option to select either
an HP target or an HE target. What these are referring to are the two distinct cores
present in the Alif E7. "HE" stands for "High Efficiency" while "HP" stands for
"High Performance". To get the best performance out of an LVGL application, select HP.
Consider HE when power usage is a concern. The merit of having asymmetrical cores
is that your application can run theoretically run low-priority workloads efficiently on
the HE core and delegate time critical, processing intensive workloads to the HP core.
There is also an option to choose a "Build Type". For best performance, choose "release".
If debugging you will want "debug".
To maximize the score on the LVGL benchmark and maximize the performance of an LVGL
application in general, ensure the HP core is selected and the build type is release.

View File

@@ -0,0 +1,57 @@
.. _arm:
===
Arm
===
Arm is a leading semiconductor and software design company, renowned for creating the Cortex-M microcontroller (MCU) cores and Cortex-A/R (MPU) processor cores, which are integral to a wide range of devices. These cores are at the heart of many embedded systems, powering chips from industry giants such as STMicroelectronics, NXP, and Renesas. Arm's energy-efficient designs are used in billions of devices worldwide, from microcontrollers to smartphones and servers. By licensing their processor designs, Arm enables a broad ecosystem of partners to develop customized solutions optimized for performance, power, and size. Arm's architecture is highly compatible with various operating systems and software libraries, including LVGL, making it a versatile choice for developers creating efficient, high-performance graphical user interfaces.
Compile LVGL for Arm
--------------------
No specific action is required. Any compiler that supports the target Arm architecture can be used to compile LVGL's source code, including GCC, LLVM, and AC6.
It is also possible to cross-compile LVGL for an MPU (instead of compiling it on the target hardware) or create a shared library. For more information, check out :ref:`build_cmake`.
Getting Started with AC6
~~~~~~~~~~~~~~~~~~~~~~~~
Since AC6 is a proprietary toolchain, it contains many specific optimizations, so you can expect the best performance when using it.
AC6 is not free, but it offers a community license that can be activated as follows:
1. Download and install the AC6 compiler from `Arm's website <https://developer.arm.com/Tools%20and%20Software/Arm%20Compiler%20for%20Embedded>`__.
2. To register a community license, go to the ``bin`` folder of the compiler and, in a terminal, run ``armlm.exe activate -server https://mdk-preview.keil.arm.com -product KEMDK-COM0`` (On Linux, use ``./armlm``).
IDE Support
-----------
There are no limitations on the supported IDEs. LVGL works in various vendors' IDEs, including Arm's Keil MDK, IAR, Renesas's e2 studio, NXP's MCUXpresso, ST's CubeIDE, as well as custom make or CMake projects.
Arm2D and the Helium instruction set
------------------------------------
Arm Cortex-M55 and Cortex-M85 have the `SIMD Helium <https://www.arm.com/technologies/helium>`__ instruction set.
Among many others, this can effectively speed up UI rendering. :ref:`Arm2D <arm2d>` is a library maintained by Arm that leverages the Helium instruction set.
Note that GCC has some known issues with Helium intrinsics. It is recommended to use AC6 or LLVM when dealing with Helium code.
To add Arm2D to your project, follow these steps:
1. To utilize its power, ensure that ``mcpu`` is set to ``cortex-m85``, ``cortex-m55``, or ``cortex-m52`` and add the ``-fvectorize`` flag. To test without SIMD, use e.g. ``cortex-m85+nomve``.
2. Arm2D can be downloaded from `https://github.com/ARM-software/Arm-2D <https://github.com/ARM-software/Arm-2D>`__. Consider using the ``developing`` branch, which contains the latest updates.
3. Add ``Arm-2D/Library/Include`` to the include paths.
4. Copy ``Arm-2D/Library/Include/template/arm_2d_cfg.h`` to any location you prefer to provide the default configuration for Arm2D. Ensure that the folder containing ``arm_2d_cfg.h`` is added to the include path.
5. The Arm2D repository contains several examples and templates; however, ensure that only ``Arm-2D/Library/Source`` is compiled.
6. The CMSIS DSP library also needs to be added to the project. You can use CMSIS-PACKS or add it manually.
7. For better performance, enable ``LTO`` (Link Time Optimization) and use ``-Omax`` or ``-Ofast``.
8. Arm2D tries to read/write multiple data with a single instruction. Therefore, it's important to use the fastest memory (e.g., ``BSS`` or ``TCM``) for LVGL's buffer to avoid memory bandwidth bottlenecks.
9. Enable ``LV_USE_DRAW_ARM2D_SYNC 1`` and ``LV_USE_DRAW_SW_ASM LV_DRAW_SW_ASM_HELIUM`` in ``lv_conf.h``.
Neon Acceleration
-----------------
Several Cortex-A microprocessors support the `Neon SIMD <https://www.arm.com/technologies/neon>`__ instruction set. LVGL has built-in support to improve the performance of software rendering by utilizing Neon instructions. To enable Neon acceleration, set ``LV_USE_DRAW_SW_ASM`` to ``LV_DRAW_SW_ASM_NEON`` in ``lv_conf.h``.

View File

@@ -0,0 +1,198 @@
=============================
Espressif (ESP32 Chip Series)
=============================
LVGL can be used and configured as standard `ESP-IDF <https://github.com/espressif/esp-idf>`__ component.
If you are new to ESP-IDF, follow the instructions in the `ESP-IDF Programming guide <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/index.html>`__ to install and set up ESP-IDF on your machine.
LVGL Demo Projects for ESP32
----------------------------
For a quick start with LVGL and ESP32, the following pre-configured demo projects are available for specific development boards:
- `ESP-BOX-3 <https://github.com/lvgl/lv_port_espressif_esp-box-3>`__
- `ESP32-S3-LCD-EV-BOARD <https://github.com/lvgl/lv_port_espressif_esp32-s3-lcd-ev-board>`__
- `M5Stack-CoreS3 <https://github.com/lvgl/lv_port_espressif_M5Stack_CoreS3>`__
Refer to the README.md files in these repositories for build and flash instructions.
These demo projects use Espressif's Board Support Packages (BSPs). Additional BSPs and examples are available in the `esp-bsp <https://github.com/espressif/esp-bsp>`__ repository.
Using LVGL in Your ESP-IDF Project
----------------------------------
The simplest way to integrate LVGL into your ESP-IDF project is via the `esp_lvgl_port <https://components.espressif.com/components/espressif/esp_lvgl_port>`__ component. This component, used in the demo projects mentioned above, provides helper functions for easy installation of LVGL and display drivers. Moreover, it can add support for touch, rotary encoders, button or USB HID inputs. It simplifies power savings, screen rotation and other platform specific nuances.
The esp_lvgl_port supports LVGL versions 8 and 9 and is compatible with ESP-IDF v4.4 and above. To add it to your project, use the following command:
.. code:: sh
idf.py add-dependency "espressif/esp_lvgl_port^2.3.0"
By default, esp_lvgl_port depends on the latest stable version of LVGL, so no additional steps are needed for new projects. If a specific LVGL version is required, specify this in your project to avoid automatic updates. LVGL can also be used without esp_lvgl_port, as described below.
Obtaining LVGL
~~~~~~~~~~~~~~
LVGL is distributed through `ESP Registry <https://components.espressif.com/>`__, where all LVGL releases are uploaded.
In case you do not want to use esp_lvgl_port, you can add `LVGL component <https://components.espressif.com/component/lvgl/lvgl>`__ into your project with following command:
.. code-block:: sh
idf.py add-dependency "lvgl/lvgl^9.*"
Adjust the ``^9.*`` part to match your LVGL version requirement. More information on version specifications can be found in the `IDF Component Manager documentation <https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/versioning.html#range-specifications>`__. During the next build, the LVGL component will be fetched from the component registry and added to the project.
**Advanced usage: Use LVGL as local component**
For LVGL development and testing, it may be useful to use LVGL as a local component instead of from the ESP Registry, which offers only released versions and does not allow local modifications. To do this, clone LVGL to your project with the following command:
.. code-block:: sh
git submodule add https://github.com/lvgl/lvgl.git components/lvgl
.. note::
All components from ``${project_dir}/components`` are automatically added to build.
Configuration
~~~~~~~~~~~~~
To configure LVGL, launch the configuration menu with ``idf.py menuconfig`` in your project root directory. Navigate to ``Component config`` and then ``LVGL configuration``.
Support for Display and Touch Drivers
-------------------------------------
For successful LVGL project you will need a display driver and optionally a touch driver. Espressif provides these drivers that are built on its `esp_lcd <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/lcd/index.html>`__ component.
- esp_lcd natively supports for some `basic displays <https://github.com/espressif/esp-idf/tree/master/components/esp_lcd/src>`__
- Other displays are maintained in `esp-bsp repository <https://github.com/espressif/esp-bsp/tree/master/components/lcd>`__ and are uploaded to ESP Registry
- Touch drivers are maintained in `esp-bsp repository <https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch>`__ and are uploaded to ESP Registry
These components share a common public API, making it easy to migrate your projects across different display and touch drivers.
To add a display or touch driver to your project, use a command like:
.. code-block:: sh
idf.py add-dependency "espressif/esp_lcd_gc9a01^2.0.0"
Using the File System under ESP-IDF
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ESP-IDF uses the standard C file operation functions (``fopen``, ``fread``) in all its storage related APIs.
This allows seamless interoperability with LVGL when enabling the :c:macro:`LV_USE_FS_STDIO` configuration.
The process is described in details below, using ``SPIFFS`` as demonstration.
- **Decide what storage system you want to use**
ESP-IDF has many, ready-to-use examples like
`SPIFFS <https://github.com/espressif/esp-idf/tree/master/examples/storage/spiffsgen>`__
,
`SD Card <https://github.com/espressif/esp-idf/tree/master/examples/storage/sd_card/sdspi>`__
and
`LittleFS <https://github.com/espressif/esp-idf/tree/master/examples/storage/littlefs>`__
.
- **Re-configure your own project**
The example project should be examined for details, but in general the changes involve:
- Enabling LVGL's STDIO file system in the configuration
You can use ``menuconfig``:
- ``Component config → LVGL configuration → 3rd Party Libraries``: enable ``File system on top of stdio API``
- Then select ``Set an upper cased letter on which the drive will accessible`` and set it to ``65`` (ASCII **A**)
- You can also set ``Default driver letter`` to 65 to skip the prefix in file paths.
- Modifying the partition table
The exact configuration depends on your flash size and existing partitions,
but the new final result should look something like this:
.. csv-table:: Partition Table
nvs, data, nvs, 0x9000, 0x6000
phy_init, data, phy, 0xf000, 0x1000
factory, app, factory, 0x10000, 1400k
storage, data, spiffs, , 400k
.. note::
If you are not using a custom ``partition.csv`` yet, it can be added
via ``menuconfig`` (``Partition Table → Partition Table → Custom partition table CSV``).
- Apply changes to the build system
Some ESP file systems provide automatic generation from a host folder using CMake. The proper line(s) must be copied to ``main/CMakeLists.txt``
.. note::
``LittleFS`` has extra dependencies that should be added to ``main/idf_component.yml``
- **Prepare the image files**
LVGL's ``LVGLImage.py`` Python tool can be used to convert images to binary pixel map files.
It supports various formats and compression.
Meanwhile 3rd party libraries
(like :ref:`LodePNG<lodepng_rst>` and :ref:`Tiny JPEG<tjpgd>`)
allow using image files without conversion.
After preparing the files, they should be moved to the target device:
- If properly activated a **SPIFFS** file system based on the ``spiffs_image`` folder should be automatically generated and later flashed to the target
- Similar mechanism for **LittleFS** uses the ``flash_data`` folder, but it's only available for Linux hosts
- For the **SD Card**, a traditional file browser can be used
- **Invoke proper API calls in the application code**
The core functionality requires only a few lines. The following example draws the image as well.
.. code:: c
#include "esp_spiffs.h"
void lv_example_image_from_esp_fs(void) {
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = NULL,
.max_files = 5,
.format_if_mount_failed = false
};
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to register SPIFF filesystem");
return;
}
lv_obj_t * obj = lv_image_create(lv_screen_active());
lv_image_set_src(widget, "A:/spiffs/logo.bin");
lv_obj_center(widget);
}
- **Build and flash**
After calling ``idf.py build flash`` the picture should be displayed on the screen.
.. note::
Changes made by ``menuconfig`` are not being tracked in the repository if the ``sdkconfig`` file is added to ``.gitignore``, which is the default for many ESP-IDF projects.
To make your configuration permanent, add the following lines to ``sdkconfig.defaults``:
.. code:: c
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_LV_USE_FS_STDIO=y
CONFIG_LV_FS_STDIO_LETTER=65
CONFIG_LV_FS_DEFAULT_DRIVER_LETTER=65

View File

@@ -0,0 +1,13 @@
============
Chip vendors
============
.. toctree::
:maxdepth: 2
alif
arm
espressif
nxp
renesas/index
stm32

View File

@@ -0,0 +1,536 @@
===
NXP
===
NXP has integrated LVGL into the MCUXpresso SDK packages for several of our
microcontrollers as an optional software component, allowing easy evaluation and
migration into your product design. LVGL is a free and open-source embedded
graphic library with features that enable you to create embedded GUIs with
intuitive graphical elements, beautiful visual effects and a low memory
footprint. The complete graphic framework includes a variety of widgets for you
to use in the creation of your GUI, and supports more advanced functions such as
animations and anti-aliasing.
LVGL enables graphics in our free GUI Guider UI tool. It's available for use
with NXP's general purpose and crossover microcontrollers, providing developers
with a tool for creating complete, high quality GUI applications with LVGL.
Creating new project with LVGL
------------------------------
`Download an SDK for a supported board <https://www.nxp.com/design/software/embedded-software/littlevgl-open-source-graphics-library:LITTLEVGL-OPEN-SOURCE-GRAPHICS-LIBRARY?&tid=vanLITTLEVGL-OPEN-SOURCE-GRAPHICS-LIBRARY>`__
today and get started with your next GUI application. It comes fully configured
with LVGL (and with PXP/VGLite/G2D support if the modules are present), no
additional integration work is required.
HW acceleration for NXP iMX RT platforms
----------------------------------------
Depending on the RT platform used, the acceleration can be done by NXP PXP
(PiXel Pipeline) and/or the Verisilicon GPU through an API named VGLite. Each
accelerator has its own context that allows them to be used individually as well
simultaneously (in LVGL multithreading mode).
HW acceleration for NXP iMX platforms
----------------------------------------
On MPU platforms, the acceleration can be done (hardware independent) by NXP G2D
library. This accelerator has its own context that allows them to be used
individually as well simultaneously with the CPU (in LVGL multithreading mode).
PXP accelerator
~~~~~~~~~~~~~~~
Basic configuration:
^^^^^^^^^^^^^^^^^^^^
- Select NXP PXP engine in "lv_conf.h": Set :c:macro:`LV_USE_PXP` to `1`.
- In order to use PXP as a draw unit, select in "lv_conf.h": Set :c:macro:`LV_USE_DRAW_PXP` to `1`.
- In order to use PXP to rotate the screen, select in "lv_conf.h": Set :c:macro:`LV_USE_ROTATE_PXP` to `1`.
- Enable PXP asserts in "lv_conf.h": Set :c:macro: `LV_USE_PXP_ASSERT` to `1`.
There are few PXP assertions that can stop the program execution in case the
c:macro:`LV_ASSERT_HANDLER` is set to `while(1);` (Halt by default). Else,
there will be logged just an error message via `LV_LOG_ERROR`.
- If :c:macro:`SDK_OS_FREE_RTOS` symbol is defined, FreeRTOS implementation
will be used, otherwise bare metal code will be included.
Basic initialization:
^^^^^^^^^^^^^^^^^^^^^
PXP draw initialization is done automatically in :cpp:func:`lv_init()` once the
PXP is enabled as a draw unit or to rotate the screen, no user code is required:
.. code-block:: c
#if LV_USE_DRAW_PXP || LV_USE_ROTATE_PXP
lv_draw_pxp_init();
#endif
During PXP initialization, a new draw unit `lv_draw_pxp_unit_t` will be created
with the additional callbacks, if :c:macro:`LV_USE_DRAW_PXP` is set to `1`:
.. code-block:: c
lv_draw_pxp_unit_t * draw_pxp_unit = lv_draw_create_unit(sizeof(lv_draw_pxp_unit_t));
draw_pxp_unit->base_unit.evaluate_cb = _pxp_evaluate;
draw_pxp_unit->base_unit.dispatch_cb = _pxp_dispatch;
draw_pxp_unit->base_unit.delete_cb = _pxp_delete;
and an addition thread `_pxp_render_thread_cb()` will be spawned in order to
handle the supported draw tasks.
.. code-block:: c
#if LV_USE_PXP_DRAW_THREAD
lv_thread_init(&draw_pxp_unit->thread, "pxpdraw", LV_THREAD_PRIO_HIGH, _pxp_render_thread_cb, 2 * 1024, draw_pxp_unit);
#endif
If `LV_USE_PXP_DRAW_THREAD` is not defined, then no additional draw thread will be created
and the PXP drawing task will get executed on the same LVGL main thread.
`_pxp_evaluate()` will get called after each task is being created and will
analyze if the task is supported by PXP or not. If it is supported, then an
preferred score and the draw unit id will be set to the task. An `score` equal
to `100` is the default CPU score. Smaller score means that PXP is capable of
drawing it faster.
`_pxp_dispatch()` is the PXP dispatcher callback, it will take a ready to draw
task (having the `DRAW_UNIT_ID_PXP` set) and will pass the task to the PXP draw
unit for processing.
`_pxp_delete()` will cleanup the PXP draw unit.
Features supported:
^^^^^^^^^^^^^^^^^^^
Several drawing features in LVGL can be offloaded to the PXP engine. The CPU is
available for other operations while the PXP is running. A RTOS is required to
block the LVGL drawing thread and switch to another task or suspend the CPU for
power savings.
Supported draw tasks are available in "src/draw/nxp/pxp/lv_draw_pxp.c":
.. code-block:: c
switch(t->type) {
case LV_DRAW_TASK_TYPE_FILL:
lv_draw_pxp_fill(t, t->draw_dsc, &t->area);
break;
case LV_DRAW_TASK_TYPE_IMAGE:
lv_draw_pxp_img(t, t->draw_dsc, &t->area);
break;
case LV_DRAW_TASK_TYPE_LAYER:
lv_draw_pxp_layer(t, t->draw_dsc, &t->area);
break;
default:
break;
}
Additionally, the screen rotation can be handled by the PXP:
.. code-block::c
void lv_draw_pxp_rotate(const void * src_buf, void * dest_buf, int32_t src_width, int32_t src_height,
int32_t src_stride, int32_t dest_stride, lv_display_rotation_t rotation,
lv_color_format_t cf);
- Fill area with color (w/o radius, w/o gradient) + optional opacity.
- Blit source image RGB565/ARGB888/XRGB8888 over destination.
RGB565/RGB888/ARGB888/XRGB8888 + optional opacity.
- Recolor source image RGB565.
- Scale and rotate (90, 180, 270 degree) source image RGB565.
- Blending layers (w/ same supported formats as blitting).
- Rotate screen (90, 180, 270 degree).
Known limitations:
^^^^^^^^^^^^^^^^^^
- PXP can only rotate the frames in angles that are multiple of 90 degrees.
- Rotation is not supported for images unaligned to blocks of 16x16 pixels. PXP
is set to process 16x16 blocks to optimize the system for memory bandwidth and
image processing time. The output engine essentially truncates any output
pixels after the desired number of pixels has been written. When rotating a
source image and the output is not divisible by the block size, the incorrect
pixels could be truncated and the final output image can look shifted.
- Recolor or transformation for images w/ opacity or alpha channel can't be
obtained in a single PXP pipeline configuration. Two or multiple steps would
be required.
- Buffer address must be aligned to 64 bytes: set :c:macro:`LV_DRAW_BUF_ALIGN`
to `64` in "lv_conf.h".
No stride alignment is required: set :c:macro:`LV_DRAW_BUF_STRIDE_ALIGN` to
`1` in "lv_conf.h".
Project setup:
^^^^^^^^^^^^^^
- Add PXP related source files (and corresponding headers if available) to
project:
- "src/draw/nxp/pxp/lv_draw_buf_pxp.c": draw buffer callbacks
- "src/draw/nxp/pxp/lv_draw_pxp_fill.c": fill area
- "src/draw/nxp/pxp/lv_draw_pxp_img.c": blit image (w/ optional recolor or
transformation)
- "src/draw/nxp/pxp/lv_draw_pxp_layer.c": layer blending
- "src/draw/nxp/pxp/lv_draw_pxp.c": draw unit initialization
- "src/draw/nxp/pxp/lv_pxp_cfg.c": init, deinit, run/wait PXP device
- "src/draw/nxp/pxp/lv_pxp_osa.c": OS abstraction (FreeRTOS or bare metal)
- "src/draw/nxp/pxp/lv_pxp_utils.c": function helpers
- PXP related code depends on two drivers provided by MCU SDK. These drivers
need to be added to project:
- fsl_pxp.c: PXP driver
- fsl_cache.c: CPU cache handling functions
PXP default configuration:
^^^^^^^^^^^^^^^^^^^^^^^^^^
- Implementation depends on multiple OS-specific functions. The struct
:cpp:struct:`pxp_cfg_t` with callback pointers is used as a parameter for the
:cpp:func:`lv_pxp_init()` function. Default implementation for
FreeRTOS in lv_pxp_osa.c.
- :cpp:func:`pxp_interrupt_init()`: Initialize PXP interrupt (HW setup,
OS setup)
- :cpp:func:`pxp_interrupt_deinit()`: Deinitialize PXP interrupt (HW setup,
OS setup)
- :cpp:func:`pxp_run()`: Start PXP job. Use OS-specific mechanism to block
drawing thread.
- :cpp:func:`pxp_wait()`: Wait for PXP completion.
VGLite accelerator
~~~~~~~~~~~~~~~~~~
Extra drawing features in LVGL can be handled by the VGLite engine. The
CPU is available for other operations while the VGLite is running. A
RTOS is required to block the LVGL drawing thread and switch to another
task or suspend the CPU for power savings.
Basic configuration:
^^^^^^^^^^^^^^^^^^^^
- Select NXP VGLite engine in "lv_conf.h": Set :c:macro:`LV_USE_DRAW_VGLITE` to
`1`. :c:macro:`SDK_OS_FREE_RTOS` symbol needs to be defined so that FreeRTOS
driver osal implementation will be enabled.
- Enable VGLite asserts in "lv_conf.h": Set :c:macro: `LV_USE_VGLITE_ASSERT` to
`1`.
VGLite assertions will verify the driver API status code and in any error, it
can stop the program execution in case the c:macro: `LV_ASSERT_HANDLER` is set
to `while(1);` (Halt by default). Else, there will be logged just an error
message via `LV_LOG_ERROR`.
Basic initialization:
^^^^^^^^^^^^^^^^^^^^^
Initialize VGLite GPU before calling :cpp:func:`lv_init()` by specifying the
width/height of tessellation window. The default values for tessellation width
and height, and command buffer size are in the SDK file "vglite_support.h".
.. code-block:: c
#if LV_USE_DRAW_VGLITE
#include "vg_lite.h"
#include "vglite_support.h"
#endif
...
#if LV_USE_DRAW_VGLITE
if(vg_lite_init(DEFAULT_VG_LITE_TW_WIDTH, DEFAULT_VG_LITE_TW_HEIGHT) != VG_LITE_SUCCESS)
{
PRINTF("VGLite init error. STOP.");
vg_lite_close();
while (1)
;
}
if (vg_lite_set_command_buffer_size(VG_LITE_COMMAND_BUFFER_SIZE) != VG_LITE_SUCCESS)
{
PRINTF("VGLite set command buffer. STOP.");
vg_lite_close();
while (1)
;
}
#endif
VGLite draw initialization is done automatically in :cpp:func:`lv_init()` once
the VGLite is enabled, no user code is required:
.. code-block:: c
#if LV_USE_DRAW_VGLITE
lv_draw_vglite_init();
#endif
During VGLite initialization, a new draw unit `lv_draw_vglite_unit_t` will be
created with the additional callbacks:
.. code-block:: c
lv_draw_vglite_unit_t * draw_vglite_unit = lv_draw_create_unit(sizeof(lv_draw_vglite_unit_t));
draw_vglite_unit->base_unit.evaluate_cb = _vglite_evaluate;
draw_vglite_unit->base_unit.dispatch_cb = _vglite_dispatch;
draw_vglite_unit->base_unit.delete_cb = _vglite_delete;
and an addition thread `_vglite_render_thread_cb()` will be spawned in order to
handle the supported draw tasks.
.. code-block:: c
#if LV_USE_VGLITE_DRAW_THREAD
lv_thread_init(&draw_vglite_unit->thread, "vglitedraw", LV_THREAD_PRIO_HIGH, _vglite_render_thread_cb, 2 * 1024, draw_vglite_unit);
#endif
If `LV_USE_VGLITE_DRAW_THREAD` is not defined, then no additional draw thread will be created
and the VGLite drawing task will get executed on the same LVGL main thread.
`_vglite_evaluate()` will get called after each task is being created and will
analyze if the task is supported by VGLite or not. If it is supported, then an
preferred score and the draw unit id will be set to the task. An `score` equal
to `100` is the default CPU score. Smaller score means that VGLite is capable of
drawing it faster.
`_vglite_dispatch()` is the VGLite dispatcher callback, it will take a ready to
draw task (having the `DRAW_UNIT_ID_VGLITE` set) and will pass the task to the
VGLite draw unit for processing.
`_vglite_delete()` will cleanup the VGLite draw unit.
Advanced configuration:
^^^^^^^^^^^^^^^^^^^^^^^
- Enable VGLite blit split in "lv_conf.h":
Set :c:macro: `LV_USE_VGLITE_BLIT_SPLIT` to `1`.
Enabling the blit split workaround will mitigate any quality degradation issue
on screen's dimension > 352 pixels.
.. code-block:: c
#define VGLITE_BLIT_SPLIT_THR 352
- By default, the blit split threshold is set to 352. Blits with width or height
higher than this value will be done in multiple steps. Value must be multiple
of stride alignment in px. For most color formats, the alignment is 16px
(except the index formats). Transformation will not be supported once with
the blit split.
- Enable VGLite draw task synchronously in "lv_conf.h":
Set :c:macro: `LV_USE_VGLITE_DRAW_ASYNC` to `1`.
Multiple draw tasks can be queued and flushed them once to the GPU based on
the GPU idle status. If GPU is busy, the task will be queued, and the VGLite
dispatcher will ask for a new available task. If GPU is idle, the queue with
any pending tasks will be flushed to the GPU. The completion status of draw
task will be sent to the main LVGL thread asynchronously.
Features supported:
^^^^^^^^^^^^^^^^^^^
Several drawing features in LVGL can be offloaded to the VGLite engine. The CPU
is available for other operations while the GPU is running. RTOS is required to
block the LVGL drawing thread and switch to another task or suspend the CPU for
power savings.
Supported draw tasks are available in "src/draw/nxp/vglite/lv_draw_vglite.c":
.. code-block:: c
switch(t->type) {
case LV_DRAW_TASK_TYPE_LABEL:
lv_draw_vglite_label(t, t->draw_dsc, &t->area);
break;
case LV_DRAW_TASK_TYPE_FILL:
lv_draw_vglite_fill(t, t->draw_dsc, &t->area);
break;
case LV_DRAW_TASK_TYPE_BORDER:
lv_draw_vglite_border(t, t->draw_dsc, &t->area);
break;
case LV_DRAW_TASK_TYPE_IMAGE:
lv_draw_vglite_img(t, t->draw_dsc, &t->area);
break;
case LV_DRAW_TASK_TYPE_ARC:
lv_draw_vglite_arc(t, t->draw_dsc, &t->area);
break;
case LV_DRAW_TASK_TYPE_LINE:
lv_draw_vglite_line(t, t->draw_dsc);
break;
case LV_DRAW_TASK_TYPE_LAYER:
lv_draw_vglite_layer(t, t->draw_dsc, &t->area);
break;
case LV_DRAW_TASK_TYPE_TRIANGLE:
lv_draw_vglite_triangle(t, t->draw_dsc);
break;
default:
break;
}
All the below operation can be done in addition with optional opacity.
- Fill area with color (w/ radius or gradient).
- Blit source image (any format from ``_vglite_src_cf_supported()``) over
destination (any format from ``_vglite_dest_cf_supported()``).
- Recolor source image.
- Scale and rotate (any decimal degree) source image.
- Blending layers (w/ same supported formats as blitting).
- Draw letters (blit bitmap letters / raster font).
- Draw full borders (LV_BORDER_SIDE_FULL).
- Draw arcs (w/ rounded edges).
- Draw lines (w/ dash or rounded edges).
- Draw triangles with color (w/ gradient).
Known limitations:
^^^^^^^^^^^^^^^^^^
- Source image alignment: The byte alignment requirement for a pixel depends on
the specific pixel format. Both buffer address and buffer stride must be
aligned. As general rule, the alignment is set to 16 pixels. This makes the
buffer address alignment to be 32 bytes for RGB565 and 64 bytes for ARGB8888.
- For pixel engine (PE) destination, the alignment should be 64 bytes for all
tiled (4x4) buffer layouts. The pixel engine has no additional alignment
requirement for linear buffer layouts (:c:macro:`VG_LITE_LINEAR`).
Project setup:
^^^^^^^^^^^^^^
- Add VGLite related source files (and corresponding headers if available) to
project:
- "src/draw/nxp/vglite/lv_draw_buf_vglite.c": draw buffer callbacks
- "src/draw/nxp/vglite/lv_draw_vglite_arc.c": draw arc
- "src/draw/nxp/vglite/lv_draw_vglite_border.c": draw border
- "src/draw/nxp/vglite/lv_draw_vglite_fill.c": fill area
- "src/draw/nxp/vglite/lv_draw_vglite_img.c": blit image (w/ optional recolor or transformation)
- "src/draw/nxp/vglite/lv_draw_vglite_label.c": draw label
- "src/draw/nxp/vglite/lv_draw_vglite_layer.c": layer blending
- "src/draw/nxp/vglite/lv_draw_vglite_line.c": draw line
- "src/draw/nxp/vglite/lv_draw_vglite_triangle.c": draw triangle
- "src/draw/nxp/vglite/lv_draw_vglite.c": draw unit initialization
- "src/draw/nxp/vglite/lv_vglite_buf.c": init/get vglite buffer
- "src/draw/nxp/vglite/lv_vglite_matrix.c": set vglite matrix
- "src/draw/nxp/vglite/lv_vglite_path.c": create vglite path data
- "src/draw/nxp/vglite/lv_vglite_utils.c": function helpers
G2D accelerator
~~~~~~~~~~~~~~~
Basic configuration:
^^^^^^^^^^^^^^^^^^^^
- Select NXP G2D engine in "lv_conf.h": Set :c:macro:`LV_USE_G2D` to `1`.
- In order to use G2D as a draw unit, select in "lv_conf.h": Set :c:macro:`LV_USE_DRAW_G2D` to `1`.
- Enable G2D asserts in "lv_conf.h": Set :c:macro: `LV_USE_G2D_ASSERT` to `1`.
There are few G2D assertions that can stop the program execution in case the
c:macro: `LV_ASSERT_HANDLER` is set to `while(1);` (Halt by default). Else,
there will be logged just an error message via `LV_LOG_ERROR`.
Basic initialization:
^^^^^^^^^^^^^^^^^^^^^
G2D draw initialization is done automatically in :cpp:func:`lv_init()` once the
G2D is enabled as a draw unit , no user code is required:
.. code:: c
#if LV_USE_DRAW_G2D
lv_draw_g2d_init();
#endif
During G2D initialization, a new draw unit `lv_draw_g2d_unit_t` will be created
with the additional callbacks, if :c:macro:`LV_USE_DRAW_G2D` is set to `1`:
.. code:: c
lv_draw_g2d_unit_t * draw_g2d_unit = lv_draw_create_unit(sizeof(lv_draw_g2d_unit_t));
draw_g2d_unit->base_unit.evaluate_cb = _g2d_evaluate;
draw_g2d_unit->base_unit.dispatch_cb = _g2d_dispatch;
draw_g2d_unit->base_unit.delete_cb = _g2d_delete;
and an addition thread `_g2d_render_thread_cb()` will be spawned in order to
handle the supported draw tasks.
.. code:: c
#if LV_USE_G2D_DRAW_THREAD
lv_thread_init(&draw_g2d_unit->thread, LV_THREAD_PRIO_HIGH, _g2d_render_thread_cb, 2 * 1024, draw_g2d_unit);
#endif
If `LV_USE_G2D_DRAW_THREAD` is not defined, then no additional draw thread will be created
and the G2D drawing task will get executed on the same LVGL main thread.
`_g2d_evaluate()` will get called after each task is being created and will
analyze if the task is supported by G2D or not. If it is supported, then an
preferred score and the draw unit id will be set to the task. An `score` equal
to `100` is the default CPU score. Smaller score means that G2D is capable of
drawing it faster.
`_g2d_dispatch()` is the G2D dispatcher callback, it will take a ready to draw
task (having the `DRAW_UNIT_ID_G2D` set) and will pass the task to the G2D draw
unit for processing.
`_g2d_delete()` will cleanup the G2D draw unit.
Features supported:
^^^^^^^^^^^^^^^^^^^
Several drawing features in LVGL can be offloaded to the G2D engine. The CPU is
available for other operations while the G2D is running. Linux OS is required to
block the LVGL drawing thread and switch to another task or suspend the CPU for
power savings.
Supported draw tasks are available in "src/draw/nx/g2d/lv_draw_g2d.c":
.. code:: c
switch(t->type) {
case LV_DRAW_TASK_TYPE_FILL:
lv_draw_g2d_fill(u, t->draw_dsc, &t->area);
break;
case LV_DRAW_TASK_TYPE_IMAGE:
lv_draw_g2d_img(u, t->draw_dsc, &t->area);
break;
default:
break;
}
- Fill area with color (w/o radius, w/o gradient) + optional opacity.
- Blit source image ARGB8888 over destination.
ARGB8888 + optional opacity.
- Scale source image ARGB8888.
Known limitations:
^^^^^^^^^^^^^^^^^^
- G2D/PXP can only rotate at 90x angles.
- Rotation is not supported for images unaligned to blocks of 16x16 pixels. G2D/PXP
is set to process 16x16 blocks to optimize the system for memory bandwidth and
image processing time. The output engine essentially truncates any output
pixels after the desired number of pixels has been written. When rotating a
source image and the output is not divisible by the block size, the incorrect
pixels could be truncated and the final output image can look shifted.
- Recolor or transformation for images w/ opacity or alpha channel can't be
obtained in a single G2D/PXP pipeline configuration. Two or multiple steps would
be required.
- Buffer address must be aligned to 64 bytes: set :c:macro:`LV_DRAW_BUF_ALIGN`
to `64` in "lv_conf.h".
No stride alignment is required: set :c:macro:`LV_DRAW_BUF_STRIDE_ALIGN` to
`1` in "lv_conf.h".
Project setup:
^^^^^^^^^^^^^^
- Add G2D related source files (and corresponding headers if available) to
project:
- "src/draw/nxp/g2d/lv_draw_buf_g2d.c": draw buffer callbacks
- "src/draw/nxp/g2d/lv_draw_g2d_fill.c": fill area
- "src/draw/nxp/g2d/lv_draw_g2d_img.c": blit image (w/ optional recolor or
transformation)
- "src/draw/nxp/g2d/lv_draw_g2d.c": draw unit initialization
- "src/draw/nxp/g2d/lv_draw_g2d_buf_map.c": hash map for g2d buffers
- "src/draw/nxp/g2d/lv_g2d_utils.c": function helpers

View File

@@ -0,0 +1,48 @@
================
Built-in Drivers
================
`Renesas <https://renesas.com/>`__ is an official partner of LVGL. Therefore, LVGL contains built-in support for
`Dave2D <https://www.renesas.com/document/mas/tes-dave2d-driver-documentation>`__ (the GPU of Renesas) and we also maintain
ready-to-use Renesas projects.
Dave2D
******
Dave2D is capable of accelerating most of the drawing operations of LVGL:
- Rectangle drawing, even with gradients
- Image drawing, scaling, and rotation
- Letter drawing
- Triangle drawing
- Line drawing
As Dave2D works in the background, the CPU is free for other tasks. In practice, during rendering, Dave2D can reduce the CPU usage by
half or to one-third, depending on the application.
GLCDC
*****
GLCDC is a multi-stage graphics output peripheral available in several Renesas MCUs. It is able to drive LCD panels via a highly
configurable RGB interface.
More info can be found at the :ref:`driver's page<renesas_glcdc>`.
MPU Drivers
***********
Renesas MPUs (i.e., the RZ/G family) can use LVGL MPU drivers.
Wayland
-------
Supported on boards with a Wayland desktop AKA a Wayland compositor.
fbdev
-----
Almost always available.

View File

@@ -0,0 +1,15 @@
.. _renesas:
=======
Renesas
=======
.. toctree::
:maxdepth: 2
built_in_drivers
ra_family
rx_family
rzg_family
rza_family
supported_boards

View File

@@ -0,0 +1,86 @@
=========
RA Family
=========
.. |sup2| unicode:: U+000B2 .. SUPERSCRIPT TWO
.. |img_debug_btn| image:: /_static/images/renesas/debug_btn.png
:alt: Debug button
Supported boards in the RA Family:
- **EK-RA8D1**
- **EK-RA6M3G**
Run the Project
***************
- The official IDE of Renesas is called e\ |sup2| studio. As it's Eclipse-based, it runs on Windows, Linux, and Mac as well.
The RA family requires the latest version with FSP 5.3. It can be downloaded `here <https://www.renesas.com/us/en/software-tool/flexible-software-package-fsp>`__.
- JLink is used for debugging, it can be downloaded `here <https://www.segger.com/downloads/jlink/>`__.
- Clone the ready-to-use repository for your selected board:
.. code-block:: shell
git clone https://github.com/lvgl/lv_port_renesas_ek-ra8d1.git --recurse-submodules
Downloading the `.zip` from GitHub doesn't work as it doesn't download the submodules.
- Open e\ |sup2| studio, go to ``File`` -> ``Import project`` and select ``General`` / ``Existing projects into workspace``
- Browse the cloned folder and press ``Finish``.
- Double click on ``configuration.xml``. This will activate the `Configuration Window`.
Renesas' Flexible Software Package (FSP) includes BSP and HAL layer support extended with multiple RTOS variants and other middleware stacks.
The components will be available via code generation, including the entry point of *"main.c"*.
Press ``Generate Project Content`` in the top right corner.
.. image:: /_static/images/renesas/generate.png
:alt: Code generation with FSP
- Build the project by pressing ``Ctrl`` + ``Alt`` + ``B``
- Click the Debug button (|img_debug_btn|). If prompted with `Debug Configurations`, on the `Debugger` tab select the ``J-Link ARM`` as `Debug hardware` and the proper IC as `Target Device`:
- ``R7FA8D1BH`` for EK-RA8D1
.. image:: /_static/images/renesas/debug_ra8.png
:alt: Debugger parameters for RA8
- ``R7FA6M3AH`` for EK-RA6M3G
.. image:: /_static/images/renesas/debug_ra6.png
:alt: Debugger parameters for RA6
.. note::
On EK-RA8D1 boards, the ``SW1`` DIP switch (middle of the board) 7 should be ON, all others are OFF.
Modify the project
******************
Open a demo
-----------
The entry point of the main task is contained in ``src/LVGL_thread_entry.c``.
You can disable the LVGL demos (or just comment them out) and call some ``lv_example_...()`` functions, or add your custom code.
Configuration
-------------
``src/lv_conf.h`` contains the most important settings for LVGL. Namely:
- :c:macro:`LV_COLOR_DEPTH` to set LVGL's default color depth
- :c:macro:`LV_MEM_SIZE` to set the maximum RAM available for LVGL
- :c:macro:`LV_USE_DAVE2D` to enable the GPU
Hardware and software components can be modified in a visual way using the `Configuration Window`.
Support
*******
In case of any problems or questions open an issue in the corresponding repository.

View File

@@ -0,0 +1,99 @@
=========
RX Family
=========
.. |sup2| unicode:: U+000B2 .. SUPERSCRIPT TWO
.. |img_debug_btn| image:: /_static/images/renesas/debug_btn.png
:alt: Debug button
Supported boards in the RX Family:
- **RX72N Envision Kit**
Run the Project
***************
- The official IDE of Renesas is called e\ |sup2| studio. As it's Eclipse-based, it runs on Windows, Linux, and Mac as well.
It can be downloaded `here <https://www.renesas.com/us/en/software-tool/e-studio>`__.
- Download and install the required driver for the debugger
- for Windows: `64 bit here <https://www.renesas.com/us/en/document/uid/usb-driver-renesas-mcu-tools-v27700-64-bit-version-windows-os?r=488806>`__
and `32 bit here <https://www.renesas.com/us/en/document/uid/usb-driver-renesas-mcu-toolse2e2-liteie850ie850apg-fp5-v27700for-32-bit-version-windows-os?r=488806>`__
- for Linux: `here <https://www.renesas.com/us/en/document/swo/e2-emulator-e2-emulator-lite-linux-driver?r=488806>`__
- RX72 requires an external compiler for the RXv3 core. A free and open-source version is available
`here <https://llvm-gcc-renesas.com/rx-download-toolchains/>`__ after a registration.
The compiler must be activated in e\ |sup2| studio:
- Go to go to ``Help`` -> ``Add Renesas Toolchains``
- Press the ``Add...`` button
- Browse the installation folder of the toolchain
<br/>
.. image:: /_static/images/renesas/toolchains.png
:alt: Toolchains
- Clone the ready-to-use `lv_port_renesas_rx72n-envision-kit <https://github.com/lvgl/lv_port_renesas_rx72n-envision-kit.git>`__ repository:
.. code-block:: shell
git clone https://github.com/lvgl/lv_port_renesas_rx72n-envision-kit.git --recurse-submodules
Downloading the `.zip` from GitHub doesn't work as it doesn't download the submodules.
- Open e\ |sup2| studio, go to ``File`` -> ``Import project`` and select ``General`` / ``Existing projects into workspace``
- Select the cloned folder and press ``Finish``.
- Double click on ``RX72N_EnVision_LVGL.scfg``. This will activate the `Configuration Window`.
Renesas' Smart Configurator (SMC) includes BSP and HAL layer support extended with multiple RTOS variants and other middleware stacks.
The components will be available via code generation, including the entry point of the application.
Press ``Generate Code`` in the top right corner.
.. image:: /_static/images/renesas/generate_smc.png
:alt: Code generation with SMC
- Build the project by pressing ``Ctrl`` + ``Alt`` + ``B``
- Click the Debug button (|img_debug_btn|). If prompted with `Debug Configurations`, on the `Debugger` tab select the ``E2 Lite``
as `Debug hardware` and ``R5F572NN`` as `Target Device`:
.. image:: /_static/images/renesas/debug_rx72.png
:alt: Debugger parameters for RX72
.. note::
Make sure that both channels of ``SW1`` DIP switch (next to ``ECN1``) are OFF.
Modify the project
******************
Open a demo
-----------
The entry point of the main task is contained in ``src/LVGL_thread_entry.c``.
You can disable the LVGL demos (or just comment them out) and call some ``lv_example_...()`` functions, or add your custom code.
Configuration
-------------
``src/lv_conf.h`` contains the most important settings for LVGL. Namely:
- :c:macro:`LV_COLOR_DEPTH` to set LVGL's default color depth
- :c:macro:`LV_MEM_SIZE` to set the maximum RAM available for LVGL
- :c:macro:`LV_USE_DAVE2D` to enable the GPU
Hardware and software components can be modified in a visual way using the `Configuration Window`.
Support
*******
In case of any problems or questions open an issue in the corresponding repository.

View File

@@ -0,0 +1,48 @@
===========
RZ/A Family
===========
Supported boards in the RZ/A Family:
- **RZ/A3M**
Run the Project
***************
- The RZ/A boards are MPUs with a focus on baremetal and RTOS applications. Projects are built for them using e2 Studio IDE, available for Windows, Mac, and Linux.
- Clone the ready-to-use repository for your board:
.. code-block:: shell
git clone https://github.com/lvgl/lv_port_renesas-ek-rz_a3m --recurse-submodules
Downloading the `.zip` from GitHub doesn't work as it doesn't download the submodules.
- Follow the instructions in the project README.md to
build and flash the project to the board.
Modify the project
******************
Open a demo
-----------
The entry point is contained in ``src/LVGL_thread_entry.c``.
You can disable the LVGL demos (``lv_demo_benchmark()``) (or just comment them out)
and call some ``lv_example_...()`` functions, or add your custom code.
Configuration
-------------
Edit ``lv_conf.h`` to configure LVGL.
It will automatically run any demo that is enabled in ``lv_conf.h``. You can see ``lv_conf.defaults`` for a summary of the configs which have been changed from the defaults.
Support
*******
In case of any problems or questions open an issue in the corresponding repository.

View File

@@ -0,0 +1,61 @@
===========
RZ/G Family
===========
Supported boards in the RZ/G Family:
- **RZ/G2L-EVKIT**
- **RZ/G2UL-EVKIT**
Run the Project
***************
- The RZ/G boards are MPUs with support for running Linux. Projects are built for them using a cross-compiler
CLI toolchain SDK in a Linux PC environment (WSL, Docker, etc. can be used on Windows).
- The G2L has a Wayland desktop and the project appears as a Wayland window. The G2UL does not have
a desktop so the project is fullscreen and uses the ``fbdev`` driver.
- The SDK currently uses LVGL v8.3 so this project uses this version to mirror the SDK version,
even though LVGL is statically linked. You may try using newer versions of LVGL.
- Clone the ready-to-use repository for your selected board:
.. code-block:: shell
git clone https://github.com/lvgl/lv_port_renesas_rz-g2l-evkit --recurse-submodules
Downloading the `.zip` from GitHub doesn't work as it doesn't download the submodules.
- Follow the instructions in the project README.md to obtain the SD Card image and toolchain installer,
build, and upload the project to the board.
- Stop any automatically started demos (on G2UL run ``systemctl stop demo-launcher`` in the terminal).
- Run the project
.. code-block:: shell
./lvgl_demo_benchmark
Modify the project
******************
Open a demo
-----------
The entry point is contained in ``src/main.c``.
You can disable the LVGL demos (``lv_demo_benchmark()``) (or just comment them out)
and call some ``lv_example_...()`` functions, or add your custom code.
Configuration
-------------
Edit ``lv_conf.h`` and ``lv_drv_conf.h`` to configure LVGL. The board image
contains LVGL and lv_drivers as dynamically linkable libraries. This project builds LVGL statically
for customizability and to port the LVGL v9 benchmark LVGL v8.3.
Support
*******
In case of any problems or questions open an issue in the corresponding repository.

View File

@@ -0,0 +1,112 @@
================
Supported Boards
================
.. list-table::
* - Board Name
- CPU
- Memory
- Display
- `Board <https://lvgl.io/boards>`__ video
- Links
* - **EK-RA8D1**
-
| 480 MHz
| Arm Cortex-M85
-
| 1MB internal SDRAM
| 64MB external SDRAM
| 2MB internal flash
| 64MB external octo-SPI flash
-
| 4.5”
| 480x854
| 2-lane MIPI
- .. raw:: html
<iframe width="320" height="180" src="https://www.youtube.com/embed/WkJPB8wto_U" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
- `Demo repository <https://github.com/lvgl/lv_port_renesas_ek-ra8d1>`__
* - **EK-RA6M3G**
-
| 120 MHz
| Arm Cortex-M4
-
| 640kB internal SRAM
| 2MB internal SRAM
| 32MB external QSPI flash
-
| 4.3”
| 480x272
| Parallel RGB565
- .. raw:: html
<iframe width="320" height="180" src="https://www.youtube.com/embed/0kar4Ee3Qic" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
-
| `Demo repository <https://github.com/lvgl/lv_port_renesas_ek-ra6m3g>`__
* - **RX72N Envision Kit**
-
| 240 MHz
| Renesas RXv3
-
| 1MB internal SRAM
| 4MB internal SRAM
| 32MB external QSPI flash
-
| 4.3”
| 480x272
| Parallel RGB565
- .. raw:: html
<iframe width="320" height="180" src="https://www.youtube.com/embed/__56v8DsfH0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
- `Demo repository <https://github.com/lvgl/lv_port_renesas_rx72n-envision-kit>`__
* - **RZ/G2L-EVKIT**
-
| 1.2 GHz
| Arm Cortex-A55
-
| 2GB DDR4 SDRAM
-
| 1920x1080
| Micro-HDMI
- .. raw:: html
<iframe width="320" height="180" src="https://www.youtube.com/embed/oeuVvB7y-QA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
- `Demo repository <https://github.com/lvgl/lv_port_renesas_rz-g2l-evkit>`__
* - **RZ/G2UL-EVKIT**
-
| 1.0 GHz
| Arm Cortex-A55
-
| 1GB DDR4 SDRAM
-
| 1280x800
| Parallel RGB888
| with Micro-HDMI bridge
- .. raw:: html
<iframe width="320" height="180" src="https://www.youtube.com/embed/VnynDLR36Xc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
- `Demo repository <https://github.com/lvgl/lv_port_renesas_rz-g2ul-evkit>`__
* - **RZ/A3M**
-
| 1.0 GHz
| Arm Cortex-A55
-
| 128MB internal DDR3L-SDRAM
| 128KB internal SRAM
| 128MB external QSPI NAND flash
| 32MB external QSPI NOR flash
-
| 4.3”
| 1280x720
| MIPI
- .. raw:: html
<iframe width="320" height="180" src="https://www.youtube.com/embed/IEjBGgrR_mk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
- `Demo repository <https://github.com/lvgl/lv_port_renesas-ek-rz_a3m>`__

View File

@@ -0,0 +1,299 @@
=====
STM32
=====
LVGL Can be added to `STM32CubeIDE <https://www.st.com/en/development-tools/stm32cubeide.html>`__
in a similar fashion to any other Eclipse-based IDE.
Including LVGL in a Project
---------------------------
- Create or open a project in STM32CubeIDE.
- Copy the entire LVGL folder to *[project_folder]/Drivers/lvgl*.
- In the STM32CubeIDE **Project Explorer** pane: right click on the
LVGL folder that you copied (you may need to refresh the view first
before it will appear), and select **Add/remove include path…**. If
this doesn't appear, or doesn't work, you can review your project
include paths under the **Project** -> **Properties** menu, and then
navigating to **C/C++ Build** -> **Settings** -> **Include paths**, and
ensuring that the LVGL directory is listed.
Now that the source files are included in your project, follow the instructions to
:ref:`add LVGL to your project <adding_lvgl_to_your_project>` and to create the
``lv_conf.h`` file, and initialise the display.
Bare Metal Example
------------------
A minimal example using STM32CubeIDE, and HAL. \* When setting up
**Pinout and Configuration** using the **Device Configuration Tool**,
select **System Core** -> **SYS** and ensure that **Timebase Source** is
set to **SysTick**. \* Configure any other peripherals (including the
LCD panel), and initialise them in *main.c*. \* ``#include "lvgl.h"`` in
the *main.c* file. \* Create some frame buffer(s) as global variables:
.. code-block:: c
/* Frame buffers
* Static or global buffer(s). The second buffer is optional
* TODO: Adjust color format and choose buffer size. DISPLAY_WIDTH * 10 is one suggestion. */
#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565)) /* will be 2 for RGB565 */
#define BUFF_SIZE (DISPLAY_WIDTH * 10 * BYTES_PER_PIXEL)
static uint8_t buf_1[BUFF_SIZE];
static uint8_t buf_2[BUFF_SIZE];
- In your ``main()`` function, after initialising your CPU,
peripherals, and LCD panel, call :cpp:func:`lv_init` to initialise LVGL.
You can then create the display driver using
:cpp:func:`lv_display_create`, and register the frame buffers using
:cpp:func:`lv_display_set_buffers`.
.. code-block:: c
//Initialise LVGL UI library
lv_init();
lv_display_t * disp = lv_display_create(WIDTH, HEIGHT); /* Basic initialization with horizontal and vertical resolution in pixels */
lv_display_set_flush_cb(disp, my_flush_cb); /* Set a flush callback to draw to the display */
lv_display_set_buffers(disp, buf_1, buf_2, sizeof(buf_1), LV_DISPLAY_RENDER_MODE_PARTIAL); /* Set an initialized buffer */
- Create some dummy Widgets to test the output:
.. code-block:: c
/* Change Active Screen's background color */
lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x003a57), LV_PART_MAIN);
lv_obj_set_style_text_color(lv_screen_active(), lv_color_hex(0xffffff), LV_PART_MAIN);
/* Create a spinner */
lv_obj_t * spinner = lv_spinner_create(lv_screen_active());
lv_obj_set_size(spinner, 64, 64);
lv_obj_align(spinner, LV_ALIGN_BOTTOM_MID, 0, 0);
- Add a call to :cpp:func:`lv_timer_handler` inside your ``while(1)`` loop:
.. code-block:: c
/* Infinite loop */
while (1)
{
lv_timer_handler();
HAL_Delay(5);
}
- Add a call to :cpp:func:`lv_tick_inc` inside the :cpp:func:`SysTick_Handler` function. Open the *stm32xxxx_it.c*
file (the name will depend on your specific MCU), and update the :cpp:func:`SysTick_Handler` function:
.. code-block:: c
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
HAL_SYSTICK_IRQHandler();
lv_tick_inc(1);
#ifdef USE_RTOS_SYSTICK
osSystickHandler();
#endif
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
- Finally, write the callback function, ``my_flush_cb``, which will send the display buffer to your LCD panel. Below is
one example, but it will vary depending on your setup.
.. code-block:: c
void my_flush_cb(lv_display_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
//Set the drawing region
set_draw_window(area->x1, area->y1, area->x2, area->y2);
int height = area->y2 - area->y1 + 1;
int width = area->x2 - area->x1 + 1;
//We will do the SPI write manually here for speed
HAL_GPIO_WritePin(DC_PORT, DC_PIN, GPIO_PIN_SET);
//CS low to begin data
HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET);
//Write colour to each pixel
for (int i = 0; i < width * height; i++) {
uint16_t color_full = (color_p->red << 11) | (color_p->green << 5) | (color_p->blue);
parallel_write(color_full);
color_p++;
}
//Return CS to high
HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET);
/* IMPORTANT!!!
* Inform the graphics library that you are ready with the flushing */
lv_display_flush_ready(disp);
}
FreeRTOS Example
----------------
A minimal example using STM32CubeIDE, HAL, and CMSISv1 (FreeRTOS).
*Note that we have not used Mutexes in this example, however LVGL is* **NOT**
*thread safe and so Mutexes should be used*. See: :ref:`threading`
\* ``#include "lvgl.h"`` \* Create your frame buffer(s) as global variables:
.. code-block:: c
/* Frame buffers
* Static or global buffer(s). The second buffer is optional */
#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565)) /* will be 2 for RGB565 */
/* TODO: Declare your own BUFF_SIZE appropriate to your system. */
#define BUFF_SIZE (DISPLAY_WIDTH * 10 * BYTES_PER_PIXEL)
static uint8_t buf_1[BUFF_SIZE];
static uint8_t buf_2[BUFF_SIZE];
- In your ``main`` function, after your peripherals (SPI, GPIOs, LCD
etc) have been initialised, initialise LVGL using :cpp:func:`lv_init`,
create a new display driver using :cpp:func:`lv_display_create`, and
register the frame buffers using :cpp:func:`lv_display_set_buffers`.
.. code-block:: c
/* Initialise LVGL UI library */
lv_init();
lv_display_t *display = lv_display_create(WIDTH, HEIGHT); /* Create the display */
lv_display_set_flush_cb(display, my_flush_cb); /* Set a flush callback to draw to the display */
lv_display_set_buffers(display, buf_1, buf_2, sizeof(buf_1), LV_DISPLAY_RENDER_MODE_PARTIAL); /* Set an initialized buffer */
/* Register the touch controller with LVGL - Not included here for brevity. */
- Create some dummy Widgets to test the output:
.. code-block:: c
/* Change Active Screen's background color */
lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x003a57), LV_PART_MAIN);
lv_obj_set_style_text_color(lv_screen_active(), lv_color_hex(0xffffff), LV_PART_MAIN);
/* Create a spinner */
lv_obj_t * spinner = lv_spinner_create(lv_screen_active());
lv_obj_set_size(spinner, 64, 64);
lv_obj_align(spinner, LV_ALIGN_BOTTOM_MID, 0, 0);
- Create two threads to call :cpp:func:`lv_timer_handler`, and
:cpp:func:`lv_tick_inc`.You will need two ``osThreadId`` handles for
CMSISv1. These don't strictly have to be globally accessible in this
case, however STM32Cube code generation does by default. If you are
using CMSIS and STM32Cube code generation it should look something
like this:
.. code-block:: c
//Thread Handles
osThreadId lvgl_tickHandle;
osThreadId lvgl_timerHandle;
/* definition and creation of lvgl_tick */
osThreadDef(lvgl_tick, LVGLTick, osPriorityNormal, 0, 1024);
lvgl_tickHandle = osThreadCreate(osThread(lvgl_tick), NULL);
//LVGL update timer
osThreadDef(lvgl_timer, LVGLTimer, osPriorityNormal, 0, 1024);
lvgl_timerHandle = osThreadCreate(osThread(lvgl_timer), NULL);
- And create the thread functions:
.. code-block:: c
/* LVGL timer for tasks. */
void LVGLTimer(void const * argument)
{
for(;;)
{
lv_timer_handler();
osDelay(20);
}
}
/* LVGL tick source */
void LVGLTick(void const * argument)
{
for(;;)
{
lv_tick_inc(10);
osDelay(10);
}
}
- Finally, create the ``my_flush_cb`` function to output the frame
buffer to your LCD. The specifics of this function will vary
depending on which MCU features you are using. Below is a simple
example of a parallel LCD interface, adjust this to suit your specific
display and MCU capabilities.
.. code-block:: c
void my_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map)
{
uint16_t * color_p = (uint16_t *)px_map;
//Set the drawing region
set_draw_window(area->x1, area->y1, area->x2, area->y2);
int height = area->y2 - area->y1 + 1;
int width = area->x2 - area->x1 + 1;
//Begin SPI Write for DATA
HAL_GPIO_WritePin(DC_PORT, DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET);
//Write colour to each pixel
for (int i = 0; i < width * height; i++) {
parallel_write(color_p);
color_p++;
}
//Return CS to high
HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET);
/* IMPORTANT!!!
* Inform the graphics library that you are ready with the flushing */
lv_display_flush_ready(display);
}
.. _dma2d:
DMA2D Support
-------------
LVGL supports DMA2D - a feature of some STM32 MCUs which can improve performance
when blending fills and images. Some STM32 product lines such as STM32F4 STM32F7, STM32L4,
STM32U5, and STM32H7 include models with DMA2D support.
LVGL's integration with DMA2D can be enabled by setting ``LV_USE_DRAW_DMA2D``
to ``1`` in ``lv_conf.h``
With ``LV_USE_DRAW_DMA2D_INTERRUPT`` set to ``0`` and ``LV_USE_OS`` set to ``LV_OS_NONE``,
DMA2D will draw some fills and images concurrently with the software render where
possible. If ``LV_USE_DRAW_DMA2D_INTERRUPT`` is set to ``1`` and ``LV_USE_OS`` set to
``LV_OS_FREERTOS`` (or another OS) the main difference will be that the core will idle
instead of "busywait" while waiting for a DMA2D transfer to complete.
If ``LV_USE_DRAW_DMA2D_INTERRUPT`` is enabled then you are required to call
:cpp:expr:`lv_draw_dma2d_transfer_complete_interrupt_handler` whenever the DMA2D
"transfer complete" global interrupt is received.
DMA2D also makes possible to mix layers that have color format on
:c:macro:`LV_COLOR_FORMAT_ARGB1555` on top of :c:macro:`LV_COLOR_FORMAT_RGB565`
layers.
If your STM device has a NeoChrom GPU, you can use the :ref:`Nema GFX renderer <nema_gfx>` instead.

View File

@@ -0,0 +1,127 @@
=========================
X11 Display/Inputs driver
=========================
Overview
--------
| The **X11** display/input `driver <https://github.com/lvgl/lvgl/src/drivers/x11>`__ offers support for simulating the LVGL display and keyboard/mouse inputs in an X11 desktop window.
| It is an alternative to **Wayland**, **XCB**, **SDL** or **Qt**.
The main purpose for this driver is for testing/debugging the LVGL application in a **Linux** simulation window.
Prerequisites
-------------
The X11 driver uses XLib to access the linux window manager.
1. Install XLib: ``sudo apt-get install libx11-6`` (should be installed already)
2. Install XLib development package: ``sudo apt-get install libx11-dev``
Configure X11 driver
--------------------
1. Enable the X11 driver support in lv_conf.h, by cmake compiler define or by KConfig
.. code-block:: c
#define LV_USE_X11 1
2. Optional configuration options:
- Direct Exit
.. code-block:: c
#define LV_X11_DIRECT_EXIT 1 /* preferred default - ends the application automatically if last window has been closed */
// or
#define LV_X11_DIRECT_EXIT 0 /* application is responsible for ending the application (e.g. by own LV_EVENT_DELETE handler */
- Double buffering
.. code-block:: c
#define LV_X11_DOUBLE_BUFFER 1 /* preferred default */
// or
#define LV_X11_DOUBLE_BUFFER 0 /* not recommended */
- Render mode
.. code-block:: c
#define LV_X11_RENDER_MODE_PARTIAL 1 /* LV_DISPLAY_RENDER_MODE_PARTIAL, preferred default */
// or
#define LV_X11_RENDER_MODE_DIRECT 1 /* LV_DISPLAY_RENDER_MODE_DIRECT, not recommended for X11 driver */
// or
#define LV_X11_RENDER_MODE_DULL 1 /* LV_DISPLAY_RENDER_MODE_FULL, not recommended for X11 driver */
Usage
-----
| The minimal initialisation opening a window and enabling keyboard/mouse support
| (e.g. in main.c, LV_X11_DIRECT_EXIT must be 1):
.. code-block:: c
int main(int argc, char ** argv)
{
...
/* initialize X11 display driver */
lv_display_t * disp = lv_x11_window_create("LVGL X11 Simulation", monitor_hor_res, monitor_ver_res);
/* initialize X11 input drivers (for keyboard, mouse & mousewheel) */
lv_x11_inputs_create(disp, NULL);
...
while(true)
{
...
/* Periodically call the lv_timer handler */
lv_timer_handler();
}
}
| Full initialisation with mouse pointer symbol and own application exit handling
| (dependent on LV_X11_DIRECT_EXIT (can be 1 or 0))
.. code-block:: c
bool terminated = false;
#if !LV_X11_DIRECT_EXIT
static void on_close_cb(lv_event_t * e)
{
...
terminate = true;
}
#endif
int main(int argc, char ** argv)
{
...
/* initialize X11 display driver */
lv_display_t * disp = lv_x11_window_create("LVGL X11 Simulation", monitor_hor_res, monitor_ver_res);
lv_display_add_event_cb(disp, on_close_cb, LV_EVENT_DELETE, disp);
/* initialize X11 input drivers (for keyboard, mouse & mousewheel) */
LV_IMAGE_DECLARE(my_mouse_cursor_icon);
lv_x11_inputs_create(disp, &my_mouse_cursor_icon);
#if !LV_X11_DIRECT_EXIT
/* set optional window close callback to enable application cleanup and exit */
lv_x11_window_set_close_cb(disp, on_close_cb, disp);
#endif
...
while(!terminated)
{
...
/* Periodically call the lv_timer handler */
lv_timer_handler();
}
}

View File

@@ -0,0 +1,82 @@
========================
Linux Framebuffer Driver
========================
Overview
--------
The Linux framebuffer (fbdev) is a linux subsystem used to display graphics. It is a hardware-independent API that gives user space software
access to the framebuffer (the part of a computer's video memory containing a current video frame) using only the Linux kernel's own basic
facilities and its device file system interface, avoiding the need for libraries that implement video drivers in user space.
Prerequisites
-------------
Your system has a framebuffer device configured (usually under ``/dev/fb0``).
Configuring the driver
----------------------
Enable the framebuffer driver support in lv_conf.h, by cmake compiler define or by KConfig. Additionally you may configure the rendering
mode.
.. code-block:: c
#define LV_USE_LINUX_FBDEV 1
#define LV_LINUX_FBDEV_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL
Usage
-----
To set up a framebuffer-based display, first create a display with ``lv_linux_fbdev_create``. Afterwards set the framebuffer device
node on the display (usually this is ``/dev/fb0``).
.. code-block:: c
lv_display_t *disp = lv_linux_fbdev_create();
lv_linux_fbdev_set_file(disp, "/dev/fb0");
If your screen stays black or only draws partially, you can try enabling direct rendering via ``LV_DISPLAY_RENDER_MODE_DIRECT``. Additionally,
you can activate a force refresh mode with ``lv_linux_fbdev_set_force_refresh(true)``. This usually has a performance impact though and shouldn't
be enabled unless really needed.
Hide the cursor
---------------
You may encounter a blinking cursor on the screen. The method to hide it
varies depending on the platform. For instance, here is how it can be done
on a Raspberry Pi:
1. Edit ``/boot/cmdline.txt`` file.
2. Add ``vt.global_cursor_default=0``.
Common mistakes
---------------
Default resolution issue
^^^^^^^^^^^^^^^^^^^^^^^^
When the Linux kernel initializes, it sets up subsystems like the framebuffer
(fbdev) to manage display output. If an HDMI display is connected, the kernel
detects it and allocates a portion of RAM as the framebuffer, which holds the
pixel data for rendering images.
However, a common issue arises when the display is not powered on during the
boot process. If the board is powered on before the screen, the kernel may
incorrectly configure the framebuffer resolution. As a result, both the visible
and virtual resolutions can be incorrect, leading to display problems.
This issue often occurs with HDMI connections where the display is powered up
after the system has already booted.
The following command outputs the current settings of the specified framebuffer
device, such as resolution, pixel depth, and timings.
.. code-block::
fbset -fb /dev/fb0
To prevent display-related issues, it is recommended to ensure all devices,
including the HDMI display, are connected and powered on before powering up
the board.

View File

@@ -0,0 +1,114 @@
=====
FT81x
=====
A minimal framebuffer driver for EVE FT81x smart display controllers. Works for BT81x too.
Single-buffered partial render mode supported for now. It may not support DSPI or QSPI currently.
Tested at 32 MHz on ft812 and at 23 MHz on bt817.
Usage
*****
.. code-block:: c
#define FB_SIZE 800 * 2 * 50
#define MAX_TRANSFER_SIZE FB_SIZE
static void spi_cb(lv_display_t * disp, lv_ft81x_spi_operation operation, void * data, uint32_t length)
{
spi_device_handle_t spi = lv_ft81x_get_user_data(disp);
switch(operation) {
case LV_FT81X_SPI_OPERATION_CS_ASSERT:
gpio_set_level(CS_PIN, 0);
break;
case LV_FT81X_SPI_OPERATION_CS_DEASSERT:
gpio_set_level(CS_PIN, 1);
esp_rom_delay_us(10); /* tiny delay in case a CS_ASSERT immediately follows */
break;
case LV_FT81X_SPI_OPERATION_SEND: {
spi_transaction_t trans = {0};
while(length) {
uint32_t sz = length < MAX_TRANSFER_SIZE ? length : MAX_TRANSFER_SIZE;
trans.length = sz * 8;
trans.rxlength = 0;
trans.tx_buffer = data;
spi_device_polling_transmit(spi, &trans);
length -= sz;
data += sz;
}
break;
}
case LV_FT81X_SPI_OPERATION_RECEIVE: {
spi_transaction_t trans = {0};
trans.length = length * 8;
trans.rxlength = length * 8;
trans.rx_buffer = data;
spi_device_polling_transmit(spi, &trans);
break;
}
}
}
int main() {
/* ... */
// reset the ft81x
gpio_set_level(PD_PIN, 0);
vTaskDelay(6 / portTICK_PERIOD_MS);
gpio_set_level(PD_PIN, 1);
vTaskDelay(21 / portTICK_PERIOD_MS);
/* ... */
// taken from https://github.com/lvgl/lvgl_esp32_drivers/blob/9fed1cc47b5a45fec6bae08b55d2147d3b50260c/lvgl_tft/EVE_config.h
// NHD-5.0-800480FT-CxXx-xxx 800x480 5.0" Newhaven, resistive or capacitive, FT81x
// EVE_NHD_50
#define EVE_VSYNC0 (0L)
#define EVE_VSYNC1 (3L)
#define EVE_VOFFSET (32L)
#define EVE_VCYCLE (525L)
#define EVE_HSYNC0 (0L)
#define EVE_HSYNC1 (48L)
#define EVE_HOFFSET (88L)
#define EVE_HCYCLE (928L)
#define EVE_PCLKPOL (0L)
#define EVE_SWIZZLE (0L)
#define EVE_PCLK (2L)
#define EVE_CSPREAD (1L)
lv_ft81x_parameters_t params = {
.hor_res = 800,
.ver_res = 480,
.hcycle = EVE_HCYCLE,
.hoffset = EVE_HOFFSET,
.hsync0 = EVE_HSYNC0,
.hsync1 = EVE_HSYNC1,
.vcycle = EVE_VCYCLE,
.voffset = EVE_VOFFSET,
.vsync0 = EVE_VSYNC0,
.vsync1 = EVE_VSYNC1,
.swizzle = EVE_SWIZZLE,
.pclkpol = EVE_PCLKPOL,
.cspread = EVE_CSPREAD,
.pclk = EVE_PCLK,
.has_crystal = true,
.is_bt81x = false
};
static uint8_t fb[FB_SIZE] __attribute__((aligned(4)));
lv_display_t * disp = lv_ft81x_create(&params, fb, FB_SIZE, spi_cb, spi);
/* ... */
}
Troubleshooting
***************
If the backlight does not come on (or is too bright or dim),
try changing the value of ``PWM_DUTY_BACKLIGHT_ON``
in ``lv_ft81x.c``, which can vary by board.

View File

@@ -0,0 +1,216 @@
=================================================
Generic MIPI DCS compatible LCD Controller driver
=================================================
Overview
--------
From the `Wikipedia <https://en.wikipedia.org/wiki/MIPI_Alliance>`__:
`MIPI Alliance <https://www.mipi.org/>`__ is a global business alliance that develops technical specifications
for the mobile ecosystem, particularly smart phones but including mobile-influenced industries. MIPI was founded in 2003 by Arm, Intel, Nokia, Samsung,
STMicroelectronics and Texas Instruments.
MIPI Alliance published a series of specifications related to display devices, including DBI (Display Bus Interface), DSI (Display Serial Interface) and DCS
(Display Command Set). Usually when one talks about a MIPI-compatible display, one thinks of a device with a DSI serial interface. However, the Display Bus Interface specification
includes a number of other, legacy interfaces, like SPI serial, or i8080-compatible parallel interface, which are often used to interface LCD displays to lower-end microcontrollers.
Furthermore, the DCS specification contains a standard command set, which is supported by a large number of legacy TFT LCD controllers, including the popular Sitronix
(ST7735, ST7789, ST7796) and Ilitek (ILI9341) SOCs. These commands provide a common interface to configure display orientation, color resolution, various power modes, and provide generic video memory access. On top
of that standard command set each LCD controller chip has a number of vendor-specific commands to configure voltage generator levels, timings, or gamma curves.
.. note::
It is important to understand that this generic MIPI LCD driver is not a hardware driver for displays with the DSI ("MIPI") serial interface. Instead, it implements the MIPI DCS command set used in many LCD controllers with an SPI or i8080 bus, and provides a common framework for chip-specific display controllers.
.. tip::
Although this is a generic driver, it can be used to support compatible chips which do not have a specific driver.
Prerequisites
-------------
There are no prerequisites.
Configuring the driver
----------------------
Enable the generic MIPI LCD driver support in lv_conf.h, by cmake compiler define or by KConfig
.. code-block:: c
#define LV_USE_GENERIC_MIPI 1
.. note::
:c:macro:`LV_USE_GENERIC_MIPI` is automatically enabled when a compatible driver is enabled.
Usage
-----
You need to implement two platform-dependent functions:
.. code-block:: c
/* Send short command to the LCD. This function shall wait until the transaction finishes. */
int32_t my_lcd_send_cmd(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, const uint8_t *param, size_t param_size)
{
...
}
/* Send large array of pixel data to the LCD. If necessary, this function has to do the byte-swapping. This function can do the transfer in the background. */
int32_t my_lcd_send_color(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, uint8_t *param, size_t param_size)
{
...
}
The only difference between the :cpp:func:`my_lcd_send_cmd()` and :cpp:func:`my_lcd_send_color()` functions is that :cpp:func:`my_lcd_send_cmd()` is used to send short commands and it is expected
complete the transaction when it returns (in other words, it should be blocking), while :cpp:func:`my_lcd_send_color()` is only used to send pixel data, and it is recommended to use
DMA to transmit data in the background. More sophisticated methods can be also implemented, like queuing transfers and scheduling them in the background.
Please note that while display flushing is handled by the driver, it is the user's responsibility to call :cpp:func:`lv_display_flush_ready()`
when the color transfer completes. In case of a DMA transfer this is usually done in a transfer ready callback.
.. note::
While it is acceptable to use a blocking implementation for the pixel transfer as well, performance will suffer.
.. tip::
Care must be taken to avoid sending a command while there is an active transfer going on in the background. It is the user's responsibility to implement this either
by polling the hardware, polling a global variable (which is reset at the end of the transfer), or by using a semaphore or other locking mechanism.
Please also note that the driver does not handle the draw buffer allocation, because this may be platform-dependent, too. Thus you need to allocate the buffers and assign them
to the display object as usual by calling :cpp:func:`lv_display_set_buffers()`.
The driver can be used to create multiple displays. In such a configuration the callbacks must be able to distinguish between the displays. Usually one would
implement a separate set of callbacks for each display. Also note that the user must take care of arbitrating the bus when multiple devices are connected to it.
Example
-------
.. note::
You can find a step-by-step guide and the actual implementation of the callbacks on an STM32F746 using STM32CubeIDE and the ST HAL libraries here: :ref:`lcd_stm32_guide`
.. code-block:: c
#include "src/drivers/display/st7789/lv_st7789.h"
#define LCD_H_RES 240
#define LCD_V_RES 320
#define LCD_BUF_LINES 60
lv_display_t *my_disp;
...
/* Initialize LCD I/O bus, reset LCD */
static int32_t my_lcd_io_init(void)
{
...
return HAL_OK;
}
/* Send command to the LCD controller */
static void my_lcd_send_cmd(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, const uint8_t *param, size_t param_size)
{
...
}
/* Send pixel data to the LCD controller */
static void my_lcd_send_color(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, uint8_t *param, size_t param_size)
{
...
/* This must be called to signal that the transfer has finished.
* It is typically called in a "DMA transfer complete" callback
* long after `my_lcd_send_color` has returned.
*/
lv_display_flush_ready(disp);
}
int main(int argc, char ** argv)
{
...
/* Initialize LVGL */
lv_init();
/* Initialize LCD bus I/O */
if (my_lcd_io_init() != 0)
return;
/* Create the LVGL display object and the LCD display driver */
my_disp = lv_lcd_generic_mipi_create(LCD_H_RES, LCD_V_RES, LV_LCD_FLAG_NONE, my_lcd_send_cmd, my_lcd_send_color);
/* Set display orientation to landscape */
lv_display_set_rotation(my_disp, LV_DISPLAY_ROTATION_90);
/* Configure draw buffers, etc. */
uint8_t * buf1 = NULL;
uint8_t * buf2 = NULL;
uint32_t buf_size = LCD_H_RES * LCD_BUF_LINES * lv_color_format_get_size(lv_display_get_color_format(my_disp));
buf1 = lv_malloc(buf_size);
if(buf1 == NULL) {
LV_LOG_ERROR("display draw buffer malloc failed");
return;
}
/* Allocate secondary buffer if needed */
...
lv_display_set_buffers(my_disp, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
ui_init(my_disp);
while(true) {
...
/* Periodically call the lv_timer handler */
lv_timer_handler();
}
}
Advanced topics
---------------
Create flags
^^^^^^^^^^^^
The third argument of the :cpp:func:`lv_lcd_generic_mipi_create()` function is a flag array. This can be used to configure the orientation and RGB ordering of the panel if the
default settings do not work for you. In particular, the generic MIPI driver accepts the following flags:
.. code-block:: c
LV_LCD_FLAG_NONE
LV_LCD_FLAG_MIRROR_X
LV_LCD_FLAG_MIRROR_Y
LV_LCD_FLAG_BGR
You can pass multiple flags by ORing them together, e.g., :c:macro:`LV_LCD_FLAG_MIRROR_X` ``|`` :c:macro:`LV_LCD_FLAG_BGR`.
Custom command lists
^^^^^^^^^^^^^^^^^^^^
While the chip-specific drivers do their best to initialize the LCD controller correctly, it is possible, that different TFT panels need different configurations.
In particular a correct gamma setup is crucial for good color reproduction. Unfortunately, finding a good set of parameters is not easy. Usually the manufacturer
of the panel provides some example code with recommended register settings.
You can use the ``my_lcd_send_cmd()`` function to send an arbitrary command to the LCD controller. However, to make it easier to send a large number of parameters
the generic MIPI driver supports sending a custom command list to the controller. The commands must be put into a 'uint8_t' array:
.. code-block:: c
static const uint8_t init_cmd_list[] = {
<command 1>, <number of parameters>, <parameter 1>, ... <parameter N>,
<command 2>, <number of parameters>, <parameter 1>, ... <parameter N>,
...
LV_LCD_CMD_DELAY_MS, LV_LCD_CMD_EOF /* terminate list: this is required! */
};
...
lv_lcd_generic_mipi_send_cmd_list(my_disp, init_cmd_list);
You can add a delay between the commands by using the pseudo-command ``LV_LCD_CMD_DELAY_MS``, which must be followed by the delay given in 10ms units.
To terminate the command list you must use a delay with a value of ``LV_LCD_CMD_EOF``, as shown above.
See an actual example of sending a command list `here <https://github.com/lvgl/lvgl/blob/master/src/drivers/display/st7789/lv_st7789.c>`__.

View File

@@ -0,0 +1,73 @@
=============================
ILI9341 LCD Controller driver
=============================
Overview
--------
The `ILI9341 <https://www.buydisplay.com/download/ic/ILI9341.pdf>`__ is a 262,144-color single-chip SOC driver for a-TFT liquid crystal display with resolution of 240RGBx320
dots, comprising a 720-channel source driver, a 320-channel gate driver, 172,800 bytes GRAM for graphic
display data of 240RGBx320 dots, and power supply circuit.
ILI9341 supports parallel 8-/9-/16-/18-bit data bus MCU interface, 6-/16-/18-bit data bus RGB interface and
3-/4-line serial peripheral interface (SPI).
The ILI9341 LCD controller `driver <https://github.com/lvgl/lvgl/src/drivers/display/ili9341>`__ is a platform-agnostic driver, based on the `generic MIPI driver <https://github.com/lvgl/lvgl/doc/integration/drivers/display/gen_mipi.rst>`__.
It implements display initialization, supports display rotation and implements the display flush callback. The user needs to implement only two platform-specific functions to send
a command or pixel data to the controller via SPI or parallel bus. Typically these are implemented by calling the appropriate SDK library functions on the given platform.
Prerequisites
-------------
There are no prerequisites.
Configuring the driver
----------------------
Enable the ILI9341 driver support in lv_conf.h, by cmake compiler define or by KConfig
.. code-block:: c
#define LV_USE_ILI9341 1
Usage
-----
You need to implement two platform-dependent functions:
.. code-block:: c
/* Send short command to the LCD. This function shall wait until the transaction finishes. */
int32_t my_lcd_send_cmd(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, const uint8_t *param, size_t param_size)
{
...
}
/* Send large array of pixel data to the LCD. If necessary, this function has to do the byte-swapping. This function can do the transfer in the background. */
int32_t my_lcd_send_color(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, uint8_t *param, size_t param_size)
{
...
}
To create an ILI9341-based display use the function
.. code-block:: c
/**
* Create an LCD display with ILI9341 driver
* @param hor_res horizontal resolution
* @param ver_res vertical resolution
* @param flags default configuration settings (mirror, RGB ordering, etc.)
* @param send_cmd platform-dependent function to send a command to the LCD controller (usually uses polling transfer)
* @param send_color platform-dependent function to send pixel data to the LCD controller (usually uses DMA transfer: must implement a 'ready' callback)
* @return pointer to the created display
*/
lv_display_t * lv_ili9341_create(uint32_t hor_res, uint32_t ver_res, lv_lcd_flag_t flags,
lv_ili9341_send_cmd_cb_t send_cmd_cb, lv_ili9341_send_color_cb_t send_color_cb);
For additional details and a working example see the `generic MIPI driver documentation <https://github.com/lvgl/lvgl/doc/integration/drivers/display/gen_mipi.rst>`__.
.. note::
You can find a step-by-step guide and the actual implementation of the callbacks on an STM32F746 using STM32CubeIDE and the ST HAL libraries here: :ref:`lcd_stm32_guide`

View File

@@ -0,0 +1,17 @@
=======
Display
=======
.. toctree::
:maxdepth: 2
fbdev
ft81x
gen_mipi
ili9341
lcd_stm32_guide
renesas_glcdc
st_ltdc
st7735
st7789
st7796

View File

@@ -0,0 +1,320 @@
.. _lcd_stm32_guide:
=========================================================================
Step-by-step Guide: How to use the LVGL v9 LCD drivers with STM32 devices
=========================================================================
Introduction
------------
This guide is intended to be a step-by-step instruction of how to configure the STM32Cube HAL with the new TFT-LCD display drivers introduced in LVGL v9.0. The example code has been tested on the STM32F746-based Nucleo-F746ZG board with an ST7789-based LCD panel connected via SPI. The application itself and the hardware configuration code were generated with the STM32CubeIDE 1.14.0 tool.
.. tip::
ST Micro provide their own TFT-LCD drivers in their X-CUBE-DISPLAY Software Extension Package. While these drivers can be used with LVGL as well, the LVGL LCD drivers do not depend on this package.
The LVGL LCD drivers are meant as an alternative, simple to use API to implement LCD support for your LVGL-based project on any platform. Moreover, even in the initial release we support more LCD controllers than X-CUBE-DISPLAY currently provides, and we plan to add support for even more LCD controllers in the future.
Please note however, that unlike X-CUBE-DISPLAY the LVGL LCD drivers do not implement the communication part, whether SPI, parallel i8080 bus or other. It is the user's responsibility to implement and optimize these on their chosen platform. LVGL will only provide examples for the most popular platforms.
By following the steps you will have a fully functional program, which can be used as the foundation of your own LVGL-based project. If you are in a hurry and not interested in the details, you can find the final project `here <https://github.com/lvgl/lv_port_lcd_stm32>`__. You will only need to configure LVGL to use the driver corresponding to your hardware (if it is other than the ST7789), and implement the function ``ui_init()`` to create your widgets.
.. note::
This example is not meant as the best possible implementation, or the recommended solution. It relies solely on the HAL drivers provided by ST Micro, which favor portability over performance. Despite of this the performance is very good, thanks to the efficient, DMA-based implementation of the drivers.
.. note::
Although the example uses FreeRTOS, this is not a strict requirement with the LVGL LCD display drivers.
You can find the source code snippets of this guide in the `lv_port_lcd_stm32_template.c <https://github.com/lvgl/lvgl/examples/porting/lv_port_lcd_stm32_template.c>`__ example.
Hardware configuration
----------------------
In this example we'll use the SPI1 peripheral to connect the microcontroller to the LCD panel. Besides the hardware-controlled SPI pins SCK and MOSI we need some additional output pins for the chip select, command/data select, and LCD reset:
==== ============= ======= ==========
pin configuration LCD user label
==== ============= ======= ==========
PA4 GPIO_Output CS LCD_CS
PA5 SPI1_SCK SCK --
PA7 SPI1_MOSI SDI --
PA15 GPIO_Output RESET LCD_RESET
PB10 GPIO_Output DC LCD_DCX
==== ============= ======= ==========
Step-by-step instructions
-------------------------
#. Create new project in File/New/STM32 Project.
#. Select target processor/board.
#. Set project name and location.
#. Set Targeted Project Type to STM32Cube and press Finish.
#. Say "Yes" to Initialize peripherals with their default Mode? After the project is created, the configuration file (.ioc) is opened automatically.
#. Switch to the Pinout & Configuration tab.
#. In the System Core category switch to RCC.
#. Set High Speed Clock to "BYPASS Clock Source", and Low Speed Clock to "Crystal/Ceramic Resonator".
#. In the System Core category select SYS, and set Timebase Source to other than SysTick (in our example, TIM2).
#. Switch to the Clock Configuration tab.
#. Set the HCLK clock frequency to the maximum value (216 MHz for the STM32F746).
#. Switch back to the Pinout & Configuration tab, and in the Middleware and Software Packs category select FREERTOS.
#. Select Interface: CMSIS_V1.
#. In the Advanced Settings tab enable USE_NEWLIB_REENTRANT. We are finished here.
#. In the Pinout view configure PA5 as SPI1_SCK, PA7 as SPI1_MOSI (right click the pin and select the function).
#. In the Pinout & Configuration/Connectivity category select SPI1.
#. Set Mode to Transmit Only Master, and Hardware NSS Signal to Disable.
#. In the Configuration subwindow switch to Parameter Settings.
#. Set Frame Format to Motorola, Data Size to 8 Bits, First Bit to MSB First.
#. Set the Prescaler to the maximum value according to the LCD controller's datasheet (e.g., 15 MBits/s). Set CPOL/CPHA as required (leave as default).
#. Set NSSP Mode to Disabled and NSS Signal Type to Software.
#. In DMA Settings add a new Request for SPI1_TX (when using SPI1).
#. Set Priority to Medium, Data Width to Half Word.
#. In NVIC Settings enable SPI1 global interrupt.
#. In GPIO Settings set SPI1_SCK to Pull-down and Very High output speed and set the User Label to ``LCD_SCK``.
#. Set SPI1_MOSI to Pull-up and Very High, and name it ``LCD_SDI``.
#. Select System Core/GPIO category. In the Pinout view configure additional pins for chip select, reset and command/data select. Name them ``LCD_CS``, ``LCD_RESET`` and ``LCD_DCX``, respectively. Configure them as GPIO Output. (In this example we will use PA4 for ``LCD_CS``, PA15 for ``LCD_RESET`` and PB10 for ``LCD_DCX``.)
#. Set ``LCD_CS`` to No pull-up and no pull-down, Low level and Very High speed.
#. Set ``LCD_RESET`` to Pull-up and High level.
#. Set ``LCD_DCX`` to No pull-up and no pull-down, High level and Very High speed.
#. Open the Project Manager tab, and select Advanced Settings. On the right hand side there is a Register Callback window. Select SPI and set it to ENABLE.
#. We are ready with the hardware configuration. Save the configuration and let STM32Cube generate the source.
#. In the project tree clone the LVGL repository into the Middlewares/Third_Party folder (this tutorial uses the release/v9.0 branch of LVGL):
.. code-block:: dosbatch
git clone https://github.com/lvgl/lvgl.git -b release/v9.0
#. Cloning should create an 'lvgl' subfolder inside the 'Third_Party' folder. From the 'lvgl' folder copy 'lv_conf_template.h' into the 'Middlewares' folder, and rename it to 'lv_conf.h'. Refresh the project tree.
#. Open 'lv_conf.h', and in line 15 change ``#if 0`` to ``#if 1``.
#. Search for the string ``LV_USE_ST7735``, and enable the appropriate LCD driver by setting its value to 1. This example uses the ST7789 driver:
.. code-block:: c
#define LV_USE_ST7789 1
#. Right click the folder 'Middlewares/Third_Party/lvgl/tests', select Resource Configurations/Exclude from Build..., check both Debug and Release, then press OK.
#. Right click the project name and select "Properties". In the C/C++ Build/Settings panel select MCU GCC Compiler/Include paths. In the Configuration dropdown select [ All configurations ]. Add the following Include path:
.. code-block:: c
../Middlewares/Third_Party/lvgl
#. Open Core/Src/stm32xxx_it.c (the file name depends on the processor variation). Add 'lv_tick.h' to the Private includes section:
.. code-block:: c
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "./src/tick/lv_tick.h"
/* USER CODE END Includes */
#. Find the function ``TIM2_IRQHandler``. Add a call to ``lv_tick_inc()``:
.. code-block:: c
void TIM2_IRQHandler(void)
{
/* USER CODE BEGIN TIM2_IRQn 0 */
/* USER CODE END TIM2_IRQn 0 */
HAL_TIM_IRQHandler(&htim2);
/* USER CODE BEGIN TIM2_IRQn 1 */
lv_tick_inc(1);
/* USER CODE END TIM2_IRQn 1 */
}
#. Save the file, then open Core/Src/main.c. Add the following lines to the Private includes (if your LCD uses other than the ST7789, replace the driver path and header with the appropriate one):
.. code-block:: c
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "lvgl.h"
#include "./src/drivers/display/st7789/lv_st7789.h"
/* USER CODE END Includes */
#. Add the following lines to Private defines (change them according to your LCD specs):
.. code-block:: c
#define LCD_H_RES 240
#define LCD_V_RES 320
#define BUS_SPI1_POLL_TIMEOUT 0x1000U
#. Add the following lines to the Private variables:
.. code-block:: c
osThreadId LvglTaskHandle;
lv_display_t *lcd_disp;
volatile int lcd_bus_busy = 0;
#. Add the following line to the Private function prototypes:
.. code-block:: c
void ui_init(lv_display_t *disp);
void LVGL_Task(void const *argument);
#. Add the following lines after USER CODE BEGIN RTOS_THREADS:
.. code-block:: c
osThreadDef(LvglTask, LVGL_Task, osPriorityIdle, 0, 1024);
LvglTaskHandle = osThreadCreate(osThread(LvglTask), NULL);
#. Copy and paste the hardware initialization and the transfer callback functions from the example code after USER CODE BEGIN 4:
.. code-block:: c
/* USER CODE BEGIN 4 */
void lcd_color_transfer_ready_cb(SPI_HandleTypeDef *hspi)
{
/* CS high */
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);
lcd_bus_busy = 0;
lv_display_flush_ready(lcd_disp);
}
/* Initialize LCD I/O bus, reset LCD */
static int32_t lcd_io_init(void)
{
/* Register SPI Tx Complete Callback */
HAL_SPI_RegisterCallback(&hspi1, HAL_SPI_TX_COMPLETE_CB_ID, lcd_color_transfer_ready_cb);
/* reset LCD */
HAL_GPIO_WritePin(LCD_RESET_GPIO_Port, LCD_RESET_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(LCD_RESET_GPIO_Port, LCD_RESET_Pin, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LCD_DCX_GPIO_Port, LCD_DCX_Pin, GPIO_PIN_SET);
return HAL_OK;
}
/* Platform-specific implementation of the LCD send command function. In general this should use polling transfer. */
static void lcd_send_cmd(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, const uint8_t *param, size_t param_size)
{
LV_UNUSED(disp);
while (lcd_bus_busy); /* wait until previous transfer is finished */
/* Set the SPI in 8-bit mode */
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
HAL_SPI_Init(&hspi1);
/* DCX low (command) */
HAL_GPIO_WritePin(LCD_DCX_GPIO_Port, LCD_DCX_Pin, GPIO_PIN_RESET);
/* CS low */
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET);
/* send command */
if (HAL_SPI_Transmit(&hspi1, cmd, cmd_size, BUS_SPI1_POLL_TIMEOUT) == HAL_OK) {
/* DCX high (data) */
HAL_GPIO_WritePin(LCD_DCX_GPIO_Port, LCD_DCX_Pin, GPIO_PIN_SET);
/* for short data blocks we use polling transfer */
HAL_SPI_Transmit(&hspi1, (uint8_t *)param, (uint16_t)param_size, BUS_SPI1_POLL_TIMEOUT);
/* CS high */
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET);
}
}
/* Platform-specific implementation of the LCD send color function. For better performance this should use DMA transfer.
* In case of a DMA transfer a callback must be installed to notify LVGL about the end of the transfer.
*/
static void lcd_send_color(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, uint8_t *param, size_t param_size)
{
LV_UNUSED(disp);
while (lcd_bus_busy); /* wait until previous transfer is finished */
/* Set the SPI in 8-bit mode */
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
HAL_SPI_Init(&hspi1);
/* DCX low (command) */
HAL_GPIO_WritePin(LCD_DCX_GPIO_Port, LCD_DCX_Pin, GPIO_PIN_RESET);
/* CS low */
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET);
/* send command */
if (HAL_SPI_Transmit(&hspi1, cmd, cmd_size, BUS_SPI1_POLL_TIMEOUT) == HAL_OK) {
/* DCX high (data) */
HAL_GPIO_WritePin(LCD_DCX_GPIO_Port, LCD_DCX_Pin, GPIO_PIN_SET);
/* for color data use DMA transfer */
/* Set the SPI in 16-bit mode to match endianness */
hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
HAL_SPI_Init(&hspi1);
lcd_bus_busy = 1;
HAL_SPI_Transmit_DMA(&hspi1, param, (uint16_t)param_size / 2);
/* NOTE: CS will be reset in the transfer ready callback */
}
}
#. Add the LVGL_Task() function. Replace the ``lv_st7789_create()`` call with the appropriate driver. You can change the default orientation by adjusting the parameter of ``lv_display_set_rotation()``. You will also need to create the display buffers here. This example uses a double buffering scheme with 1/10th size partial buffers. In most cases this is a good compromise between the required memory size and performance, but you are free to experiment with other settings.
.. code-block:: c
void LVGL_Task(void const *argument)
{
/* Initialize LVGL */
lv_init();
/* Initialize LCD I/O */
if (lcd_io_init() != 0)
return;
/* Create the LVGL display object and the LCD display driver */
lcd_disp = lv_st7789_create(LCD_H_RES, LCD_V_RES, LV_LCD_FLAG_NONE, lcd_send_cmd, lcd_send_color);
lv_display_set_rotation(lcd_disp, LV_DISPLAY_ROTATION_270);
/* Allocate draw buffers on the heap. In this example we use two partial buffers of 1/10th size of the screen */
lv_color_t * buf1 = NULL;
lv_color_t * buf2 = NULL;
uint32_t buf_size = LCD_H_RES * LCD_V_RES / 10 * lv_color_format_get_size(lv_display_get_color_format(lcd_disp));
buf1 = lv_malloc(buf_size);
if(buf1 == NULL) {
LV_LOG_ERROR("display draw buffer malloc failed");
return;
}
buf2 = lv_malloc(buf_size);
if(buf2 == NULL) {
LV_LOG_ERROR("display buffer malloc failed");
lv_free(buf1);
return;
}
lv_display_set_buffers(lcd_disp, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
ui_init(lcd_disp);
for(;;) {
/* The task running lv_timer_handler should have lower priority than that running `lv_tick_inc` */
lv_timer_handler();
/* raise the task priority of LVGL and/or reduce the handler period can improve the performance */
osDelay(10);
}
}
#. All that's left is to implement ``ui_init()`` to create the screen. Here's a simple "Hello World" example:
.. code-block:: c
void ui_init(lv_display_t *disp)
{
lv_obj_t *obj;
/* set screen background to white */
lv_obj_t *scr = lv_screen_active();
lv_obj_set_style_bg_color(scr, lv_color_white(), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_100, 0);
/* create label */
obj = lv_label_create(scr);
lv_obj_set_align(widget, LV_ALIGN_CENTER);
lv_obj_set_height(widget, LV_SIZE_CONTENT);
lv_obj_set_width(widget, LV_SIZE_CONTENT);
lv_obj_set_style_text_font(widget, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(widget, lv_color_black(), 0);
lv_label_set_text(widget, "Hello World!");
}

View File

@@ -0,0 +1,87 @@
.. _renesas_glcdc:
=============
Renesas GLCDC
=============
.. |sup2| unicode:: U+000B2 .. SUPERSCRIPT TWO
Overview
--------
.. image:: /_static/images/renesas/glcdc.png
:alt: Architectural overview of Renesas GLCDC
:align: center
<br/>
GLCDC is a multi-stage graphics output peripheral used in Renesas MCUs.
It is designed to automatically generate timing and data signals for different LCD panels.
- Supports LCD panels with RGB interface (up to 24 bits) and sync signals (HSYNC, VSYNC and Data Enable optional)
- Supports various color formats for input graphics planes (RGB888, ARGB8888, RGB565, ARGB1555, ARGB4444, CLUT8, CLUT4, CLUT1)
- Supports the Color Look-Up Table (CLUT) usage for input graphics planes (ARGB8888) with 512 words (32 bits/word)
- Supports various color formats for output (RGB888, RGB666, RGB565, Serial RGB888)
- Can input two graphics planes on top of the background plane and blend them on the screen
- Generates a dot clock to the panel. The clock source is selectable from internal or external (LCD_EXTCLK)
- Supports brightness adjustment, contrast adjustment, and gamma correction
- Supports GLCDC interrupts to handle frame-buffer switching or underflow detection
Setting up a project and further integration with Renesas' ecosystem is described in detail on :ref:`page Renesas <renesas>`.
Check out the following repositories for ready-to-use examples:
- `EK-RA8D1 <https://github.com/lvgl/lv_port_renesas_ek-ra8d1>`__
- `EK-RA6M3G <https://github.com/lvgl/lv_port_renesas_ek-ra6m3g>`__
- `RX72N Envision Kit <https://github.com/lvgl/lv_port_renesas_rx72n-envision-kit>`__
Prerequisites
-------------
- This diver relies on code generated by e\ |sup2| studio. Missing the step while setting up the project will cause a compilation error.
- Activate the diver by setting :c:macro:`LV_USE_RENESAS_GLCDC` to ``1`` in your *"lv_conf.h"*.
Usage
-----
There is no need to implement any platform-specific functions.
The following code demonstrates using the diver in :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT` mode.
.. code-block:: c
lv_display_t * disp = lv_renesas_glcdc_direct_create();
lv_display_set_default(disp);
To use the driver in :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` mode, an extra buffer must be allocated,
preferably in the fastest available memory region.
Buffer swapping can be activated by passing a second buffer of same size instead of the :cpp:expr:`NULL` argument.
.. code-block:: c
static lv_color_t partial_draw_buf[DISPLAY_HSIZE_INPUT0 * DISPLAY_VSIZE_INPUT0 / 10] BSP_PLACE_IN_SECTION(".sdram") BSP_ALIGN_VARIABLE(1024);
lv_display_t * disp = lv_renesas_glcdc_partial_create(partial_draw_buf, NULL, sizeof(partial_draw_buf));
lv_display_set_default(disp);
.. note::
Partial mode can be activated via the macro in ``src/board_init.c`` file of the demo projects.
Screen rotation
"""""""""""""""
Software based screen rotation is supported in partial mode. It uses the common API, no extra configuration is required:
.. code-block:: c
lv_display_set_rotation(lv_display_get_default(), LV_DISP_ROTATION_90);
/* OR */
lv_display_set_rotation(lv_display_get_default(), LV_DISP_ROTATION_180);
/* OR */
lv_display_set_rotation(lv_display_get_default(), LV_DISP_ROTATION_270);
Make sure the heap is large enough, as a buffer with the same size as the partial buffer will be allocated.

View File

@@ -0,0 +1,75 @@
============================
ST7735 LCD Controller driver
============================
Overview
--------
The `ST7735S <https://www.buydisplay.com/download/ic/ST7735S.pdf>`__ is a single-chip controller/driver for 262K-color, graphic type TFT-LCD. It consists of 396
source line and 162 gate line driving circuits. This chip is capable of connecting directly to an external
microprocessor, and accepts Serial Peripheral Interface (SPI), 8-bit/9-bit/16-bit/18-bit parallel interface.
Display data can be stored in the on-chip display data RAM of 132 x 162 x 18 bits. It can perform display data
RAM read/write operation with no external operation clock to minimize power consumption. In addition,
because of the integrated power supply circuits necessary to drive liquid crystal, it is possible to make a
display system with fewer components.
The ST7735 LCD controller `driver <https://github.com/lvgl/lvgl/src/drivers/display/st7735>`__ is a platform-agnostic driver, based on the `generic MIPI driver <https://github.com/lvgl/lvgl/doc/integration/drivers/display/gen_mipi.rst>`__.
It implements display initialization, supports display rotation and implements the display flush callback. The user needs to implement only two platform-specific functions to send
a command or pixel data to the controller via SPI or parallel bus. Typically these are implemented by calling the appropriate SDK library functions on the given platform.
Prerequisites
-------------
There are no prerequisites.
Configuring the driver
----------------------
Enable the ST7735 driver support in lv_conf.h, by cmake compiler define or by KConfig
.. code-block:: c
#define LV_USE_ST7735 1
Usage
-----
You need to implement two platform-dependent functions:
.. code-block:: c
/* Send short command to the LCD. This function shall wait until the transaction finishes. */
int32_t my_lcd_send_cmd(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, const uint8_t *param, size_t param_size)
{
...
}
/* Send large array of pixel data to the LCD. If necessary, this function has to do the byte-swapping. This function can do the transfer in the background. */
int32_t my_lcd_send_color(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, uint8_t *param, size_t param_size)
{
...
}
To create an ST7735-based display use the function
.. code-block:: c
/**
* Create an LCD display with ST7735 driver
* @param hor_res horizontal resolution
* @param ver_res vertical resolution
* @param flags default configuration settings (mirror, RGB ordering, etc.)
* @param send_cmd platform-dependent function to send a command to the LCD controller (usually uses polling transfer)
* @param send_color platform-dependent function to send pixel data to the LCD controller (usually uses DMA transfer: must implement a 'ready' callback)
* @return pointer to the created display
*/
lv_display_t * lv_st7735_create(uint32_t hor_res, uint32_t ver_res, lv_lcd_flag_t flags,
lv_st7735_send_cmd_cb_t send_cmd_cb, lv_st7735_send_color_cb_t send_color_cb);
For additional details and a working example see the `generic MIPI driver documentation <https://github.com/lvgl/lvgl/doc/integration/drivers/display/gen_mipi.rst>`__.
.. note::
You can find a step-by-step guide and the actual implementation of the callbacks on an STM32F746 using STM32CubeIDE and the ST HAL libraries here: :ref:`lcd_stm32_guide`

View File

@@ -0,0 +1,74 @@
============================
ST7789 LCD Controller driver
============================
Overview
--------
The `ST7789 <https://www.buydisplay.com/download/ic/ST7789.pdf>`__ is a single-chip controller/driver for 262K-color, graphic type TFT-LCD. It consists of 720
source line and 320 gate line driving circuits. This chip is capable of connecting directly to an external
microprocessor, and accepts, 8-bits/9-bits/16-bits/18-bits parallel interface. Display data can be stored in the
on-chip display data RAM of 240x320x18 bits. It can perform display data RAM read/write operation with no
external operation clock to minimize power consumption. In addition, because of the integrated power supply
circuit necessary to drive liquid crystal; it is possible to make a display system with the fewest components.
The ST7789 LCD controller `driver <https://github.com/lvgl/lvgl/src/drivers/display/st7789>`__ is a platform-agnostic driver, based on the `generic MIPI driver <https://github.com/lvgl/lvgl/doc/integration/drivers/display/gen_mipi.rst>`__.
It implements display initialization, supports display rotation and implements the display flush callback. The user needs to implement only two platform-specific functions to send
a command or pixel data to the controller via SPI or parallel bus. Typically these are implemented by calling the appropriate SDK library functions on the given platform.
Prerequisites
-------------
There are no prerequisites.
Configuring the driver
----------------------
Enable the ST7789 driver support in lv_conf.h, by cmake compiler define or by KConfig
.. code-block:: c
#define LV_USE_ST7789 1
Usage
-----
You need to implement two platform-dependent functions:
.. code-block:: c
/* Send short command to the LCD. This function shall wait until the transaction finishes. */
int32_t my_lcd_send_cmd(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, const uint8_t *param, size_t param_size)
{
...
}
/* Send large array of pixel data to the LCD. If necessary, this function has to do the byte-swapping. This function can do the transfer in the background. */
int32_t my_lcd_send_color(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, uint8_t *param, size_t param_size)
{
...
}
To create an ST7789-based display use the function
.. code-block:: c
/**
* Create an LCD display with ST7789 driver
* @param hor_res horizontal resolution
* @param ver_res vertical resolution
* @param flags default configuration settings (mirror, RGB ordering, etc.)
* @param send_cmd platform-dependent function to send a command to the LCD controller (usually uses polling transfer)
* @param send_color platform-dependent function to send pixel data to the LCD controller (usually uses DMA transfer: must implement a 'ready' callback)
* @return pointer to the created display
*/
lv_display_t * lv_st7789_create(uint32_t hor_res, uint32_t ver_res, lv_lcd_flag_t flags,
lv_st7789_send_cmd_cb_t send_cmd_cb, lv_st7789_send_color_cb_t send_color_cb);
For additional details and a working example see the `generic MIPI driver documentation <https://github.com/lvgl/lvgl/doc/integration/drivers/display/gen_mipi.rst>`__.
.. note::
You can find a step-by-step guide and the actual implementation of the callbacks on an STM32F746 using STM32CubeIDE and the ST HAL libraries here: :ref:`lcd_stm32_guide`

View File

@@ -0,0 +1,75 @@
============================
ST7796 LCD Controller driver
============================
Overview
--------
The `ST7796S <https://www.buydisplay.com/download/ic/ST7796S.pdf>`__ is a single-chip controller/driver for 262K-color, graphic type TFT-LCD. It consists of 960
source lines and 480 gate lines driving circuits. The ST7796S is capable of connecting directly to an external
microprocessor, and accepts 8-bit/9-bit/16-bit/18-bit parallel interface, SPI, and the ST7796S also provides
MIPI interface. Display data can be stored in the on-chip display data RAM of 320x480x18 bits. It can perform
display data RAM read-/write-operation with no external clock to minimize power consumption. In addition,
because of the integrated power supply circuit necessary to drive liquid crystal; it is possible to make a display
system with fewest components.
The ST7796 LCD controller `driver <https://github.com/lvgl/lvgl/src/drivers/display/st7796>`__ is a platform-agnostic driver, based on the `generic MIPI driver <https://github.com/lvgl/lvgl/doc/integration/drivers/display/gen_mipi.rst>`__.
It implements display initialization, supports display rotation and implements the display flush callback. The user needs to implement only two platform-specific functions to send
a command or pixel data to the controller via SPI or parallel bus. Typically these are implemented by calling the appropriate SDK library functions on the given platform.
Prerequisites
-------------
There are no prerequisites.
Configuring the driver
----------------------
Enable the ST7796 driver support in lv_conf.h, by cmake compiler define or by KConfig
.. code-block:: c
#define LV_USE_ST7796 1
Usage
-----
You need to implement two platform-dependent functions:
.. code-block:: c
/* Send short command to the LCD. This function shall wait until the transaction finishes. */
int32_t my_lcd_send_cmd(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, const uint8_t *param, size_t param_size)
{
...
}
/* Send large array of pixel data to the LCD. If necessary, this function has to do the byte-swapping. This function can do the transfer in the background. */
int32_t my_lcd_send_color(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, uint8_t *param, size_t param_size)
{
...
}
To create an ST7796-based display use the function
.. code-block:: c
/**
* Create an LCD display with ST7796 driver
* @param hor_res horizontal resolution
* @param ver_res vertical resolution
* @param flags default configuration settings (mirror, RGB ordering, etc.)
* @param send_cmd platform-dependent function to send a command to the LCD controller (usually uses polling transfer)
* @param send_color platform-dependent function to send pixel data to the LCD controller (usually uses DMA transfer: must implement a 'ready' callback)
* @return pointer to the created display
*/
lv_display_t * lv_st7796_create(uint32_t hor_res, uint32_t ver_res, lv_lcd_flag_t flags,
lv_st7796_send_cmd_cb_t send_cmd_cb, lv_st7796_send_color_cb_t send_color_cb);
For additional details and a working example see the `generic MIPI driver documentation <https://github.com/lvgl/lvgl/doc/integration/drivers/display/gen_mipi.rst>`__.
.. note::
You can find a step-by-step guide and the actual implementation of the callbacks on an STM32F746 using STM32CubeIDE and the ST HAL libraries here: :ref:`lcd_stm32_guide`

View File

@@ -0,0 +1,122 @@
.. _stm32 ltdc driver:
=================
STM32 LTDC Driver
=================
Some STM32s have a specialized peripheral for driving
displays called LTDC (LCD-TFT display controller).
Usage Modes With LVGL
*********************
The driver within LVGL is designed to work with an
already-configured LTDC peripheral. It relies on the
HAL to detect information about the configuration.
The color format of the created LVGL display will
match the LTDC layer's color format. Use STM32CubeIDE
or STM32CubeMX to generate LTDC initialization code.
There are some different use cases for LVGL's driver.
All permutations of the below options are well supported.
- single or double buffered
- direct or partial render mode
- OS and no OS
- parallelized flushing with DMA2D (only for partial render mode)
If OS is enabled, a synchronization primitive will be used to
give the thread a chance to yield to other threads while blocked,
improving CPU utilization. See :c:macro:`LV_USE_OS` in your lv_conf.h
LTDC Layers
***********
This driver creates an LVGL display
which is only concerned with a specific layer of the LTDC peripheral, meaning
two LVGL LTDC displays can be created and operate independently on the separate
layers.
Direct Render Mode
******************
For direct render mode, invoke :cpp:func:`lv_st_ltdc_create_direct` like this:
.. code-block:: c
void * my_ltdc_framebuffer_address = (void *)0x20000000u;
uint32_t my_ltdc_layer_index = 0; /* typically 0 or 1 */
lv_display_t * disp = lv_st_ltdc_create_direct(my_ltdc_framebuffer_address,
optional_other_full_size_buffer,
my_ltdc_layer_index);
``my_ltdc_framebuffer_address`` is the framebuffer configured for use by
LTDC. ``optional_other_full_size_buffer`` can be another buffer which is the same
size as the default framebuffer for double-buffered
mode, or ``NULL`` otherwise. ``my_ltdc_layer_index`` is the layer index of the
LTDC layer to create the display for.
For the best visial results, ``optional_other_full_size_buffer`` should be used
if enough memory is available. Single-buffered mode is what you should use
if memory is very scarce. If there is almost enough memory for double-buffered
direct mode, but not quite, then use partial render mode.
Partial Render Mode
*******************
For partial render mode, invoke :cpp:func:`lv_st_ltdc_create_partial` like this:
.. code-block:: c
static uint8_t partial_buf1[65536];
static uint8_t optional_partial_buf2[65536];
uint32_t my_ltdc_layer_index = 0; /* typically 0 or 1 */
lv_display_t * disp = lv_st_ltdc_create_partial(partial_buf1,
optional_partial_buf2,
65536,
my_ltdc_layer_index);
The driver will use the information in the LTDC layer configuration to find the
layer's framebuffer and flush to it.
Providing a second partial buffer can improve CPU utilization and increase
performance compared to
a single buffer if :c:macro:`LV_ST_LTDC_USE_DMA2D_FLUSH` is enabled.
Display Rotation
****************
The driver supports display rotation with
:cpp:expr:`lv_display_set_rotation(disp, rotation)` where rotation is one of
:cpp:enumerator:`LV_DISP_ROTATION_90`, :cpp:enumerator:`LV_DISP_ROTATION_180`,
or :cpp:enumerator:`LV_DISP_ROTATION_270`. The rotation is initially
:cpp:enumerator:`LV_DISP_ROTATION_0`.
The rotation is done in software and only works if the display was
created using :cpp:func:`lv_st_ltdc_create_partial`.
:c:macro:`LV_ST_LTDC_USE_DMA2D_FLUSH` will be have no effect if rotation
is used.
DMA2D
*****
:c:macro:`LV_ST_LTDC_USE_DMA2D_FLUSH` can be enabled to use DMA2D to flush
partial buffers in parallel with other LVGL tasks, whether or not OS is
enabled. If the display is not partial, then there is no need to enable this
option.
Additionally it is possible to mix layers that have color format on
:c:macro:`LV_COLOR_FORMAT_ARGB1555` on top of :c:macro:`LV_COLOR_FORMAT_RGB565`
layers using the DMA2D.
It must not be enabled at the same time as :c:macro:`LV_USE_DRAW_DMA2D`.
See the :ref:`DMA2D support <dma2d>`.
.. admonition:: Further Reading
You may be interested in enabling the :ref:`Nema GFX renderer <nema_gfx>`
if your STM32 has a NeoChrom GPU.
`lv_port_riverdi_stm32u5 <https://github.com/lvgl/lv_port_riverdi_stm32u5>`__
is a way to quick way to get started with LTDC on LVGL.

View File

@@ -0,0 +1,18 @@
.. _drivers:
=======
Drivers
=======
.. toctree::
:maxdepth: 2
display/index
libinput
opengles
touchpad/index
wayland
windows
X11
uefi
sdl

View File

@@ -0,0 +1,87 @@
===============
Libinput Driver
===============
Overview
--------
Libinput is an input stack for processes that need to provide events from commonly used input devices. That includes mice, keyboards, touchpads,
touchscreens and graphics tablets. Libinput handles device-specific quirks and provides an easy-to-use API to receive events from devices.
Prerequisites
-------------
You have the development version of libinput installed (usually ``libinput-dev``). If your input device requires quirks, make sure they are
installed as well (usually in ``/usr/share/libinput/*.quirks``). To test if your device is set up correctly for use with libinput, you can
run ``libinput list-devices``.
.. code-block:: console
$ sudo libinput list-devices
...
Device: ETPS/2 Elantech Touchpad
Kernel: /dev/input/event5
Group: 10
Seat: seat0, default
Size: 102x74mm
Capabilities: pointer gesture
Tap-to-click: disabled
Tap-and-drag: enabled
...
If your device doesn't show up, you may have to configure udev and the appropriate udev rules to connect it.
Additionally, if you want full keyboard support, including letters and modifiers, you'll need the development version of libxkbcommon
installed (usually ``libxkbcommon-dev``).
Configuring the driver
----------------------
Enable the libinput driver support in lv_conf.h, by cmake compiler define or by KConfig.
.. code-block:: c
#define LV_USE_LIBINPUT 1
Full keyboard support needs to be enabled separately.
.. code-block:: c
#define LV_LIBINPUT_XKB 1
#define LV_LIBINPUT_XKB_KEY_MAP { .rules = NULL, .model = "pc101", .layout = "us", .variant = NULL, .options = NULL }
To find the right key map values, you may use the ``setxkbmap -query`` command.
Usage
-----
To set up an input device via the libinput driver, all you need to do is call ``lv_libinput_create`` with the respective device type
(``LV_INDEV_TYPE_POINTER`` or ``LV_INDEV_TYPE_KEYPAD``) and device node path (e.g. ``/dev/input/event5``).
.. code-block:: c
lv_indev_t *indev = lv_libinput_create(LV_INDEV_TYPE_POINTER, "/dev/input/event5");
Note that touchscreens are treated as (absolute) pointer devices by the libinput driver and require ``LV_INDEV_TYPE_POINTER``.
Depending on your system, the device node paths might not be stable across reboots. If this is the case, you can use ``lv_libinput_find_dev``
to find the first device that has a specific capability.
.. code-block:: c
char *path = lv_libinput_find_dev(LV_LIBINPUT_CAPABILITY_TOUCH, true);
The second argument controls whether or not all devices are rescanned. If you have many devices connected this can get quite slow.
Therefore, you should only specify ``true`` on the first call when calling this method multiple times in a row. If you want to find
all devices that have a specific capability, use ``lv_libinput_find_devs``.
If you want to connect a keyboard device to a textarea, create a dedicated input group and set it on both the indev and textarea.
.. code-block:: c
lv_obj_t *textarea = lv_textarea_create(...);
...
lv_group_t *keyboard_input_group = lv_group_create();
lv_indev_set_group(indev, keyboard_input_group);
lv_group_add_obj(keyboard_input_group, textarea);

Some files were not shown because too many files have changed in this diff Show More