ui/lvgl: improve UI element types in zig
ci/woodpecker/push/woodpecker Pipeline was successful Details

this commit improves on the LVGL API wrapping interface in zig by
defining different types where each is associated with either a
particular lv_xxx_t object in C or a logically different UI element.

this makes programming much more pleasant, allows compartmentalizing
different UI concepts and elements, and reduces chances of making a
thanks to stricter type enforcement.

no visual changes in the UI.
pull/24/head
alex 1 year ago
parent 746b179478
commit 7d1ab5cb78
Signed by: x1ddos
GPG Key ID: FDEFB4A63CBD8460

@ -141,7 +141,7 @@ fn updateNetworkStatus(report: comm.Message.NetworkReport) !void {
// can happen with a fresh connection while dhcp is still in progress.
if (report.wifi_ssid != null and report.ipaddrs.len == 0) {
// TODO: sometimes this is too fast, not all ip addrs are avail (ipv4 vs ipv6)
if (lvgl.createTimer(nm_request_network_status, 1000, null)) |t| {
if (lvgl.LvTimer.new(nm_request_network_status, 1000, null)) |t| {
t.setRepeatCount(1);
} else |err| {
logger.err("network status timer failed: {any}", .{err});
@ -293,8 +293,8 @@ pub fn main() anyerror!void {
// run idle timer indefinitely.
// continue on failure: screen standby won't work at the worst.
_ = lvgl.createTimer(nm_check_idle_time, 2000, null) catch |err| {
logger.err("lvgl.CreateTimer(idle check): {any}", .{err});
_ = lvgl.LvTimer.new(nm_check_idle_time, 2000, null) catch |err| {
logger.err("lvgl.LvTimer.new(idle check): {any}", .{err});
};
{

@ -72,6 +72,11 @@ extern lv_style_t *nm_style_btn_red()
return &style_btn_red;
}
extern lv_style_t *nm_style_title()
{
return &style_title;
}
static void textarea_event_cb(lv_event_t *e)
{
lv_obj_t *textarea = lv_event_get_target(e);

File diff suppressed because it is too large Load Diff

@ -56,7 +56,7 @@ pub fn updateStatus(report: comm.Message.PoweroffProgress) !void {
all_stopped = all_stopped and sv.stopped;
}
if (all_stopped) {
win.status.setLabelText("powering off ...");
win.status.setText("powering off ...");
}
} else {
return error.NoProgressWindow;
@ -67,8 +67,8 @@ pub fn updateStatus(report: comm.Message.PoweroffProgress) !void {
/// the device turns off.
const ProgressWin = struct {
win: lvgl.Window,
status: *lvgl.LvObj, // text status label
svcont: *lvgl.LvObj, // services container
status: lvgl.Label, // text status
svcont: lvgl.FlexLayout, // services container
/// symbol width next to the service name. this aligns all service names vertically.
/// has to be wide enough to accomodate the spinner, but not too wide
@ -76,21 +76,18 @@ const ProgressWin = struct {
const sym_width = 20;
fn create() !ProgressWin {
const win = try lvgl.createWindow(null, 60, " " ++ symbol.Power ++ " SHUTDOWN");
errdefer win.winobj.destroy(); // also deletes all children created below
const wincont = win.content();
wincont.flexFlow(.column);
const win = try lvgl.Window.newTop(60, " " ++ symbol.Power ++ " SHUTDOWN");
errdefer win.destroy(); // also deletes all children created below
const wincont = win.content().flex(.column, .{});
// initial status message
const status = try lvgl.createLabel(wincont, "shutting down services. it may take up to a few minutes.", .{});
const status = try lvgl.Label.new(wincont, "shutting down services. it may take up to a few minutes.", .{});
status.setWidth(lvgl.sizePercent(100));
// prepare a container for services status
const svcont = try lvgl.createObject(wincont);
svcont.removeBackgroundStyle();
svcont.flexFlow(.column);
svcont.flexGrow(1);
svcont.padColumnDefault();
const svcont = try lvgl.FlexLayout.new(wincont, .column, .{});
svcont.setWidth(lvgl.sizePercent(100));
svcont.flexGrow(1);
return .{
.win = win,
@ -104,32 +101,28 @@ const ProgressWin = struct {
}
fn addServiceStatus(self: ProgressWin, name: []const u8, stopped: bool, err: ?[]const u8) !void {
const row = try lvgl.createObject(self.svcont);
row.removeBackgroundStyle();
row.flexFlow(.row);
row.flexAlign(.center, .center, .center);
row.padColumnDefault();
const row = try lvgl.FlexLayout.new(self.svcont, .row, .{ .all = .center });
row.setPad(10, .all, .{});
row.setWidth(lvgl.sizePercent(100));
row.setHeightToContent();
var buf: [100]u8 = undefined;
if (err) |e| {
const sym = try lvgl.createLabelFmt(row, &buf, symbol.Warning, .{}, .{ .long_mode = .clip });
const sym = try lvgl.Label.newFmt(row, &buf, symbol.Warning, .{}, .{ .long_mode = .clip });
sym.setWidth(sym_width);
sym.setTextColor(lvgl.paletteMain(.red), .{});
const lb = try lvgl.createLabelFmt(row, &buf, "{s}: {s}", .{ name, e }, .{ .long_mode = .dot });
lb.setTextColor(lvgl.paletteMain(.red), .{});
sym.setColor(lvgl.Palette.main(.red), .{});
const lb = try lvgl.Label.newFmt(row, &buf, "{s}: {s}", .{ name, e }, .{ .long_mode = .dot });
lb.setColor(lvgl.Palette.main(.red), .{});
lb.flexGrow(1);
} else if (stopped) {
const sym = try lvgl.createLabelFmt(row, &buf, symbol.Ok, .{}, .{ .long_mode = .clip });
const sym = try lvgl.Label.newFmt(row, &buf, symbol.Ok, .{}, .{ .long_mode = .clip });
sym.setWidth(sym_width);
const lb = try lvgl.createLabelFmt(row, &buf, "{s}", .{name}, .{ .long_mode = .dot });
const lb = try lvgl.Label.newFmt(row, &buf, "{s}", .{name}, .{ .long_mode = .dot });
lb.flexGrow(1);
} else {
const spin = try lvgl.createSpinner(row);
const spin = try lvgl.Spinner.new(row);
spin.setWidth(sym_width);
const lb = try lvgl.createLabelFmt(row, &buf, "{s}", .{name}, .{ .long_mode = .dot });
const lb = try lvgl.Label.newFmt(row, &buf, "{s}", .{name}, .{ .long_mode = .dot });
lb.flexGrow(1);
}
}

@ -27,18 +27,15 @@ pub fn init() !void {
}
export fn nm_create_info_panel(parent: *lvgl.LvObj) c_int {
createInfoPanel(parent) catch |err| {
createInfoPanel(lvgl.Container{ .lvobj = parent }) catch |err| {
logger.err("createInfoPanel: {any}", .{err});
return -1;
};
return 0;
}
fn createInfoPanel(parent: *lvgl.LvObj) !void {
parent.flexFlow(.column);
parent.flexAlign(.start, .start, .start);
fn createInfoPanel(cont: lvgl.Container) !void {
const flex = cont.flex(.column, .{});
var buf: [100]u8 = undefined;
const sver = try std.fmt.bufPrintZ(&buf, "GUI version: {any}", .{buildopts.semver});
_ = try lvgl.createLabel(parent, sver, .{});
_ = try lvgl.Label.newFmt(flex, &buf, "GUI version: {any}", .{buildopts.semver}, .{});
}

@ -10,31 +10,30 @@ const logger = std.log.scoped(.ui);
/// unsafe for concurrent use.
pub fn topdrop(onoff: enum { show, remove }) void {
// a static construct: there can be only one global topdrop.
// see https://ziglang.org/documentation/master/#Static-Local-Variables
// https://ziglang.org/documentation/master/#Static-Local-Variables
const S = struct {
var lv_obj: ?*lvgl.LvObj = null;
var top: ?lvgl.Container = null;
};
switch (onoff) {
.show => {
if (S.lv_obj != null) {
if (S.top != null) {
return;
}
const o = lvgl.createTopObject() catch |err| {
logger.err("topdrop: lvgl.createTopObject: {any}", .{err});
const top = lvgl.Container.newTop() catch |err| {
logger.err("topdrop: lvgl.Container.newTop: {any}", .{err});
return;
};
o.setFlag(.on, .ignore_layout);
o.resizeToMax();
o.setBackgroundColor(lvgl.Black, .{});
S.lv_obj = o;
lvgl.displayRedraw();
top.setFlag(.ignore_layout);
top.resizeToMax();
top.setBackgroundColor(lvgl.Black, .{});
S.top = top;
lvgl.redraw();
},
.remove => {
if (S.lv_obj) |o| {
o.destroy();
S.lv_obj = null;
if (S.top) |top| {
top.destroy();
S.top = null;
}
},
}
@ -53,51 +52,46 @@ pub const ModalButtonCallbackFn = *const fn (index: usize) void;
///
/// note: the cb callback must have @alignOf(ModalbuttonCallbackFn) alignment.
pub fn modal(title: [*:0]const u8, text: [*:0]const u8, btns: []const [*:0]const u8, cb: ModalButtonCallbackFn) !void {
const win = try lvgl.createWindow(null, 60, title);
errdefer win.winobj.destroy(); // also deletes all children created below
win.winobj.setUserdata(cb);
const win = try lvgl.Window.newTop(60, title);
errdefer win.destroy(); // also deletes all children created below
win.setUserdata(cb);
const wincont = win.content();
wincont.flexFlow(.column);
wincont.flexAlign(.start, .center, .center);
const msg = try lvgl.createLabel(wincont, text, .{ .pos = .center });
msg.setWidth(lvgl.displayHoriz() - 100);
const wincont = win.content().flex(.column, .{ .cross = .center, .track = .center });
const msg = try lvgl.Label.new(wincont, text, .{ .pos = .center });
msg.setWidth(lvgl.LvDisp.horiz() - 100);
msg.flexGrow(1);
const btncont = try lvgl.createFlexObject(wincont, .row);
btncont.removeBackgroundStyle();
btncont.padColumnDefault();
btncont.flexAlign(.center, .center, .center);
btncont.setWidth(lvgl.displayHoriz() - 40);
// buttons container
const btncont = try lvgl.FlexLayout.new(wincont, .row, .{ .all = .center });
btncont.setWidth(lvgl.LvDisp.horiz() - 40);
btncont.setHeightToContent();
// leave 5% as an extra spacing.
const btnwidth = lvgl.sizePercent(try std.math.divFloor(i16, 95, @truncate(u8, btns.len)));
for (btns) |btext, i| {
const btn = try lvgl.createButton(btncont, btext);
const btn = try lvgl.TextButton.new(btncont, btext);
btn.setFlag(.event_bubble);
btn.setFlag(.user1); // .user1 indicates actionable button in callback
btn.setUserdata(@intToPtr(?*anyopaque, i)); // button index in callback
btn.setWidth(btnwidth);
btn.setFlag(.on, .event_bubble);
btn.setFlag(.on, .user1); // .user1 indicates actionable button in callback
if (i == 0) {
btn.addStyle(lvgl.nm_style_btn_red(), .{});
}
btn.setUserdata(@intToPtr(?*anyopaque, i)); // button index in callback
}
_ = btncont.on(.click, nm_modal_callback, win.winobj);
_ = btncont.on(.click, nm_modal_callback, win.lvobj);
}
export fn nm_modal_callback(e: *lvgl.LvEvent) void {
if (e.userdata()) |event_data| {
const target = e.target();
if (!target.hasFlag(.user1)) { // .user1 is set by modal fn
if (e.userdata()) |edata| {
const target = lvgl.Container{ .lvobj = e.target() }; // type doesn't really matter
if (!target.hasFlag(.user1)) { // .user1 is set in modal setup
return;
}
const btn_index = @ptrToInt(target.userdata());
const winobj = @ptrCast(*lvgl.LvObj, event_data);
// guaranteed to be aligned due to cb arg in modal fn.
const cb = @ptrCast(ModalButtonCallbackFn, @alignCast(@alignOf(ModalButtonCallbackFn), winobj.userdata()));
winobj.destroy();
const win = lvgl.Window{ .lvobj = @ptrCast(*lvgl.LvObj, edata) };
const cb = @ptrCast(ModalButtonCallbackFn, @alignCast(@alignOf(ModalButtonCallbackFn), win.userdata()));
win.destroy();
cb(btn_index);
}
}