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
parent
746b179478
commit
7d1ab5cb78
|
@ -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);
|
||||
|
|
900
src/ui/lvgl.zig
900
src/ui/lvgl.zig
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);
|
||||
}
|
||||
}
|
||||
|
|
Reference in New Issue