For anyone following along with this thread, I want to clarify what ComposeUI is and what problems it is designed to solve.
ComposeUI is not a wrapper around LVGL’s API. It is a declarative layer that lets you describe UI structure, geometry, style, and behavior as data rather than imperative mutation.
It is built this way because LVGL already provides a complete object system with its own lifecycle, opinions, and state model. Instead of adding another OOP layer on top, ComposeUI treats LVGL as a system and extends it as a higher‑level paradigm — this keeps responsibilities clean and avoids polluting LVGL’s domain model.
ComposeUI simply gives you a vocabulary for describing the shape of an interface. WidgetAttributes.h defines the subset of LVGL’s domain that ComposeUI models. It is essentially the system’s “Rosetta Stone”: the complete set of attributes that ComposeUI knows how to interpret and translate into LVGL behavior.
The repository works out of the box: you can clone it and run the example project immediately (assuming you meet the dependency requirements found in the README of the project). Once you have done that, the next step should be creating your own widgets through WidgetDefinitions and WidgetInstances. Currently only two widgets are described in ComposeUI: LVGL built-in type “Label”, Custom Widget “Graticules”.
WidgetDefinitions declares the set of WidgetAttributes that you want ComposeUI to associate with LVGL widgets. These definitions populate the WidgetRegistry, which the system uses to understand what widget types exist and how their attributes map onto LVGL widgets.
WidgetInstances associates widget classes and is also used to configure Widget properties. Widget classes are added to the WidgetPool, which serves as a library of widget objects the system can reference. For built-in LVGL types, you can simply use the base Widget class.
Below is the simplest example of modifying the system. For clarity, I’m showing only the .cpp portion here; the corresponding header changes follow naturally from the definitions.
Add WidgetAttributes to the WidgetRegistry:
namespace UI::Definitions::Widgets {
Utils::Widget::Attributes my_label_widget;
void setBuiltIn() {
// You will want to reference WidgetAttributes to determine what it is that you want
// to specify here. Relevant Label attributes can be discovered in LVGL's official docs
// Metadata
my_label_widget.isCustom = false; // Just being explicit, false = default.
my_label_widget.type = Utils::Widget::Type::LABEL;
my_label_widget.role = Utils::Widget::Role::FUNCTIONAL;
my_label_widget.name = "LVGL forum Example";
// Layout & appearance.
my_label_widget.geometry.mode = Utils::Widget::SizingMode::ABSOLUTE;
my_label_widget.geometry.width = 150;
my_label_widget.geometry.height = 25;
my_label_widget.position.alignment = LV_ALIGN_BOTTOM_LEFT;
my_label_widget.label.text = "A Label!";
my_label_widget.text.textColor = Utils::Widget::Color::BLACK;
my_label_widget.text.textAlign = LV_TEXT_ALIGN_CENTER;
my_label_widget.text.letterSpacing = 5;
my_label_widget.text.lineSpacing = 10;
my_label_widget.background.backgroundColor = Utils::Widget::Color::WHITE;
my_label_widget.background.backgroundOpacity = 255;
my_label_widget.border.borderWidth = 2;
my_label_widget.border.borderColor = Utils::Widget::Color::BLACK;
my_label_widget.border.borderOpacity = 255;
}
void registerWidgets(WidgetRegistry& registry) {
setBuiltIn();
registry.registerWidget(Utils::Widget::Type::LABEL, &my_label_widget);
}
}
Add a Widget to the WidgetPool:
namespace UI::Instances::Widgets {
// This is a statically defined widget object. ComposeUI uses compile‑time
// dispatch, so this isn’t a runtime “instance” in the OOP sense.
Widget my_label_widget;
void setDrawEvent() {
// Only needed when registering callbacks for custom widget types
}
void addWidgets(WidgetPool& pool) {
// For built‑in types, this is effectively a no‑op, but it keeps the pattern consistent
setDrawEvent();
// Registers the widget
pool.addWidget(Utils::Widget::Type::LABEL, &my_label_widget);
}
}
And that’s all you need, at this point you can compile and run. The behavior of the system is fully determined by these two files, which keep the model predictable and easy to reason about.
If you are interested in extending ComposeUI - whether that means adding new built-in widgets, creating custom LVGL widgets, or adapting it to a different display driver - I am happy to put together more examples. Just let me know what direction you are exploring, and I can tailor something specific.