diff --git a/Kconfig b/Kconfig index 4220be2627..1d9740bb75 100644 --- a/Kconfig +++ b/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 diff --git a/lv_conf_template.h b/lv_conf_template.h index c5880b7193..69903a8ca6 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -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 diff --git a/src/core/lv_obj.c b/src/core/lv_obj.c index 86fca2c764..da4c1decd0 100644 --- a/src/core/lv_obj.c +++ b/src/core/lv_obj.c @@ -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) { diff --git a/src/core/lv_obj_private.h b/src/core/lv_obj_private.h index e6c62f1e28..5b1100fb85 100644 --- a/src/core/lv_obj_private.h +++ b/src/core/lv_obj_private.h @@ -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 { diff --git a/src/core/lv_obj_tree.c b/src/core/lv_obj_tree.c index 15c069e907..960ddec5d1 100644 --- a/src/core/lv_obj_tree.c +++ b/src/core/lv_obj_tree.c @@ -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*/ diff --git a/src/core/lv_obj_tree.h b/src/core/lv_obj_tree.h index 9f679dd40a..3ed2011499 100644 --- a/src/core/lv_obj_tree.h +++ b/src/core/lv_obj_tree.h @@ -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 + "_" + + * 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 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 diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index d70a33f20b..0cd35c15f5 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -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 diff --git a/tests/src/lv_test_conf_full.h b/tests/src/lv_test_conf_full.h index 2d8206cf66..5034db9ba5 100644 --- a/tests/src/lv_test_conf_full.h +++ b/tests/src/lv_test_conf_full.h @@ -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 diff --git a/tests/src/test_cases/widgets/test_dropdown.c b/tests/src/test_cases/widgets/test_dropdown.c index 9cdbf0f489..9d420f4c05 100644 --- a/tests/src/test_cases/widgets/test_dropdown.c +++ b/tests/src/test_cases/widgets/test_dropdown.c @@ -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()); diff --git a/tests/src/test_cases/widgets/test_obj_tree.c b/tests/src/test_cases/widgets/test_obj_tree.c index 4aa21b6946..0e52796198 100644 --- a/tests/src/test_cases/widgets/test_obj_tree.c +++ b/tests/src/test_cases/widgets/test_obj_tree.c @@ -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