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
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
-----------
@@ -89,7 +138,12 @@ Example
<!-- my_list.xml -->
<component>
<api>
<slot name="header"/>
</api>
<view flex_flow="column">
<lv_obj name="header" width="100%"/>
<my_button button_label="First"/>
<my_button button_label="Wifi" button_icon="img_wifi"/>
<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);
void my_widget_set_indicator_value(lv_obj_t * obj, int32_t value);
.. _xml_api_element_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);
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. `<lv_slider value="30" x="20">`*/
if(item) {
/*Apply the attributes from e.g. `<lv_slider value="30" x="20">`*/
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;
}
}
+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_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. <my_button x="20" styles="red"/> */
/* Apply the properties of the component, e.g. <my_button x="20" width="300"/> */
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. <api>*/
process_prop_element(state, attrs);
process_prop_element(state, name, attrs);
break;
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