| name | helix-xml |
| description | HelixScreen XML UI engine — triggers when editing files in ui_xml/, lib/helix-xml/, src/xml_registration.cpp, or include/ headers that register subjects/components/events. Also for creating modal dialogs via ModalStack (src/ui/modals/), defining custom XML widgets/components in ui_xml/components/, writing C++ code that binds lv_subject_t to XML layouts, working with design tokens (#space_md, #card_bg) in XML, or using bind_text/bind_flag/bind_style reactive bindings. Covers the full XML→Subject→C++ pipeline, declarative UI rules, component system, event registration, and gotchas specific to this engine.
|
HelixScreen XML UI Engine (helix-xml)
The helix-xml library (lib/helix-xml/) provides the declarative XML UI system for HelixScreen. XML layouts live in ui_xml/, components are registered in src/xml_registration.cpp, and C++ subjects are bound via lv_xml_register_subject(). The engine implements a Subject-Observer pipeline:
XML Layout (components) → bind_text/bind_value/bind_flag → Subjects (C++) → UI auto-updates
Core Architecture
Registration Flow (startup order matters)
lv_xml_register_font(NULL, "montserrat_16", &lv_font_montserrat_16);
lv_xml_register_component_from_file("A:ui_xml/globals.xml");
ui_theme_register_responsive_spacing();
ui_theme_register_responsive_fonts();
lv_xml_register_component_from_file("A:ui_xml/home_panel.xml");
Component Structure
<component>
<api>
<prop name="text" type="string" default="Click me"/>
</api>
<consts>
<px name="size" value="36"/>
</consts>
<styles>
<style name="base" bg_color="0x333" text_color="0xfff"/>
</styles>
<view extends="lv_button" width="#size">
<lv_label text="$text" align="center"/>
<style name="base"/>
</view>
</component>
Sigils
| Sigil | Meaning | Example |
|---|
# | Design token / const | style_pad_all="#space_md" |
$ | Component prop | text="$label_text" |
@ | Subject binding (ui_button only) | text="@status" |
For standard widgets, bind_text="subject_name" always treats its value as a subject (no @ needed).
Subject Lifecycle
static lv_subject_t subj;
static char buf[128];
lv_subject_init_string(&subj, buf, NULL, sizeof(buf), "Initial");
lv_xml_register_subject(NULL, "status_text", &subj);
lv_subject_copy_string(&subj, "New status");
Subject types: lv_subject_init_string(), lv_subject_init_int(), lv_subject_init_pointer(), lv_subject_init_color().
Data Binding (XML side)
<lv_label bind_text="status"/>
<lv_label bind_text="temp" bind_text-fmt="%.1f°C"/>
<lv_obj>
<bind_flag_if_eq subject="step" flag="hidden" ref_value="1"/>
</lv_obj>
<styles>
<style name="active" bg_color="0x00ff00"/>
<style name="inactive" bg_color="0xff0000"/>
</styles>
<lv_button>
<bind_style name="inactive" subject="is_on" ref_value="0"/>
<bind_style name="active" subject="is_on" ref_value="1"/>
</lv_button>
Comparison operators: bind_flag_if_eq, _not_eq, _gt, _ge, _lt, _le (same for bind_state_if_* and bind_style_if_*).
Flags: hidden, clickable, checkable, scrollable, disabled, ignore_layout, floating.
Declarative UI Rules (MANDATORY)
These rules are non-negotiable in this codebase:
- ALL UI updates via reactive subjects — never
lv_label_set_text(), lv_obj_add_flag(HIDDEN), lv_obj_set_style_*(), or lv_obj_add_state()
- Events declared in XML with
<event_cb trigger="clicked" callback="name"/>, registered in C++ via lv_xml_register_event_cb(). NEVER use lv_obj_add_event_cb()
- Inline styles override bind_style — when using reactive style changes, do NOT set inline
style_* attributes for those properties. Use two bind_style entries instead
- Register subjects BEFORE creating XML that binds to them
- Subject string buffers MUST be static or heap — stack-allocated buffers cause use-after-free
- Use design tokens (
#card_bg, #space_md) not hardcoded pixels/colors
- Semantic widgets (
<text_heading>, <text_body>, <text_small>) not raw <lv_label> with hardcoded fonts
- Component names required:
<controls_panel name="controls_panel"/> not <controls_panel/>
Exceptions (acceptable direct access): LV_EVENT_DELETE cleanup, widget pool recycling, chart data, animations, one-time setup().
Flex Layout
Three properties needed to center (unlike CSS):
<lv_obj flex_flow="column" height="100%"
style_flex_main_place="center"
style_flex_cross_place="center"
style_flex_track_place="center">
flex_grow="1" expands to fill; parent MUST have explicit height="100%"
width="content" not width="LV_SIZE_CONTENT" (which parses as 0)
style_pad_column / style_pad_row for flex gaps
Events
<lv_button name="btn">
<event_cb trigger="clicked" callback="on_btn_clicked"/>
</lv_button>
lv_xml_register_event_cb(nullptr, "on_btn_clicked", [](lv_event_t* e) {
});
Triggers: clicked, value_changed, pressed, released, long_pressed, focused, ready.
Gotchas
- Unknown XML attributes are silently ignored — typos produce no error
style_image_recolor not style_img_recolor (full words only)
- No
zoom attribute → use scale_x/scale_y (256 = 100%)
- No
flex_align attribute → use style_flex_main_place/style_flex_cross_place/style_flex_track_place
- Dropdown options:
<lv_dropdown options="A B C"/> (XML entity for newline)
lv_bar value=0 bug: set to 1 then 0 as workaround
- XML
<subjects> block shadows global C++ subjects — don't declare in both places
- Unregistered components are silently empty — no error, no log
Observer Cleanup
Track observers for heap-allocated widget data:
data->text_observer = lv_label_bind_text(label, &subject, "%s");
lv_observer_remove(data->text_observer);
delete data;
For custom widgets with owned subjects, detach children BEFORE deiniting:
lv_obj_remove_from_subject(label, nullptr);
lv_subject_deinit(&owned_subject);
File Index
| File | Content |
|---|
| references/xml-guide.md | Widgets, layouts, styles, responsive design, implementation patterns |
| references/xml-attributes.md | Complete attribute reference for all XML widgets |
| references/xml-components.md | Component API, props, custom semantic widgets |
| references/modal-system.md | Modal dialog system — helpers, subclasses, XML templates |
| references/declarative-ui-rules.md | Reactive-first rules, threading safety, banned patterns |