feat(xml): add slot support (#9193)
Arduino Lint / lint (push) Has been cancelled
Build Examples with C++ Compiler / build-examples (push) Has been cancelled
MicroPython CI / Build esp32 port (push) Has been cancelled
MicroPython CI / Build rp2 port (push) Has been cancelled
MicroPython CI / Build stm32 port (push) Has been cancelled
MicroPython CI / Build unix port (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_NORMAL_8BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_SDL - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build ESP IDF ESP32S3 (push) Has been cancelled
C/C++ CI / Run tests with 32bit build (push) Has been cancelled
C/C++ CI / Run tests with 64bit build (push) Has been cancelled
BOM Check / bom-check (push) Has been cancelled
Verify that lv_conf_internal.h matches repository state / verify-conf-internal (push) Has been cancelled
Verify the widget property name / verify-property-name (push) Has been cancelled
Verify code formatting / verify-formatting (push) Has been cancelled
Compare file templates with file names / template-check (push) Has been cancelled
Build docs / build-and-deploy (push) Has been cancelled
Test API JSON generator / Test API JSON (push) Has been cancelled
Install LVGL using CMake / build-examples (push) Has been cancelled
Check Makefile / Build using Makefile (push) Has been cancelled
Check Makefile for UEFI / Build using Makefile for UEFI (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/benchmark_results_comment/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/filter_docker_logs/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/serialize_results/test.sh) (push) Has been cancelled
Hardware Performance Test / Hardware Performance Benchmark (push) Has been cancelled
Hardware Performance Test / HW Benchmark - Save PR Number (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_32B - Ubuntu (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_64B - Ubuntu (push) Has been cancelled
Port repo release update / run-release-branch-updater (push) Has been cancelled
Verify Font License / verify-font-license (push) Has been cancelled
Verify Kconfig / verify-kconfig (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark 32b - lv_conf_perf32b (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark 64b - lv_conf_perf64b (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Save PR Number (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Gabor Kiss-Vamosi
2026-01-01 05:14:44 +01:00
committed by GitHub
parent 8bc37d840f
commit f38718108d
4 changed files with 159 additions and 24 deletions
+57
View File
@@ -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 ``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. 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 ``<slot name="my_slot"/>`` to the ``<api>`` to tell the Editor
which children to expose.
To target a slot on an instance of a component create a child like
``<component_name-slot_name>`` 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 <xml_api_element_get>` 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
<!-- simple_screen.xml -->
<component>
<api>
<slot name="icon_area"/>
<slot name="content_area"/>
</api>
<view width="100%" height="100%" flex_flow="column">
<lv_obj name="icon_area" width="100%" height="30" flex_flow="row"/>
<lv_obj name="content_area" width="100%" flex_grow="1" flex_flow="column"/>
</view>
</component>
<!-- main_screen.xml -->
<component>
<view extends="simple_screen" width="100%">
<simple_screen-icon_area>
<lv_image src="img1"/>
<lv_image src="img2"/>
</simple_screen-icon_area>
<simple_screen-content_area>
<lv_label text="Some content"/>
</simple_screen-content_area>
</view>
</component>
Limitations Limitations
----------- -----------
@@ -89,7 +138,12 @@ Example
<!-- my_list.xml --> <!-- my_list.xml -->
<component> <component>
<api>
<slot name="header"/>
</api>
<view flex_flow="column"> <view flex_flow="column">
<lv_obj name="header" width="100%"/>
<my_button button_label="First"/> <my_button button_label="First"/>
<my_button button_label="Wifi" button_icon="img_wifi"/> <my_button button_label="Wifi" button_icon="img_wifi"/>
<my_button button_label="Third"/> <my_button button_label="Third"/>
@@ -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); 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); void my_widget_set_indicator_value(lv_obj_t * obj, int32_t value);
.. _xml_api_element_get:
access="get" access="get"
~~~~~~~~~~~~ ~~~~~~~~~~~~
+34 -20
View File
@@ -844,43 +844,57 @@ static void view_start_element_handler(void * user_data, const char * name, cons
resolve_consts(attrs, &state->scope); resolve_consts(attrs, &state->scope);
void * item = NULL; state->item = NULL;
/* Select the widget specific parser type based on the name */ /* Select the widget specific parser type based on the name */
lv_widget_processor_t * p = lv_xml_widget_get_processor(name); lv_widget_processor_t * p = lv_xml_widget_get_processor(name);
if(p) { if(p) {
item = p->create_cb(state, attrs); state->item = p->create_cb(state, attrs);
state->item = item; 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);
/*Apply the attributes from e.g. `<lv_slider value="30" x="20">`*/
/*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. `<lv_slider value="30" x="20">`*/
if(item) {
p->apply_cb(state, attrs); p->apply_cb(state, attrs);
} }
} }
/* If not a widget, check if it is a component */ /* If not a widget, check if it is a component */
if(item == NULL) { if(state->item == NULL) {
item = lv_xml_component_process(state, name, attrs); state->item = lv_xml_component_process(state, name, attrs);
state->item = item;
} }
/* If it isn't a component either then it is unknown */ /* If not a component either, check if it is a slot, e.g. my_button-icon */
if(item == NULL) { if(state->item == NULL) {
LV_LOG_WARN("'%s' is not a known widget, element, or component", name); 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; return;
} }
void ** new_parent = lv_ll_ins_tail(&state->parent_ll); void ** new_parent = lv_ll_ins_tail(&state->parent_ll);
*new_parent = item; *new_parent = state->item;
if(is_view) { if(is_view) {
state->view = item; state->view = state->item;
} }
} }
+8 -4
View File
@@ -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_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_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_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 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 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); 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; return NULL;
} }
/* Apply the properties of the component, e.g. <my_button x="20" styles="red"/> */ /* Apply the properties of the component, e.g. <my_button x="20" width="300"/> */
state->item = item; state->item = item;
lv_widget_processor_t * extended_proc = lv_xml_widget_get_extended_widget_processor(scope->extends); lv_widget_processor_t * extended_proc = lv_xml_widget_get_extended_widget_processor(scope->extends);
extended_proc->apply_cb(state, attrs); 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) 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_xml_component_scope_t * scope;
LV_LL_READ(&component_scope_ll, scope) { LV_LL_READ(&component_scope_ll, scope) {
if(lv_streq(scope->name, component_name)) return 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++; 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_xml_param_t * prop = lv_ll_ins_tail(&state->scope.param_ll);
lv_memzero(prop, sizeof(lv_xml_param_t)); 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) { switch(state->section) {
case LV_XML_PARSER_SECTION_API: case LV_XML_PARSER_SECTION_API:
if(old_section != state->section) return; /*Ignore the section opening, e.g. <api>*/ if(old_section != state->section) return; /*Ignore the section opening, e.g. <api>*/
process_prop_element(state, attrs); process_prop_element(state, name, attrs);
break; break;
case LV_XML_PARSER_SECTION_CONSTS: case LV_XML_PARSER_SECTION_CONSTS:
+60
View File
@@ -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 =
"<component>"
" <api>"
" <slot name=\"slot1\"/>"
" <prop name=\"label\" type=\"string\" default=\"Hello world\"/>"
" </api>"
""
" <view width=\"content\" height=\"content\">"
" <lv_button name=\"slot1\" flex_flow=\"row\">"
" <lv_label text=\"$label\"/>"
" </lv_button>"
" </view>"
"</component>";
const char * my_list =
"<component>"
" <view flex_flow=\"column\" width=\"300\" height=\"400\">"
" <my_button/>"
" <my_button label=\"Custom label\">"
" <my_button-slot1>"
" <lv_label text=\"New label\" style_text_color=\"0xf00\"/>"
" </my_button-slot1>"
" </my_button>"
" </view>"
"</component>";
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