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:
committed by
Liam Howatt
parent
6d999331d7
commit
626d6b51ad
4
Kconfig
4
Kconfig
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user