feat(obj_name): add auto-indexing with names like 'mybtn_#'

This commit is contained in:
Gabor Kiss-Vamosi
2025-05-11 08:11:00 +02:00
committed by Liam Howatt
parent 5652914e14
commit e4bbc4f0fa
10 changed files with 382 additions and 35 deletions
+35
View File
@@ -0,0 +1,35 @@
diff --git a/src/draw/opengles/lv_draw_opengles.c b/src/draw/opengles/lv_draw_opengles.c
index b3f2122a7..bfb167bc9 100644
--- a/src/draw/opengles/lv_draw_opengles.c
+++ b/src/draw/opengles/lv_draw_opengles.c
@@ -275,6 +275,10 @@ static bool draw_to_texture(lv_draw_opengles_unit_t * u, cache_data_t * cache_da
lv_obj_remove_flag(obj, LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS);
}
+ lv_draw_dsc_base_t * base_dsc = task->draw_dsc;
+ cache_data->draw_dsc = lv_malloc(base_dsc->dsc_size);
+ lv_memcpy((void *)cache_data->draw_dsc, base_dsc, base_dsc->dsc_size);
+
switch(task->type) {
case LV_DRAW_TASK_TYPE_FILL: {
lv_draw_fill_dsc_t * fill_dsc = task->draw_dsc;
@@ -355,6 +359,8 @@ static bool draw_to_texture(lv_draw_opengles_unit_t * u, cache_data_t * cache_da
break;
}
default:
+ /*The malloced cache_data->draw_dsc will be freed automatically on failure
+ *in opengles_texture_cache_free_cb*/
return false;
}
@@ -367,10 +373,7 @@ static bool draw_to_texture(lv_draw_opengles_unit_t * u, cache_data_t * cache_da
unsigned int texture = create_texture(texture_w, texture_h, u->render_draw_buf.data);
- lv_draw_dsc_base_t * base_dsc = task->draw_dsc;
- cache_data->draw_dsc = lv_malloc(base_dsc->dsc_size);
- lv_memcpy((void *)cache_data->draw_dsc, base_dsc, base_dsc->dsc_size);
cache_data->w = texture_w;
cache_data->h = texture_h;
cache_data->texture = texture;
@@ -577,9 +577,87 @@ more on encoder behaviors and the edit mode.
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:
+57 -5
View File
@@ -464,13 +464,65 @@ void lv_obj_get_name_resolved(const lv_obj_t * obj, char buf[], size_t buf_size)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
if(obj->spec_attr && obj->spec_attr->name) {
lv_strlcpy(buf, obj->spec_attr->name, buf_size);
const char * name = lv_obj_get_name(obj);
/*Use a default name which auto-indexing*/
char name_buf[LV_OBJ_NAME_MAX_LEN];
if(name == NULL) {
lv_snprintf(name_buf, sizeof(name_buf), "%s_#", obj->class_p->name);
name = name_buf;
}
size_t name_len = lv_strlen(name);
lv_obj_t * parent = lv_obj_get_parent(obj);
/*If the last character is # automatically index the children with the same name start*/
if(parent && name_len > 0 && name[name_len - 1] == '#') {
uint32_t child_cnt = lv_obj_get_child_count(parent);
uint32_t cnt = 0;
uint32_t i;
for(i = 0; i < child_cnt; i++) {
lv_obj_t * child = lv_obj_get_child(parent, i);
/*All siblings older siblings are checked, craft the name of this widget*/
if(child == obj) {
char num_buf[8];
size_t num_len;
num_len = lv_snprintf(num_buf, sizeof(num_buf), "%d", cnt);
/*Is there enough space for the name and the index?*/
if(buf_size > name_len + num_len) {
/*E.g. buf = "some_name_", so trim the # from the end*/
lv_strncpy(buf, name, name_len - 1);
lv_strcpy(&buf[name_len - 1], num_buf);
}
else {
/*Use the name as it is as a fallback*/
lv_strlcpy(buf, obj->spec_attr->name, buf_size);
}
break;
}
/*Check the older siblings. IF they start with the same name count them*/
else {
const char * child_name = lv_obj_get_name(child);
if(child_name == NULL) {
/*If the name we are looking for start with the child's class name
*increment the index. E.g. <class_name>_#*/
size_t class_name_len = lv_strlen(child->class_p->name);
if(name_len > 3 && class_name_len == name_len - 2 &&
lv_strncmp(child->class_p->name, name, class_name_len) == 0) {
cnt++;
}
}
/*The name is set, check if it's e.g. <some_name>#*/
else {
if(lv_strcmp(child->spec_attr->name, name) == 0) {
cnt++;
}
}
}
}
}
/*Craft a name if not set. E.g. "lv_button_1"*/
else {
uint32_t idx = lv_obj_get_index_by_type(obj, obj->class_p);
lv_snprintf(buf, buf_size, "%s_%"LV_PRIu32, obj->class_p->name, idx);
/*Just use the set name*/
lv_strlcpy(buf, obj->spec_attr->name, buf_size);
}
}
+29 -10
View File
@@ -196,36 +196,55 @@ uint32_t lv_obj_get_child_count_by_type(const lv_obj_t * obj, const lv_obj_class
#if LV_USE_OBJ_NAME
/**
* Set a name for a widget. The name will be allocated.
* Set a name for a widget. The name will be allocated and freed when the
* widget is deleted or a new name is set.
* @param obj pointer to an object
* @param name the name to set
* @param name the name to set. If set to `NULL` the default "<widget_type>_#"
* name will be used.
* @note If the name ends with a `#`, older siblings with the same name
* will be counted, and the `#` will be replaced by the index of the
* given widget. For example, creating multiple widgets with the name
* "mybtn_#" will result in resolved names like "mybtn_0", "mybtn_1",
* "mybtn_2", etc. The name is resolved when `lv_obj_get_name_resolved`
* is called, so the result reflects the currently existing widgets at
* that time.
*/
void lv_obj_set_name(lv_obj_t * obj, const char * name);
/**
* Set a name for a widget. Only a pointer will be saved.
* @param obj pointer to an object
* @param name the name to set
* @param name the name to set. If set to `NULL` the default "<widget_type>_#"
* name will be used.
* @note If the name ends with a `#`, older siblings with the same name
* will be counted, and the `#` will be replaced by the index of the
* given widget. For example, creating multiple widgets with the name
* "mybtn_#" will result in resolved names like "mybtn_0", "mybtn_1",
* "mybtn_2", etc. The name is resolved when `lv_obj_get_name_resolved`
* is called, so the result reflects the currently existing widgets at
* that time.
*/
void lv_obj_set_name_static(lv_obj_t * obj, const char * name);
/**
* Get the set name as it was set
* Get the set name as it was set.
* @param obj pointer to an object
* @return get the set name or NULL if it wasn't set yet
*/
const char * lv_obj_get_name(const lv_obj_t * obj);
/**
* Get the set name or craft a name automatically if there is no set name.
* The crafted names are built like <widget type> + "_" + <index of the given type>
* For example if a parent has two button and two label children the names will be
* "lv_button_0", "lv_button1", "lv_label_0", "lv_label_1"
* The <widget name> comes from the `class->name` field.
* The index is 0 based.
* Get the set name or craft a name automatically.
* @param obj pointer to an object
* @param buf buffer to store the name
* @param buf_size the size of the buffer in bytes
* @note If the name ends with a `#`, older siblings with the same name
* will be counted, and the `#` will be replaced by the index of the
* given widget. For example, creating multiple widgets with the name
* "mybtn_#" will result in resolved names like "mybtn_0", "mybtn_1",
* "mybtn_2", etc. The name is resolved when `lv_obj_get_name_resolved`
* is called, so the result reflects the currently existing widgets at
* that time.
*/
void lv_obj_get_name_resolved(const lv_obj_t * obj, char buf[], size_t buf_size);
-1
View File
@@ -40,7 +40,6 @@
#include "parsers/lv_xml_canvas_parser.h"
#include "parsers/lv_xml_calendar_parser.h"
#include "parsers/lv_xml_event_parser.h"
#include "parsers/lv_xml_update_parser.h"
#include "../../libs/expat/expat.h"
#include "../../draw/lv_draw_image.h"
+13
View File
@@ -93,6 +93,19 @@ lv_obj_t * lv_xml_component_process(lv_xml_parser_state_t * state, const char *
lv_widget_processor_t * extended_proc = lv_xml_widget_get_extended_widget_processor(ctx->extends);
extended_proc->apply_cb(state, attrs);
#if LV_USE_OBJ_NAME
/*Set a default indexed name*/
if(state->item) {
const char * value_of_name = lv_xml_get_value_of(attrs, "name");
if(value_of_name) lv_obj_set_name(item, value_of_name);
else {
char name_buf[128];
lv_snprintf(name_buf, sizeof(name_buf), "%s_#", scope->name);
lv_obj_set_name(state->item, name_buf);
}
}
#endif
return item;
}
+3 -1
View File
@@ -61,7 +61,9 @@ void lv_xml_obj_apply(lv_xml_parser_state_t * state, const char ** attrs)
size_t name_len = lv_strlen(name);
#if LV_USE_OBJ_NAME
if(lv_streq("name", name)) lv_obj_set_name(item, value);
if(lv_streq("name", name)) {
lv_obj_set_name(item, value);
}
#endif
if(lv_streq("x", name)) lv_obj_set_x(item, lv_xml_to_size(value));
else if(lv_streq("y", name)) lv_obj_set_y(item, lv_xml_to_size(value));
+43 -2
View File
@@ -209,6 +209,8 @@ void test_obj_get_by_name(void)
lv_obj_t * cont3 = lv_obj_create(lv_screen_active());
lv_obj_set_name_static(cont3, "third_static");
lv_obj_set_name_static(cont3, "third");
lv_obj_t * cont4 = lv_obj_create(lv_screen_active());
lv_obj_t * root_label = lv_label_create(lv_screen_active());
lv_label_set_text(root_label, "Root");
@@ -223,6 +225,19 @@ void test_obj_get_by_name(void)
lv_label_set_text(hello_label, "Hello");
lv_obj_set_name(hello_label, "my_label"); /*Same name as ofr the other label*/
/*Test auto indexing*/
lv_obj_t * label0 = lv_label_create(cont3);
lv_obj_set_name(label0, "title_#");
lv_obj_t * label1 = lv_label_create(cont3);
lv_obj_set_name(label1, "title_#");
lv_obj_t * label2 = lv_label_create(cont3); //lv_label_0
lv_obj_t * label3 = lv_label_create(cont3);
lv_obj_set_name(label3, "title_#?");
lv_obj_t * label4 = lv_label_create(cont3); //lv_label_1
lv_obj_t * label5 = lv_label_create(cont3);
lv_obj_set_name(label5, "title_#");
lv_obj_t * found_obj;
@@ -233,9 +248,12 @@ void test_obj_get_by_name(void)
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "first");
TEST_ASSERT_EQUAL(cont1, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "lv_obj_2");
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "lv_obj_0");
TEST_ASSERT_EQUAL(cont2, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "lv_obj_1");
TEST_ASSERT_EQUAL(cont4, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "fifth");
TEST_ASSERT_EQUAL(NULL, found_obj);
@@ -258,11 +276,30 @@ void test_obj_get_by_name(void)
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "");
TEST_ASSERT_EQUAL(NULL, found_obj);
/* Test auto indexed names*/
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "third/title_0");
TEST_ASSERT_EQUAL(label0, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "third/title_1");
TEST_ASSERT_EQUAL(label1, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "third/lv_label_0");
TEST_ASSERT_EQUAL(label2, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "third/title_#?");
TEST_ASSERT_EQUAL(label3, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "third/lv_label_1");
TEST_ASSERT_EQUAL(label4, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "third/title_2");
TEST_ASSERT_EQUAL(label5, found_obj);
/*-------------
* Find by name
*------------*/
found_obj = lv_obj_find_by_name(lv_screen_active(), "lv_obj_2");
found_obj = lv_obj_find_by_name(lv_screen_active(), "lv_obj_0");
TEST_ASSERT_EQUAL(cont2, found_obj);
found_obj = lv_obj_find_by_name(lv_screen_active(), "my_label");
@@ -270,6 +307,10 @@ void test_obj_get_by_name(void)
found_obj = lv_obj_find_by_name(cont1, "my_label");
TEST_ASSERT_EQUAL(hello_label, found_obj);
found_obj = lv_obj_find_by_name(lv_screen_active(), "title_2");
TEST_ASSERT_EQUAL(label5, found_obj);
}
#endif
+17 -16
View File
@@ -228,22 +228,23 @@ void test_xml_error_resilience_syntax_ok(void)
{
const char * my_btn_xml =
"<component>"
"<consts>"
"<int name=\"abc\" value=\"0xff0000\"/>"
"<not_a_type name=\"xyz\" value=\"0xff0000\"/>"
"<int not_a_prop=\"abc\" value=\"0xff0000\"/>"
"<int name=\"abc\" not_a_value=\"0xff0000\"/>"
"</consts>"
"<styles>"
"<style name=\"rel_style\" bg_color=\"0xff0000\" not_a_prop=\"0xff0000\"/>"
"<inv_style name=\"rel_style\" bg_color=\"0x800000\"/>"
"<style bg_color=\"0x800000\"/>"
"</styles>"
"<view extends=\"not_a_widget\" style_text_color=\"0x0000ff\" style_text_color:checked=\"0x8080ff\" styles=\"rel_style pr_style:checked\">"
"<unknown/>"
"<lv_label not_an_attr=\"40\"/>"
"</view>"
" <consts>"
" <int name=\"abc\" value=\"0xff0000\"/>"
" <not_a_type name=\"xyz\" value=\"0xff0000\"/>"
" <int not_a_prop=\"abc\" value=\"0xff0000\"/>"
" <int name=\"abc\" not_a_value=\"0xff0000\"/>"
" </consts>"
""
" <styles>"
" <style name=\"rel_style\" bg_color=\"0xff0000\" not_a_prop=\"0xff0000\"/>"
" <inv_style name=\"rel_style\" bg_color=\"0x800000\"/>"
" <style bg_color=\"0x800000\"/>"
" </styles>"
""
" <view extends=\"not_a_widget\" style_text_color=\"0x0000ff\" style_text_color:checked=\"0x8080ff\" styles=\"rel_style pr_style:checked\">"
" <unknown/>"
" <lv_label not_an_attr=\"40\"/>"
" </view>"
"</component>";
lv_xml_component_register_from_data("my_btn", my_btn_xml);
+107
View File
@@ -0,0 +1,107 @@
#if LV_BUILD_TEST || 1
#include "../lvgl.h"
#include "unity/unity.h"
void setUp(void)
{
/* Function run before every test */
}
void tearDown(void)
{
/* Function run after every test */
lv_obj_clean(lv_screen_active());
}
void test_xml_names(void)
{
const char * base_label_xml =
"<component>"
" <view extends=\"lv_label\"></view>"
"</component>";
const char * my_label_xml =
"<component>"
" <view extends=\"base_label\"></view>"
"</component>";
const char * my_btn_xml =
"<component>"
" <view extends=\"lv_button\">"
" <my_label name=\"first_label\"/>"
" <my_label/>"
" <my_label name=\"third_label\"/>"
" <my_label/>"
" </view>"
"</component>";
const char * main_screen_xml =
"<screen>"
" <view name=\"main_screen\">"
" <my_btn/>"
" <my_btn name=\"first_btn\"/>"
" <lv_button/>"
" <my_btn/>"
" <my_btn name=\"lv_button_#\"/>"
" <lv_button/>"
" </view>"
"</screen>";
lv_xml_component_register_from_data("my_btn", my_btn_xml);
lv_xml_component_register_from_data("base_label", base_label_xml);
lv_xml_component_register_from_data("my_label", my_label_xml);
lv_xml_component_register_from_data("main_screen", main_screen_xml);
lv_obj_t * main_screen = lv_xml_create(NULL, "main_screen", NULL);
char buf[128];
lv_obj_t * btn;
btn = lv_obj_get_child(main_screen, 0);
lv_obj_get_name_resolved(btn, buf, 128);
TEST_ASSERT_EQUAL_STRING("my_btn_0", buf);
btn = lv_obj_get_child(main_screen, 1);
lv_obj_get_name_resolved(btn, buf, 128);
TEST_ASSERT_EQUAL_STRING("first_btn", buf);
btn = lv_obj_get_child(main_screen, 2);
lv_obj_get_name_resolved(btn, buf, 128);
TEST_ASSERT_EQUAL_STRING("lv_button_0", buf);
btn = lv_obj_get_child(main_screen, 3);
lv_obj_get_name_resolved(btn, buf, 128);
TEST_ASSERT_EQUAL_STRING("my_btn_1", buf);
btn = lv_obj_get_child(main_screen, 4);
lv_obj_get_name_resolved(btn, buf, 128);
TEST_ASSERT_EQUAL_STRING("lv_button_1", buf);
btn = lv_obj_get_child(main_screen, 5);
lv_obj_get_name_resolved(btn, buf, 128);
TEST_ASSERT_EQUAL_STRING("lv_button_2", buf);
btn = lv_obj_get_child(main_screen, 3);
lv_obj_t * label;
label = lv_obj_get_child(btn, 0);
lv_obj_get_name_resolved(label, buf, 128);
TEST_ASSERT_EQUAL_STRING("first_label", buf);
label = lv_obj_get_child(btn, 1);
lv_obj_get_name_resolved(label, buf, 128);
TEST_ASSERT_EQUAL_STRING("my_label_0", buf);
label = lv_obj_get_child(btn, 2);
lv_obj_get_name_resolved(label, buf, 128);
TEST_ASSERT_EQUAL_STRING("third_label", buf);
label = lv_obj_get_child(btn, 3);
lv_obj_get_name_resolved(label, buf, 128);
TEST_ASSERT_EQUAL_STRING("my_label_1", buf);
}
#endif