LVGL Gallery — Zig
Source: apps/zig/heap/lvgl_gallery/src/main.zig.
The Zig port uses ove.lvgl typed wrappers and defer guard.deinit() for the build-phase lock. Page tables are comptime arrays of function pointers.
Build
make host.posix.lvgl_gallery_zig
make && make run
Page builders
fn buildSliderPage(parent: lvgl.Object) void {
var slider = lvgl.Slider.new(parent);
slider.setSize(200, 12);
slider.center();
slider.setRange(0, 100);
slider.setValue(42);
slider.onChange(struct {
fn cb(v: i32) void { _ = v; /* … */ }
}.cb);
}
Closures aren't first-class in Zig the way they are in Rust, but a nested struct { fn cb(...) }.cb pattern produces an equivalent function pointer at comptime with no allocator involvement.
Page table
const Page = struct {
name: []const u8,
build: *const fn (lvgl.Object) void,
};
const pages = [_]Page{
.{ .name = "Object", .build = buildObjectPage },
.{ .name = "Label", .build = buildLabelPage },
.{ .name = "Slider", .build = buildSliderPage },
/* … */
};
comptime array — sits in .rodata.
Entry point
fn appMain() void {
var guard = ove.lvgl.lock();
defer guard.deinit();
const screen = lvgl.Object.activeScreen();
var tabs = lvgl.TabView.new(screen, lvgl.Direction.top, 40);
inline for (pages) |p| {
const tab = tabs.addTab(p.name);
p.build(tab);
}
}
comptime { ove.exportMain(appMain); }
inline for unrolls the loop at comptime — each page builder is called directly, no function-pointer dispatch through the table. The table itself stays for readability.
Comptime widget gating
If the LVGL config disables a particular widget (CONFIG_OVE_LVGL_USE_CHART=n), the corresponding page can be omitted at comptime:
if (comptime ove.lvgl.has(.Chart)) {
.{ .name = "Chart", .build = buildChartPage },
} else {
.{ .name = "Chart (disabled)", .build = buildDisabledStub },
}
ove.lvgl.has(.X) checks @hasDecl against the bindings stub — false-branch code never gets emitted.