diff --git a/docs/src/xml/ui_elements/api.rst b/docs/src/xml/ui_elements/api.rst index d26d9ab19a..176d02b167 100644 --- a/docs/src/xml/ui_elements/api.rst +++ b/docs/src/xml/ui_elements/api.rst @@ -63,6 +63,55 @@ These properties are set once (at creation time), and there are no specific ``set`` functions to modify the property later. LVGL's general API can still be used to modify any widget in the component, but no dedicated API functions are generated. + +Slots +----- + +With the help of a "slot" any UI element in the component can be easily exposed as a parent +that can be referenced later and children can be created there. + + +Just add ```` to the ```` to tell the Editor +which children to expose. + +To target a slot on an instance of a component create a child like +```` and add the children as needed. + +Slots are available only for components. In case of a widget the more powerful +:ref:`elements with get access type ` can be used. + +Slots are very useful to create components like screen templates where the user is +allowed to create children on certain internal UI elements. + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + Limitations ----------- @@ -89,7 +138,12 @@ Example + + + + + @@ -255,6 +309,9 @@ LVGL's UI Editor generates this: 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); + +.. _xml_api_element_get: + access="get" ~~~~~~~~~~~~ diff --git a/src/xml/lv_xml.c b/src/xml/lv_xml.c index 04fa408fc9..93795a49a2 100644 --- a/src/xml/lv_xml.c +++ b/src/xml/lv_xml.c @@ -844,43 +844,57 @@ static void view_start_element_handler(void * user_data, const char * name, cons resolve_consts(attrs, &state->scope); - void * item = NULL; + state->item = NULL; /* Select the widget specific parser type based on the name */ lv_widget_processor_t * p = lv_xml_widget_get_processor(name); if(p) { - item = p->create_cb(state, attrs); - state->item = item; + state->item = p->create_cb(state, attrs); + if(state->item) { + /*If it's a widget remove all styles. E.g. if it extends an `lv_button` + *now it has the button theme styles. However if it were a real widget + *it had e.g. `my_widget_class` so the button's theme wouldn't apply on it. + *Removing the style will ensure a better preview*/ + if(state->scope.is_widget && is_view) lv_obj_remove_style_all(state->item); - - /*If it's a widget remove all styles. E.g. if it extends an `lv_button` - *now it has the button theme styles. However if it were a real widget - *it had e.g. `my_widget_class` so the button's theme wouldn't apply on it. - *Removing the style will ensure a better preview*/ - if(state->scope.is_widget && is_view) lv_obj_remove_style_all(item); - - /*Apply the attributes from e.g. ``*/ - if(item) { + /*Apply the attributes from e.g. ``*/ p->apply_cb(state, attrs); } } /* If not a widget, check if it is a component */ - if(item == NULL) { - item = lv_xml_component_process(state, name, attrs); - state->item = item; + if(state->item == NULL) { + state->item = lv_xml_component_process(state, name, attrs); } - /* If it isn't a component either then it is unknown */ - if(item == NULL) { - LV_LOG_WARN("'%s' is not a known widget, element, or component", name); + /* If not a component either, check if it is a slot, e.g. my_button-icon */ + if(state->item == NULL) { + char buf[128]; + if(lv_strlen(name) >= sizeof(buf)) { + LV_LOG_WARN("Component/slot name '%s' is too long (max 127 chars); skipping slot parsing.", name); + } + else { + lv_strlcpy(buf, name, sizeof(buf)); + char * bufp = buf; + const char * comp_name = lv_xml_split_str(&bufp, '-'); + const char * slot_name = bufp; + lv_xml_component_scope_t * comp_scope = lv_xml_component_get_scope(comp_name); + if(comp_scope && lv_streq(comp_name, comp_scope->name)) { + state->item = lv_obj_find_by_name(state->parent, slot_name); + } + } + } + + /* If it isn't a slot either then it is unknown */ + if(state->item == NULL) { + LV_LOG_WARN("'%s' is not a known widget, element, component, or slot", name); return; } void ** new_parent = lv_ll_ins_tail(&state->parent_ll); - *new_parent = item; + *new_parent = state->item; if(is_view) { - state->view = item; + state->view = state->item; } } diff --git a/src/xml/lv_xml_component.c b/src/xml/lv_xml_component.c index 91d22237f0..1a9a057364 100644 --- a/src/xml/lv_xml_component.c +++ b/src/xml/lv_xml_component.c @@ -53,7 +53,7 @@ static void end_metadata_handler(void * user_data, const char * name); static void process_const_element(lv_xml_parser_state_t * state, const char ** attrs); static void process_font_element(lv_xml_parser_state_t * state, const char * type, const char ** attrs); static void process_image_element(lv_xml_parser_state_t * state, const char * type, const char ** attrs); -static void process_prop_element(lv_xml_parser_state_t * state, const char ** attrs); +static void process_prop_element(lv_xml_parser_state_t * state, const char * name, const char ** attrs); static char * extract_view_content(const char * xml_definition); static style_prop_anim_type_t style_prop_anim_get_type(lv_style_prop_t prop); static void anim_exec_cb(lv_anim_t * a, int32_t v); @@ -107,7 +107,7 @@ lv_obj_t * lv_xml_component_process(lv_xml_parser_state_t * state, const char * return NULL; } - /* Apply the properties of the component, e.g. */ + /* Apply the properties of the component, e.g. */ state->item = item; lv_widget_processor_t * extended_proc = lv_xml_widget_get_extended_widget_processor(scope->extends); extended_proc->apply_cb(state, attrs); @@ -129,6 +129,8 @@ lv_obj_t * lv_xml_component_process(lv_xml_parser_state_t * state, const char * lv_xml_component_scope_t * lv_xml_component_get_scope(const char * component_name) { + if(component_name == NULL) return NULL; + lv_xml_component_scope_t * scope; LV_LL_READ(&component_scope_ll, scope) { if(lv_streq(scope->name, component_name)) return scope; @@ -868,8 +870,10 @@ static void process_grad_stop_element(lv_xml_parser_state_t * state, const char dsc->stops_count++; } -static void process_prop_element(lv_xml_parser_state_t * state, const char ** attrs) +static void process_prop_element(lv_xml_parser_state_t * state, const char * name, const char ** attrs) { + if(!lv_streq(name, "prop")) return; + lv_xml_param_t * prop = lv_ll_ins_tail(&state->scope.param_ll); lv_memzero(prop, sizeof(lv_xml_param_t)); @@ -904,7 +908,7 @@ static void start_metadata_handler(void * user_data, const char * name, const ch switch(state->section) { case LV_XML_PARSER_SECTION_API: if(old_section != state->section) return; /*Ignore the section opening, e.g. */ - process_prop_element(state, attrs); + process_prop_element(state, name, attrs); break; case LV_XML_PARSER_SECTION_CONSTS: diff --git a/tests/src/test_cases/xml/test_xml_slot.c b/tests/src/test_cases/xml/test_xml_slot.c new file mode 100644 index 0000000000..6e13e13701 --- /dev/null +++ b/tests/src/test_cases/xml/test_xml_slot.c @@ -0,0 +1,60 @@ +#if LV_BUILD_TEST +#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_slot_1(void) +{ + const char * my_button = + "" + " " + " " + " " + " " + "" + " " + " " + " " + " " + " " + ""; + + const char * my_list = + "" + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + lv_result_t res; + + res = lv_xml_register_component_from_data("my_button", my_button); + TEST_ASSERT_EQUAL_INT(LV_RESULT_OK, res); + + res = lv_xml_register_component_from_data("my_list", my_list); + TEST_ASSERT_EQUAL_INT(LV_RESULT_OK, res); + + lv_obj_t * my_list_0 = lv_xml_create(lv_screen_active(), "my_list", NULL); + + lv_obj_t * new_label = lv_obj_get_child_by_name(my_list_0, "my_button_1/slot1/lv_label_1"); + + TEST_ASSERT_EQUAL_STRING("New label", lv_label_get_text(new_label)); +} + +#endif