feat(obj): add obj name support

fixes #7476

Co-authored-by: Liam Howatt <30486941+liamHowatt@users.noreply.github.com>
Co-authored-by: André Costa <andre_miguel_costa@hotmail.com>
This commit is contained in:
Gabor Kiss-Vamosi
2025-02-13 12:25:03 +01:00
committed by Liam Howatt
parent 6d999331d7
commit 626d6b51ad
10 changed files with 320 additions and 4 deletions

View File

@@ -716,6 +716,10 @@ menu "LVGL configuration"
bool "Add id field to obj"
default n
config LV_USE_OBJ_NAME
bool "Enable support widget names"
default n
config LV_OBJ_ID_AUTO_ASSIGN
bool "Automatically assign an ID when obj is created"
default y

View File

@@ -463,6 +463,9 @@
/** Add `id` field to `lv_obj_t` */
#define LV_USE_OBJ_ID 0
/** Enable support widget names*/
#define LV_USE_OBJ_NAME 0
/** Automatically assign an ID when obj is created */
#define LV_OBJ_ID_AUTO_ASSIGN LV_USE_OBJ_ID

View File

@@ -187,7 +187,7 @@ const lv_obj_class_t lv_obj_class = {
.group_def = LV_OBJ_CLASS_GROUP_DEF_FALSE,
.instance_size = (sizeof(lv_obj_t)),
.base_class = NULL,
.name = "obj",
.name = "lv_obj",
#if LV_USE_OBJ_PROPERTY
.prop_index_start = LV_PROPERTY_OBJ_START,
.prop_index_end = LV_PROPERTY_OBJ_END,
@@ -438,6 +438,8 @@ void * lv_obj_get_id(const lv_obj_t * obj)
lv_obj_t * lv_obj_get_child_by_id(const lv_obj_t * obj, const void * id)
{
LV_LOG_WARN("DEPRECATED: IDs are used only to print the widget trees. To find a widget use obj_name");
if(obj == NULL) obj = lv_display_get_screen_active(NULL);
if(obj == NULL) return NULL;
@@ -533,6 +535,11 @@ static void lv_obj_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
}
lv_event_remove_all(&obj->spec_attr->event_list);
#if LV_USE_OBJ_NAME
if(obj->spec_attr->name && !obj->spec_attr->name_static) {
lv_free((void *)obj->spec_attr->name);
}
#endif
#if LV_DRAW_TRANSFORM_USE_MATRIX
if(obj->spec_attr->matrix) {

View File

@@ -35,7 +35,9 @@ struct _lv_obj_spec_attr_t {
lv_matrix_t * matrix; /**< The transform matrix*/
#endif
lv_event_list_t event_list;
#if LV_USE_OBJ_NAME
const char * name; /**< Pointer to the name */
#endif
lv_point_t scroll; /**< The current X/Y scroll offset*/
int32_t ext_click_pad; /**< Extra click padding in all direction*/
@@ -47,6 +49,7 @@ struct _lv_obj_spec_attr_t {
uint16_t scroll_snap_y : 2; /**< Where to align the snappable children vertically*/
uint16_t scroll_dir : 4; /**< The allowed scroll direction(s), see `lv_dir_t`*/
uint16_t layer_type : 2; /**< Cache the layer type here. Element of lv_intermediate_layer_type_t */
uint16_t name_static : 1; /**< 1: `name` was not dynamically allocated */
};
struct _lv_obj_t {

View File

@@ -23,6 +23,7 @@
#define disp_ll_p &(LV_GLOBAL_DEFAULT()->disp_ll)
#define OBJ_DUMP_STRING_LEN 128
#define LV_OBJ_NAME_MAX_LEN 128
/**********************
* TYPEDEFS
@@ -36,6 +37,9 @@ static void obj_delete_core(lv_obj_t * obj);
static lv_obj_tree_walk_res_t walk_core(lv_obj_t * obj, lv_obj_tree_walk_cb_t cb, void * user_data);
static void dump_tree_core(lv_obj_t * obj, int32_t depth);
static lv_obj_t * lv_obj_get_first_not_deleting_child(lv_obj_t * obj);
#if LV_USE_OBJ_NAME
static lv_obj_t * find_by_name_direct(const lv_obj_t * parent, const char * name, size_t len);
#endif /*LV_USE_OBJ_NAME*/
/**********************
* STATIC VARIABLES
@@ -416,6 +420,116 @@ uint32_t lv_obj_get_child_count_by_type(const lv_obj_t * obj, const lv_obj_class
return cnt;
}
#if LV_USE_OBJ_NAME
void lv_obj_set_name(lv_obj_t * obj, const char * name)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_obj_allocate_spec_attr(obj);
if(!obj->spec_attr->name_static && obj->spec_attr->name) lv_free((void *)obj->spec_attr->name);
if(name == NULL) {
obj->spec_attr->name = NULL;
obj->spec_attr->name_static = 1;
}
else {
obj->spec_attr->name = lv_strdup(name);
obj->spec_attr->name_static = 0;
}
}
void lv_obj_set_name_static(lv_obj_t * obj, const char * name)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_obj_allocate_spec_attr(obj);
if(!obj->spec_attr->name_static && obj->spec_attr->name) lv_free((void *)obj->spec_attr->name);
obj->spec_attr->name = name;
obj->spec_attr->name_static = 1;
}
const char * lv_obj_get_name(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
if(obj->spec_attr == NULL) return NULL;
else return obj->spec_attr->name;
}
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);
}
/*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);
}
}
lv_obj_t * lv_obj_get_child_by_name(const lv_obj_t * parent, const char * path)
{
LV_ASSERT_OBJ(parent, MY_CLASS);
if(parent == NULL || parent->spec_attr == NULL || path == NULL) return NULL;
while(*path) {
const char * segment = path;
uint32_t len = 0;
/* Calculate the length of the current segment */
while(path[len] && path[len] != '/')
len++;
/* Look for a child whose resolved name exactly matches the segment */
lv_obj_t * child = find_by_name_direct(parent, segment, len);
if(!child) return NULL; /*Segment not found*/
/* Advance to the next segment */
path += len;
if(*path == '/') path++; /* Skip the '/' */
/* If there is no further segment, we've found the target child */
if(*path == '\0') return child;
parent = child;
}
return NULL;
}
lv_obj_t * lv_obj_find_by_name(const lv_obj_t * parent, const char * name)
{
LV_ASSERT_OBJ(parent, MY_CLASS);
if(parent == NULL) parent = lv_display_get_screen_active(NULL);
if(parent == NULL) return NULL;
lv_obj_t * child = find_by_name_direct(parent, name, UINT16_MAX);
if(child) return child;
/*Search children recursively*/
uint32_t child_cnt = lv_obj_get_child_count(parent);
uint32_t i;
for(i = 0; i < child_cnt; i++) {
child = parent->spec_attr->children[i];
lv_obj_t * found = lv_obj_find_by_name(child, name);
if(found != NULL) return found;
}
return NULL;
}
#endif /*LV_USE_OBJ_NAME*/
int32_t lv_obj_get_index(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
@@ -660,3 +774,22 @@ static lv_obj_t * lv_obj_get_first_not_deleting_child(lv_obj_t * obj)
return NULL;
}
#if LV_USE_OBJ_NAME
static lv_obj_t * find_by_name_direct(const lv_obj_t * parent, const char * name, size_t len)
{
uint32_t i;
uint32_t child_cnt = lv_obj_get_child_count(parent);
for(i = 0; i < child_cnt; i++) {
lv_obj_t * child = parent->spec_attr->children[i];
char child_name_resolved[LV_OBJ_NAME_MAX_LEN];
lv_obj_get_name_resolved(child, child_name_resolved, sizeof(child_name_resolved));
if(lv_strncmp(child_name_resolved, name, len) == 0) return child;
}
return NULL;
}
#endif /*LV_USE_OBJ_NAME*/

View File

@@ -193,6 +193,71 @@ uint32_t lv_obj_get_child_count(const lv_obj_t * obj);
uint32_t lv_obj_get_child_count_by_type(const lv_obj_t * obj, const lv_obj_class_t * class_p);
#if LV_USE_OBJ_NAME
/**
* Set a name for a widget. The name will be allocated.
* @param obj pointer to an object
* @param name the name to set
*/
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
*/
void lv_obj_set_name_static(lv_obj_t * obj, const char * name);
/**
* 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.
* @param obj pointer to an object
* @param buf buffer to store the name
* @param buf_size the size of the buffer in bytes
*/
void lv_obj_get_name_resolved(const lv_obj_t * obj, char buf[], size_t buf_size);
/**
* Find a child with a given name on a parent. This child doesn't have to be the
* direct child of the parent. First direct children of the parent will be checked,
* and the direct children of the first child, etc. (Breadth-first search).
*
* If the name of a widget was not set a name like "lv_button_1" will
* be created for it using `lv_obj_get_name_resolved`.
*
* @param parent the widget where the search should start
* @return the found widget or NULL if not found.
*/
lv_obj_t * lv_obj_find_by_name(const lv_obj_t * parent, const char * name);
/**
* Get an object by name. The name can be a path too, for example
* "main_container/lv_button_1/label".
* In this case the first part of the name-path should be the direct child of the parent,
* the second part, should the direct child of first one, etc.
*
* If the name of a widget was not set a name like "lv_button_1" will
* be created for it using `lv_obj_get_name_resolved`.
*
* @param parent the widget where the search should start
* @return the found widget or NULL if not found.
*/
lv_obj_t * lv_obj_get_child_by_name(const lv_obj_t * parent, const char * name_path);
#endif /*LV_USE_OBJ_NAME*/
/**
* Get the index of a child.
* @param obj pointer to an object

View File

@@ -1314,6 +1314,15 @@
#endif
#endif
/** Enable support widget names*/
#ifndef LV_USE_OBJ_NAME
#ifdef CONFIG_LV_USE_OBJ_NAME
#define LV_USE_OBJ_NAME CONFIG_LV_USE_OBJ_NAME
#else
#define LV_USE_OBJ_NAME 0
#endif
#endif
/** Automatically assign an ID when obj is created */
#ifndef LV_OBJ_ID_AUTO_ASSIGN
#ifdef CONFIG_LV_OBJ_ID_AUTO_ASSIGN

View File

@@ -127,6 +127,8 @@
#define LV_OBJ_ID_AUTO_ASSIGN 1
#define LV_USE_OBJ_ID_BUILTIN 1
#define LV_USE_OBJ_NAME 1
#define LV_CACHE_DEF_SIZE (10 * 1024 * 1024)
#ifndef LV_USE_LINUX_DRM

View File

@@ -14,6 +14,7 @@ void tearDown(void)
/* Function run after every test */
lv_obj_clean(lv_screen_active());
}
void test_dropdown_create_delete(void)
{
lv_dropdown_create(lv_screen_active());

View File

@@ -4,8 +4,16 @@
#include "unity/unity.h"
void test_obj_tree_1(void);
void test_obj_tree_2(void);
void setUp(void)
{
/* Function run before every test */
}
void tearDown(void)
{
/* Function run after every test */
lv_obj_clean(lv_screen_active());
}
void test_obj_tree_1(void)
{
@@ -183,4 +191,85 @@ void test_obj_move_to_index_no_operation_when_requested_negative_index_is_greate
TEST_ASSERT_EQUAL(1, lv_obj_get_index(child2));
}
void test_obj_get_by_name(void)
{
lv_obj_set_flex_flow(lv_screen_active(), LV_FLEX_FLOW_ROW);
lv_obj_t * cont1 = lv_obj_create(lv_screen_active());
lv_obj_set_name_static(cont1, "first_static");
lv_obj_set_name(cont1, "first_non_static");
lv_obj_set_name_static(cont1, "first");
lv_obj_t * cont2 = lv_obj_create(lv_screen_active());
lv_obj_set_flex_flow(cont2, LV_FLEX_FLOW_COLUMN);
lv_obj_set_name(cont2, "second_non_static");
lv_obj_set_name(cont2, "second");
lv_obj_t * cont3 = lv_obj_create(lv_screen_active());
lv_obj_t * cont4 = lv_obj_create(lv_screen_active());
lv_obj_set_name_static(cont4, "forth_static");
lv_obj_set_name_static(cont4, "forth");
lv_obj_t * root_label = lv_label_create(lv_screen_active());
lv_label_set_text(root_label, "Root");
lv_obj_set_name(root_label, "my_label");
lv_slider_create(cont2);
lv_obj_t * btn = lv_button_create(cont2);
lv_switch_create(cont2);
lv_obj_t * hello_label = lv_label_create(btn);
lv_label_set_text(hello_label, "Hello");
lv_obj_set_name(hello_label, "my_label"); /*Same name as ofr the other label*/
lv_obj_t * found_obj;
/*-------------
* Get by name
*------------*/
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "second");
TEST_ASSERT_EQUAL(cont2, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "lv_obj_3");
TEST_ASSERT_EQUAL(cont3, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "fifth");
TEST_ASSERT_EQUAL(NULL, found_obj);
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "second/lv_button_1/my_label");
TEST_ASSERT_EQUAL(hello_label, found_obj);
/*"hello" label doesn't have children*/
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "second/lv_button_1/my_label/no_child");
TEST_ASSERT_EQUAL(NULL, found_obj);
/*Non existing child*/
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "second/lv_button_1/other_label");
TEST_ASSERT_EQUAL(NULL, found_obj);
/*Extra slash*/
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "second//lv_button_1/other_label");
TEST_ASSERT_EQUAL(NULL, found_obj);
/*Empty*/
found_obj = lv_obj_get_child_by_name(lv_screen_active(), "");
TEST_ASSERT_EQUAL(NULL, found_obj);
/*-------------
* Find by name
*------------*/
found_obj = lv_obj_find_by_name(lv_screen_active(), "lv_obj_3");
TEST_ASSERT_EQUAL(cont3, found_obj);
found_obj = lv_obj_find_by_name(lv_screen_active(), "my_label");
TEST_ASSERT_EQUAL(root_label, found_obj);
found_obj = lv_obj_find_by_name(cont2, "my_label");
TEST_ASSERT_EQUAL(hello_label, found_obj);
}
#endif