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