<alp/gui.h> — LVGL Re-export
Re-exports the upstream LVGL library with Alp defaults applied so LVGL widgets work out of the box on any E1M target, and provides alp_gui_lvgl_attach() — the bridge that binds an alp_display_t to LVGL's display driver.
No custom widgets ship: the widget vocabulary is upstream LVGL's, and this header is the portable binding to Alp's display surface.
Header
#include <alp/gui.h> // pulls in <lvgl.h> with Alp defaults
Including <alp/gui.h> requires the LVGL package to be on the include path — this is a build option, not bundled source.
Enabling LVGL
| Build | How |
|---|---|
| Zephyr | Set CONFIG_LVGL=y. CONFIG_ALP_SDK_HAS_LVGL then defaults on and the build defines ALP_HAS_LVGL automatically. The lvgl board.yaml library knob selects the module. |
| Plain CMake / Yocto | Set -DALP_HAS_LVGL=ON and supply the LVGL include path yourself. |
libraries:
- lvgl
The SDK ships a profile header that pre-tunes LVGL for embedded display sizes and 16-bit colour. Override by dropping your own profile at the app's include root.
When the build has no LVGL wired in, the symbol still links — alp_gui_lvgl_attach() degrades to ALP_ERR_INVAL on a NULL display and ALP_ERR_NOSUPPORT otherwise, so portable code compiles either way.
Attach a display
The app owns the whole sequence: open the panel → lv_init() → wire the tick → attach.
#include <alp/display.h>
#include <alp/gui.h>
// 1. Open the panel through the portable display surface.
alp_display_config_t display_cfg = ALP_DISPLAY_CONFIG_DEFAULT(0);
alp_display_t *display = alp_display_open(&display_cfg);
if (display == NULL) {
return (int)alp_last_error();
}
// 2. Allocate LVGL's global state (styles, theme, timer list).
lv_init();
// 3. LVGL needs a monotonic millisecond tick for animations and timers.
lv_tick_set_cb(k_uptime_get_32);
// 4. Bind LVGL's renderer to the panel.
if (alp_gui_lvgl_attach(display) != ALP_OK) {
return -1;
}
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Hello, E1M!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
while (1) {
lv_task_handler();
k_msleep(5);
}
alp_gui_lvgl_attach() allocates an lv_display_t (LVGL v9) sized to the display's reported geometry, wires its flush callback to alp_display_blit(), and allocates a persistent partial-refresh draw buffer. Call lv_init() once before attaching any display.
:::warning Turn Zephyr's LVGL auto-init off
Set CONFIG_LV_Z_AUTO_INIT=n in prj.conf. Left on, Zephyr's own lvgl module creates its own lv_display_t bound straight to the zephyr,display chosen node the moment CONFIG_LVGL=y is set — a second writer racing alp_gui_lvgl_attach()'s display for the same panel.
With auto-init off, that module's SYS_INIT hook never runs, which is also why the app must call lv_init() and lv_tick_set_cb() itself — same work, just moved to the app side of the auto-init boundary.
:::
| Returns | When |
|---|---|
ALP_OK | Bridge installed. |
ALP_ERR_INVAL | display is NULL. |
ALP_ERR_NOSUPPORT | Build not compiled with ALP_HAS_LVGL, or the display's pixel format has no LVGL v9 equivalent. |
ALP_ERR_NOMEM | lv_display_create or the draw-buffer allocation failed. |
| (propagated) | Any error alp_display_get_caps returns. |
Pixel formats
alp_pixfmt_t | LVGL v9 |
|---|---|
ALP_PIXFMT_RGB565 | Supported |
ALP_PIXFMT_RGB888 | Supported |
ALP_PIXFMT_ARGB8888 | Supported |
ALP_PIXFMT_MONO_VLSB | ALP_ERR_NOSUPPORT |
ALP_PIXFMT_YUV420_PLANAR | ALP_ERR_NOSUPPORT |
ALP_PIXFMT_NV12 | ALP_ERR_NOSUPPORT |
ALP_PIXFMT_YUV420_PLANAR (4) and ALP_PIXFMT_NV12 (5) were appended to alp_pixfmt_t for <alp/jpeg.h>; they are encoder input formats, not framebuffer formats, and the LVGL bridge does not accept them.
MONO_VLSB has no LVGL match — LVGL's closest analogue is not the SSD1306-style vertical-byte packing that ALP_PIXFMT_MONO_VLSB denotes, so the bridge degrades to ALP_ERR_NOSUPPORT rather than claim a binding it would render incorrectly.
Draw-buffer sizing
The bridge allocates a persistent partial-refresh draw buffer (LV_DISPLAY_RENDER_MODE_PARTIAL). Its height in display lines is CONFIG_ALP_GUI_LVGL_BUF_LINES on Zephyr, and a compile-time default of 16 lines elsewhere (plain CMake / Yocto have no Kconfig).
Buffer size = panel width × lines × bytes-per-pixel, clamped to the panel's actual height. A bigger buffer amortises LVGL's per-flush overhead at the cost of heap; 16 lines is a conservative default (≤ 10 KB for a 320px-wide RGB565 panel).
Status
:::caution Not yet bench-proven on a real panel
alp_gui_lvgl_attach() became a real LVGL v9 bridge in v0.10 — it was previously a stub that always returned ALP_ERR_NOSUPPORT. The bridge is code-complete and tested under native_sim, but validation against a real display panel is still pending. Treat it as functional-but-unproven on silicon until that bench run lands.
:::
ABI status
[ABI-STABLE] — the re-export shim dates to v0.2 and the alp_gui_lvgl_attach() signature is unchanged. v0.10 changes the implementation from a stub to a working bridge; a build that previously got ALP_ERR_NOSUPPORT on a supported pixel format will now get ALP_OK.
See also
<alp/display.h>— raw display,alp_display_blit, pixel formats- LVGL documentation — the full widget reference
board.yaml:libraries: