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. // can happen with a fresh connection while dhcp is still in progress.
if (report.wifi_ssid != null and report.ipaddrs.len == 0) { 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) // 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); t.setRepeatCount(1);
} else |err| { } else |err| {
logger.err("network status timer failed: {any}", .{err}); logger.err("network status timer failed: {any}", .{err});
@ -293,8 +293,8 @@ pub fn main() anyerror!void {
// run idle timer indefinitely. // run idle timer indefinitely.
// continue on failure: screen standby won't work at the worst. // continue on failure: screen standby won't work at the worst.
_ = lvgl.createTimer(nm_check_idle_time, 2000, null) catch |err| { _ = lvgl.LvTimer.new(nm_check_idle_time, 2000, null) catch |err| {
logger.err("lvgl.CreateTimer(idle check): {any}", .{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; return &style_btn_red;
} }
extern lv_style_t *nm_style_title()
{
return &style_title;
}
static void textarea_event_cb(lv_event_t *e) static void textarea_event_cb(lv_event_t *e)
{ {
lv_obj_t *textarea = lv_event_get_target(e); lv_obj_t *textarea = lv_event_get_target(e);

@ -46,6 +46,14 @@ pub fn loopCycle() u32 {
/// represents lv_timer_t in C. /// represents lv_timer_t in C.
pub const LvTimer = opaque { pub const LvTimer = opaque {
/// timer callback signature.
pub const Callback = *const fn (timer: *LvTimer) callconv(.C) void;
/// creates a new timer with indefinite repeat count.
pub fn new(f: Callback, period_ms: u32, userdata: ?*anyopaque) !*LvTimer {
return lv_timer_create(f, period_ms, userdata) orelse error.OutOfMemory;
}
pub fn destroy(self: *LvTimer) void { pub fn destroy(self: *LvTimer) void {
lv_timer_del(self); lv_timer_del(self);
} }
@ -57,14 +65,6 @@ pub const LvTimer = opaque {
} }
}; };
/// creates a new timer with indefinite repeat count.
pub fn createTimer(f: TimerCallback, period_ms: u32, userdata: ?*anyopaque) !*LvTimer {
return lv_timer_create(f, period_ms, userdata) orelse error.OutOfMemory;
}
/// a timer callback signature.
pub const TimerCallback = *const fn (timer: *LvTimer) callconv(.C) void;
/// represents lv_indev_t in C, an input device such as touchscreen or a keyboard. /// represents lv_indev_t in C, an input device such as touchscreen or a keyboard.
pub const LvIndev = opaque { pub const LvIndev = opaque {
pub fn first() ?*LvIndev { pub fn first() ?*LvIndev {
@ -80,32 +80,17 @@ pub const LvIndev = opaque {
} }
}; };
/// a zig representation of lv_event_t, required by all event callbacks. /// represents lv_event_t in C, required by all event callbacks.
pub const LvEvent = opaque { pub const LvEvent = opaque {
pub fn code(self: *LvEvent) EventCode { /// event callback, equivalent of lv_event_cb_t.
return lv_event_get_code(self); pub const Callback = *const fn (e: *LvEvent) callconv(.C) void;
}
/// returns the original event target irrespective of ObjFlag.event_bubble flag /// event descriptor returned from a callback setup.
/// on the object which generated the event. pub const Descriptor = opaque {};
pub fn target(self: *LvEvent) *LvObj {
return lv_event_get_target(self);
}
/// returns user data provided at the time of a callback setup, for example LvObj.on. /// all possible codes for an event to trigger a function call,
pub fn userdata(self: *LvEvent) ?*anyopaque { /// equivalent to lv_event_code_t.
return lv_event_get_user_data(self); pub const Code = enum(c.lv_event_code_t) {
}
};
/// event callback, equivalent of lv_event_cb_t.
pub const LvEventCallback = *const fn (e: *LvEvent) callconv(.C) void;
/// event descriptor returned from a callback setup.
pub const LvEventDescr = opaque {};
/// all possible codes for an event to trigger a function call,
/// equivalent to lv_event_code_t.
pub const EventCode = enum(c.lv_event_code_t) {
all = c.LV_EVENT_ALL, all = c.LV_EVENT_ALL,
/// input device events /// input device events
@ -159,23 +144,42 @@ pub const EventCode = enum(c.lv_event_code_t) {
style_changed = c.LV_EVENT_STYLE_CHANGED, // object's style has changed style_changed = c.LV_EVENT_STYLE_CHANGED, // object's style has changed
layout_changed = c.LV_EVENT_LAYOUT_CHANGED, // the children position has changed due to a layout recalculation layout_changed = c.LV_EVENT_LAYOUT_CHANGED, // the children position has changed due to a layout recalculation
get_self_size = c.LV_EVENT_GET_SELF_SIZE, // get the internal size of a widget get_self_size = c.LV_EVENT_GET_SELF_SIZE, // get the internal size of a widget
};
/// returns the code of the triggered event.
pub fn code(self: *LvEvent) Code {
return lv_event_get_code(self);
}
/// returns the original event target irrespective of ObjFlag.event_bubble flag
/// on the object which generated the event.
pub fn target(self: *LvEvent) *LvObj {
return lv_event_get_target(self);
}
/// returns user data provided at the time of a callback setup, for example LvObj.on.
pub fn userdata(self: *LvEvent) ?*anyopaque {
return lv_event_get_user_data(self);
}
}; };
/// represents lv_disp_t in C. /// represents lv_disp_t in C.
pub const LvDisp = opaque {}; pub const LvDisp = opaque {
/// returns display horizontal resolution.
/// returns display horizontal resolution. /// the display must be already initialized.
pub fn displayHoriz() Coord { pub fn horiz() Coord {
return lv_disp_get_hor_res(null); return lv_disp_get_hor_res(null);
} }
/// returns display vertical resolution. /// returns display vertical resolution.
pub fn displayVert() Coord { /// the display must be already initialized.
pub fn vert() Coord {
return lv_disp_get_ver_res(null); return lv_disp_get_ver_res(null);
} }
};
/// forces redraw of dirty areas. /// forces redraw of dirty areas.
pub fn displayRedraw() void { pub fn redraw() void {
lv_refr_now(null); lv_refr_now(null);
} }
@ -190,38 +194,18 @@ pub fn idleTime() u32 {
return lv_disp_get_inactive_time(null); return lv_disp_get_inactive_time(null);
} }
/// returns active screen of the default display.
pub fn activeScreen() !*LvObj {
return lv_disp_get_scr_act(null) orelse error.NoDisplay;
}
/// creates a new screen on default display. essentially same as lv_obj_create with null parent.
/// the display must be already initialized.
pub fn createScreen() !*LvObj {
if (lv_obj_create(null)) |o| {
return o;
} else {
return error.OutOfMemory;
}
}
/// makes a screen s active.
pub fn loadScreen(s: *LvObj) void {
lv_disp_load_scr(s);
}
/// represents lv_style_t in C. /// represents lv_style_t in C.
pub const LvStyle = opaque {}; pub const LvStyle = opaque {
/// indicates which parts and in which states to apply a style to an object.
/// indicates which parts and in which states to apply a style to an object. pub const Selector = struct {
pub const StyleSelector = struct {
state: State = .default,
part: Part = .main, part: Part = .main,
state: State = .default,
/// produce an int value suitable for lv_xxx functions. /// produces an int value suitable for lv_xxx functions.
fn value(self: StyleSelector) c.lv_style_selector_t { fn value(self: Selector) c.lv_style_selector_t {
return @enumToInt(self.part) | @enumToInt(self.state); return @enumToInt(self.part) | @enumToInt(self.state);
} }
};
}; };
/// a simplified color type compatible with LVGL which defines lv_color_t /// a simplified color type compatible with LVGL which defines lv_color_t
@ -299,202 +283,465 @@ pub const Palette = enum(c.lv_palette_t) {
blue_grey = c.LV_PALETTE_BLUE_GREY, blue_grey = c.LV_PALETTE_BLUE_GREY,
grey = c.LV_PALETTE_GREY, grey = c.LV_PALETTE_GREY,
none = c.LV_PALETTE_NONE, none = c.LV_PALETTE_NONE,
};
/// lightening or darkening levels of the main palette colors. /// lightening or darkening levels of the main palette colors.
pub const PaletteModLevel = enum(u8) { pub const ModLevel = enum(u8) {
level1 = 1, level1 = 1,
level2 = 2, level2 = 2,
level3 = 3, level3 = 3,
level4 = 4, level4 = 4,
level5 = 5, level5 = 5,
}; };
/// returns main color from the predefined palette. /// returns main color from the predefined palette.
pub inline fn paletteMain(p: Palette) Color { pub inline fn main(p: Palette) Color {
return lv_palette_main(@enumToInt(p)); return lv_palette_main(@enumToInt(p));
} }
/// makes the main color from the predefined palette lighter according to the /// makes the main color from the predefined palette lighter according to the
/// specified level. /// specified level.
pub inline fn paletteLighten(p: Palette, l: PaletteModLevel) Color { pub inline fn lighten(p: Palette, l: ModLevel) Color {
return lv_palette_lighten(@enumToInt(p), @enumToInt(l)); return lv_palette_lighten(@enumToInt(p), @enumToInt(l));
} }
/// makes the main color from the predefined palette darker according to the /// makes the main color from the predefined palette darker according to the
/// specified level. /// specified level.
pub inline fn paletteDarken(p: Palette, l: PaletteModLevel) Color { pub inline fn darken(p: Palette, l: ModLevel) Color {
return lv_palette_darken(@enumToInt(p), @enumToInt(l)); return lv_palette_darken(@enumToInt(p), @enumToInt(l));
} }
};
/// represents lv_obj_t type in C. /// a set of methods applicable to every kind of lv_xxx object.
pub const LvObj = opaque { pub const BaseObjMethods = struct {
/// deallocates all resources used by the object, including its children. /// deallocates all resources used by the object, including its children.
/// user data pointers are untouched. /// user data pointers are untouched.
pub fn destroy(self: *LvObj) void { pub fn destroy(self: anytype) void {
lv_obj_del(self); lv_obj_del(self.lvobj);
} }
/// deallocates all resources used by the object's children. /// deallocates all resources used by the object's children.
pub fn deleteChildren(self: *LvObj) void { pub fn deleteChildren(self: anytype) void {
lv_obj_clean(self); lv_obj_clean(self.lvobj);
}
/// creates a new event handler where cb is called upon event with the filter code.
/// to make cb called on any event, use EventCode.all filter.
/// multiple event handlers are called in the same order as they were added.
/// the user data pointer udata is available in a handler using LvEvent.userdata fn.
pub fn on(self: *LvObj, filter: EventCode, cb: LvEventCallback, udata: ?*anyopaque) *LvEventDescr {
return lv_obj_add_event_cb(self, cb, filter, udata);
}
/// sets label text to a new value.
pub fn setLabelText(self: *LvObj, text: [*:0]const u8) void {
lv_label_set_text(self, text);
} }
/// sets or clears an object flag. /// sets or clears an object flag.
pub fn setFlag(self: *LvObj, onoff: enum { on, off }, v: ObjFlag) void { pub fn setFlag(self: anytype, v: LvObj.Flag) void {
switch (onoff) { lv_obj_add_flag(self.lvobj, @enumToInt(v));
.on => lv_obj_add_flag(self, @enumToInt(v)),
.off => lv_obj_clear_flag(self, @enumToInt(v)),
} }
pub fn clearFlag(self: anytype, v: LvObj.Flag) void {
lv_obj_clear_flag(self.lvobj, @enumToInt(v));
} }
/// reports whether the object has v flag set. /// reports whether the object has v flag set.
pub fn hasFlag(self: *LvObj, v: ObjFlag) bool { pub fn hasFlag(self: anytype, v: LvObj.Flag) bool {
return lv_obj_has_flag(self, @enumToInt(v)); return lv_obj_has_flag(self.lvobj, @enumToInt(v));
} }
/// returns a user data pointer associated with the object. /// returns a user data pointer associated with the object.
pub fn userdata(self: *LvObj) ?*anyopaque { pub fn userdata(self: anytype) ?*anyopaque {
return nm_obj_userdata(self); return nm_obj_userdata(self.lvobj);
} }
/// associates an arbitrary data with the object. /// associates an arbitrary data with the object.
/// the pointer can be accessed using LvObj.userdata fn. /// the pointer can be accessed using LvObj.userdata fn.
pub fn setUserdata(self: *LvObj, data: ?*const anyopaque) void { pub fn setUserdata(self: anytype, data: ?*const anyopaque) void {
nm_obj_set_userdata(self, data); nm_obj_set_userdata(self.lvobj, data);
}
/// creates a new event handler where cb is called upon event with the filter code.
/// to make cb called on any event, use EventCode.all filter.
/// multiple event handlers are called in the same order as they were added.
/// the user data pointer udata is available in a handler using LvEvent.userdata fn.
pub fn on(self: anytype, filter: LvEvent.Code, cb: LvEvent.Callback, udata: ?*anyopaque) *LvEvent.Descriptor {
return lv_obj_add_event_cb(self.lvobj, cb, filter, udata);
} }
};
/// methods applicable to visible objects like labels, buttons and containers.
pub const WidgetMethods = struct {
/// sets object horizontal length. /// sets object horizontal length.
pub fn setWidth(self: *LvObj, val: Coord) void { pub fn setWidth(self: anytype, val: Coord) void {
lv_obj_set_width(self, val); lv_obj_set_width(self.lvobj, val);
} }
/// sets object vertical length. /// sets object vertical length.
pub fn setHeight(self: *LvObj, val: Coord) void { pub fn setHeight(self: anytype, val: Coord) void {
lv_obj_set_height(self, val); lv_obj_set_height(self.lvobj, val);
} }
/// sets object height to its child contents. /// sets object height to its child contents.
pub fn setHeightToContent(self: *LvObj) void { pub fn setHeightToContent(self: anytype) void {
lv_obj_set_height(self, sizeContent); lv_obj_set_height(self.lvobj, sizeContent);
} }
/// sets both width and height to 100%. /// sets both width and height to 100%.
pub fn resizeToMax(self: *LvObj) void { pub fn resizeToMax(self: anytype) void {
lv_obj_set_size(self, sizePercent(100), sizePercent(100)); lv_obj_set_size(self.lvobj, sizePercent(100), sizePercent(100));
} }
/// selects which side to pad in setPad func. /// selects which side to pad in setPad func.
pub const PadSelector = enum { all, left, right, top, bottom, row, column }; pub const PadSelector = enum { all, left, right, top, bottom, row, column };
/// adds a padding style to the object. /// adds a padding style to the object.
pub fn setPad(self: *LvObj, v: Coord, p: PadSelector, sel: StyleSelector) void { pub fn setPad(self: anytype, v: Coord, p: PadSelector, sel: LvStyle.Selector) void {
switch (p) { switch (p) {
.all => { .all => {
const vsel = sel.value(); const vsel = sel.value();
lv_obj_set_style_pad_left(self, v, vsel); lv_obj_set_style_pad_left(self.lvobj, v, vsel);
lv_obj_set_style_pad_right(self, v, vsel); lv_obj_set_style_pad_right(self.lvobj, v, vsel);
lv_obj_set_style_pad_top(self, v, vsel); lv_obj_set_style_pad_top(self.lvobj, v, vsel);
lv_obj_set_style_pad_bottom(self, v, vsel); lv_obj_set_style_pad_bottom(self.lvobj, v, vsel);
}, },
.left => lv_obj_set_style_pad_left(self, v, sel.value()), .left => lv_obj_set_style_pad_left(self.lvobj, v, sel.value()),
.right => lv_obj_set_style_pad_right(self, v, sel.value()), .right => lv_obj_set_style_pad_right(self.lvobj, v, sel.value()),
.top => lv_obj_set_style_pad_top(self, v, sel.value()), .top => lv_obj_set_style_pad_top(self.lvobj, v, sel.value()),
.bottom => lv_obj_set_style_pad_bottom(self, v, sel.value()), .bottom => lv_obj_set_style_pad_bottom(self.lvobj, v, sel.value()),
.row => lv_obj_set_style_pad_row(self, v, sel.value()), .row => lv_obj_set_style_pad_row(self.lvobj, v, sel.value()),
.column => lv_obj_set_style_pad_column(self, v, sel.value()), .column => lv_obj_set_style_pad_column(self.lvobj, v, sel.value()),
} }
} }
/// same as setPad .column but using a default constant to make flex layouts consistent.
pub fn padColumnDefault(self: *LvObj) void {
self.setPad(20, .column, .{});
}
/// aligns object to the center of its parent. /// aligns object to the center of its parent.
pub fn center(self: *LvObj) void { pub fn center(self: anytype) void {
self.posAlign(.center, 0, 0); self.posAlign(.center, 0, 0);
} }
/// aligns object position. the offset is relative to the specified alignment a. /// aligns object position. the offset is relative to the specified alignment a.
pub fn posAlign(self: *LvObj, a: PosAlign, xoffset: Coord, yoffset: Coord) void { pub fn posAlign(self: anytype, a: PosAlign, xoffset: Coord, yoffset: Coord) void {
lv_obj_align(self, @enumToInt(a), xoffset, yoffset); lv_obj_align(self.lvobj, @enumToInt(a), xoffset, yoffset);
}
/// sets flex layout flow on the object.
pub fn flexFlow(self: *LvObj, ff: FlexFlow) void {
lv_obj_set_flex_flow(self, @enumToInt(ff));
} }
/// sets flex layout growth property; same meaning as in CSS flex. /// sets flex layout growth property; same meaning as in CSS flex.
pub fn flexGrow(self: *LvObj, val: u8) void { pub fn flexGrow(self: anytype, val: u8) void {
lv_obj_set_flex_grow(self, val); lv_obj_set_flex_grow(self.lvobj, val);
}
/// sets flex layout alignments.
pub fn flexAlign(self: *LvObj, main: FlexAlign, cross: FlexAlignCross, track: FlexAlign) void {
lv_obj_set_flex_align(self, @enumToInt(main), @enumToInt(cross), @enumToInt(track));
} }
/// adds a style to the object or one of its parts/states based on the selector. /// adds a style to the object or one of its parts/states based on the selector.
pub fn addStyle(self: *LvObj, v: *LvStyle, sel: StyleSelector) void { pub fn addStyle(self: anytype, v: *LvStyle, sel: LvStyle.Selector) void {
lv_obj_add_style(self, v, sel.value()); lv_obj_add_style(self.lvobj, v, sel.value());
} }
/// removes all styles from the object. /// removes all styles from the object.
pub fn removeAllStyle(self: *LvObj) void { pub fn removeAllStyle(self: anytype) void {
const sel = StyleSelector{ .part = .any, .state = .any }; const sel = LvStyle.Selector{ .part = .any, .state = .any };
lv_obj_remove_style(self, null, sel.value()); lv_obj_remove_style(self.lvobj, null, sel.value());
} }
/// removes only the background styles from the object. /// removes only the background styles from the object.
pub fn removeBackgroundStyle(self: *LvObj) void { pub fn removeBackgroundStyle(self: anytype) void {
const sel = StyleSelector{ .part = .main, .state = .any }; const sel = LvStyle.Selector{ .part = .main, .state = .any };
lv_obj_remove_style(self, null, sel.value()); lv_obj_remove_style(self.lvobj, null, sel.value());
} }
/// sets a desired background color to objects parts/states. /// sets a desired background color to objects parts/states.
pub fn setBackgroundColor(self: *LvObj, v: Color, sel: StyleSelector) void { pub fn setBackgroundColor(self: anytype, v: Color, sel: LvStyle.Selector) void {
lv_obj_set_style_bg_color(self, v, sel.value()); lv_obj_set_style_bg_color(self.lvobj, v, sel.value());
}
};
/// a base layer object which all the other UI elements are placed onto.
/// there can be only one active screen at a time on a display.
pub const Screen = struct {
lvobj: *LvObj,
pub usingnamespace BaseObjMethods;
/// creates a new screen on default display.
/// the display must be already initialized.
pub fn new() !Screen {
const o = lv_obj_create(null) orelse return error.OutOfMemory;
return .{ .lvobj = o };
} }
/// sets the color of a text, typically a label object. /// returns active screen of the default display.
pub fn setTextColor(self: *LvObj, v: Color, sel: StyleSelector) void { pub fn active() !Screen {
lv_obj_set_style_text_color(self, v, sel.value()); const o = lv_disp_get_scr_act(null) orelse return error.NoDisplay;
return .{ .lvobj = o };
}
/// makes a screen active.
pub fn load(scr: Screen) void {
lv_disp_load_scr(scr.obj);
} }
}; };
pub fn createObject(parent: *LvObj) !*LvObj { /// used as a base parent for many other elements.
return lv_obj_create(parent) orelse error.OutOfMemory; pub const Container = struct {
} lvobj: *LvObj,
pub fn createFlexObject(parent: *LvObj, flow: FlexFlow) !*LvObj { pub usingnamespace BaseObjMethods;
var o = try createObject(parent); pub usingnamespace WidgetMethods;
o.flexFlow(flow);
return o;
}
pub fn createTopObject() !*LvObj { pub fn new(parent: anytype) !Container {
const o = lv_obj_create(parent.lvobj) orelse return error.OutOfMemory;
return .{ .lvobj = o };
}
/// creates a new container on the top level, above all others.
/// suitable for widgets like a popup window.
pub fn newTop() !Container {
const toplayer = lv_disp_get_layer_top(null); const toplayer = lv_disp_get_layer_top(null);
return lv_obj_create(toplayer) orelse error.OutOfMemory; return .{ .lvobj = toplayer };
} }
/// feature-flags controlling object's behavior. /// applies flex layout to the container.
/// OR'ed values are possible. pub fn flex(self: Container, flow: FlexLayout.Flow, opt: FlexLayout.AlignOpt) FlexLayout {
pub const ObjFlag = enum(c.lv_obj_flag_t) { return FlexLayout.adopt(self.lvobj, flow, opt);
}
};
/// same as Container but with flex layout of its children.
pub const FlexLayout = struct {
lvobj: *LvObj,
pub usingnamespace BaseObjMethods;
pub usingnamespace WidgetMethods;
/// layout flow variations.
pub const Flow = enum(c.lv_flex_flow_t) {
row = c.LV_FLEX_FLOW_ROW,
column = c.LV_FLEX_FLOW_COLUMN,
row_wrap = c.LV_FLEX_FLOW_ROW_WRAP,
row_reverse = c.LV_FLEX_FLOW_ROW_REVERSE,
row_wrap_reverse = c.LV_FLEX_FLOW_ROW_WRAP_REVERSE,
column_wrap = c.LV_FLEX_FLOW_COLUMN_WRAP,
column_reverse = c.LV_FLEX_FLOW_COLUMN_REVERSE,
column_wrap_reverse = c.LV_FLEX_FLOW_COLUMN_WRAP_REVERSE,
};
/// flex layout alignments.
pub const Align = enum(c.lv_flex_align_t) {
start = c.LV_FLEX_ALIGN_START,
end = c.LV_FLEX_ALIGN_END,
center = c.LV_FLEX_ALIGN_CENTER,
space_evenly = c.LV_FLEX_ALIGN_SPACE_EVENLY,
space_around = c.LV_FLEX_ALIGN_SPACE_AROUND,
space_between = c.LV_FLEX_ALIGN_SPACE_BETWEEN,
};
/// cross-direction alignment.
pub const AlignCross = enum(c.lv_flex_align_t) {
start = c.LV_FLEX_ALIGN_START,
end = c.LV_FLEX_ALIGN_END,
center = c.LV_FLEX_ALIGN_CENTER,
};
/// main, cross and track are similar to CSS flex concepts.
pub const AlignOpt = struct {
main: Align = .start,
cross: AlignCross = .start,
track: Align = .start,
all: ?Align = null, // overrides all 3 above
};
/// creates a new object with flex layout and some default padding.
pub fn new(parent: anytype, flow: Flow, opt: AlignOpt) !FlexLayout {
const obj = lv_obj_create(parent.lvobj) orelse return error.OutOfMemory;
// remove background style first. otherwise, it'll also remove flex.
const bgsel = LvStyle.Selector{ .part = .main, .state = .any };
lv_obj_remove_style(obj, null, bgsel.value());
const flex = adopt(obj, flow, opt);
flex.padColumnDefault();
return flex;
}
fn adopt(obj: *LvObj, flow: Flow, opt: AlignOpt) FlexLayout {
lv_obj_set_flex_flow(obj, @enumToInt(flow));
if (opt.all) |a| {
const v = @enumToInt(a);
lv_obj_set_flex_align(obj, v, v, v);
} else {
lv_obj_set_flex_align(obj, @enumToInt(opt.main), @enumToInt(opt.cross), @enumToInt(opt.track));
}
return .{ .lvobj = obj };
}
/// sets flex layout flow on the object.
pub fn setFlow(self: FlexLayout, ff: Flow) void {
lv_obj_set_flex_flow(self.lvobj, @enumToInt(ff));
}
/// sets flex layout alignments.
pub fn setAlign(self: FlexLayout, main: Align, cross: AlignCross, track: Align) void {
lv_obj_set_flex_align(self.lvobj, @enumToInt(main), @enumToInt(cross), @enumToInt(track));
}
/// same as setPad .column but using a default constant to make flex layouts consistent.
pub fn padColumnDefault(self: FlexLayout) void {
self.setPad(20, .column, .{});
}
};
pub const Window = struct {
lvobj: *LvObj,
pub usingnamespace BaseObjMethods;
pub usingnamespace WidgetMethods;
pub fn new(parent: anytype, header_height: i16, title: [*:0]const u8) !Window {
const lv_win = lv_win_create(parent.lvobj, header_height) orelse return error.OutOfMemory;
if (lv_win_add_title(lv_win, title) == null) {
return error.OutOfMemory;
}
return .{ .lvobj = lv_win };
}
/// creates a new window on top of all other elements.
pub fn newTop(header_height: i16, title: [*:0]const u8) !Window {
return new(try Screen.active(), header_height, title);
}
/// returns content part of the window, i.e. the main part below title.
pub fn content(self: Window) Container {
return .{ .lvobj = lv_win_get_content(self.lvobj) };
}
};
/// a custom element consisting of a flex container with a title-sized label
/// on top.
pub const Card = struct {
lvobj: *LvObj,
title: Label,
pub usingnamespace BaseObjMethods;
pub usingnamespace WidgetMethods;
pub fn new(parent: anytype, title: [*:0]const u8) !Card {
const flex = (try Container.new(parent)).flex(.column, .{});
flex.setHeightToContent();
flex.setWidth(sizePercent(100));
const tl = try Label.new(flex, title, .{});
tl.addStyle(nm_style_title(), .{});
return .{ .lvobj = flex.lvobj, .title = tl };
}
};
/// represents lv_label_t in C, a text label.
pub const Label = struct {
lvobj: *LvObj,
pub usingnamespace BaseObjMethods;
pub usingnamespace WidgetMethods;
pub const Opt = struct {
// LVGL defaults to .wrap
long_mode: ?enum(c.lv_label_long_mode_t) {
wrap = c.LV_LABEL_LONG_WRAP, // keep the object width, wrap the too long lines and expand the object height
dot = c.LV_LABEL_LONG_DOT, // keep the size and write dots at the end if the text is too long
scroll = c.LV_LABEL_LONG_SCROLL, // keep the size and roll the text back and forth
scroll_circular = c.LV_LABEL_LONG_SCROLL_CIRCULAR, // keep the size and roll the text circularly
clip = c.LV_LABEL_LONG_CLIP, // keep the size and clip the text out of it
} = null,
pos: ?PosAlign = null,
recolor: bool = false,
};
/// the text value is copied into a heap-allocated alloc.
pub fn new(parent: anytype, text: [*:0]const u8, opt: Opt) !Label {
var lv_label = lv_label_create(parent.lvobj) orelse return error.OutOfMemory;
//lv_label_set_text_static(lb, text); // static doesn't work with .dot
lv_label_set_text(lv_label, text);
//lv_obj_set_height(lb, sizeContent); // default
if (opt.long_mode) |m| {
lv_label_set_long_mode(lv_label, @enumToInt(m));
}
if (opt.pos) |p| {
lv_obj_align(lv_label, @enumToInt(p), 0, 0);
}
if (opt.recolor) {
lv_label_set_recolor(lv_label, true);
}
return .{ .lvobj = lv_label };
}
/// formats label text using std.fmt.format and the provided buffer.
/// the text is heap-dup'ed so no need to retain the buf.
pub fn newFmt(parent: anytype, buf: []u8, comptime format: []const u8, args: anytype, opt: Opt) !Label {
const text = try std.fmt.bufPrintZ(buf, format, args);
return new(parent, text, opt);
}
/// sets label text to a new value.
/// previous value is dealloc'ed.
pub fn setText(self: Label, text: [*:0]const u8) void {
lv_label_set_text(self.lvobj, text);
}
/// formats a new label text.
/// the buffer can be dropped once the function returns.
pub fn setTextFmt(self: Label, buf: []u8, comptime format: []const u8, args: anytype) !void {
var s = try std.fmt.bufPrintZ(buf, format, args);
self.setText(s);
}
/// sets label text color.
pub fn setColor(self: Label, v: Color, sel: LvStyle.Selector) void {
lv_obj_set_style_text_color(self.lvobj, v, sel.value());
}
};
/// a button with a child Label.
pub const TextButton = struct {
lvobj: *LvObj,
label: *LvObj,
pub usingnamespace BaseObjMethods;
pub usingnamespace WidgetMethods;
pub fn new(parent: anytype, text: [*:0]const u8) !TextButton {
const btn = lv_btn_create(parent.lvobj) orelse return error.OutOfMemory;
const label = try Label.new(Container{ .lvobj = btn }, text, .{ .long_mode = .dot, .pos = .center });
return .{ .lvobj = btn, .label = label.lvobj };
}
};
pub const Spinner = struct {
lvobj: *LvObj,
pub usingnamespace BaseObjMethods;
pub usingnamespace WidgetMethods;
pub fn new(parent: anytype) !Spinner {
const spin = lv_spinner_create(parent.lvobj, 1000, 60) orelse return error.OutOfMemory;
lv_obj_set_size(spin, 20, 20);
const ind: LvStyle.Selector = .{ .part = .indicator };
lv_obj_set_style_arc_width(spin, 4, ind.value());
return .{ .lvobj = spin };
}
};
/// represents lv_bar_t in C.
/// see https://docs.lvgl.io/8.3/widgets/core/bar.html for details.
pub const Bar = struct {
lvobj: *LvObj,
pub usingnamespace BaseObjMethods;
pub usingnamespace WidgetMethods;
/// creates a horizontal bar with the default 0-100 range.
pub fn new(parent: anytype) !Bar {
const lv_bar = lv_bar_create(parent.lvobj) orelse return error.OutOfMemory;
lv_obj_set_size(lv_bar, 200, 20);
return .{ .lvobj = lv_bar };
}
/// sets a new value of a bar current position.
pub fn setValue(self: Bar, val: i32) void {
lv_bar_set_value(self.lvobj, val, c.LV_ANIM_OFF);
}
/// sets minimum and max values of the bar range.
pub fn setRange(self: Bar, min: i32, max: i32) void {
lv_bar_set_range(self.lvobj, min, max);
}
};
/// represents lv_obj_t type in C.
pub const LvObj = opaque {
/// feature-flags controlling object's behavior.
/// OR'ed values are possible.
pub const Flag = enum(c.lv_obj_flag_t) {
hidden = c.LV_OBJ_FLAG_HIDDEN, // make the object hidden, like it wasn't there at all hidden = c.LV_OBJ_FLAG_HIDDEN, // make the object hidden, like it wasn't there at all
clickable = c.LV_OBJ_FLAG_CLICKABLE, // make the object clickable by the input devices clickable = c.LV_OBJ_FLAG_CLICKABLE, // make the object clickable by the input devices
focusable = c.LV_OBJ_FLAG_CLICK_FOCUSABLE, // add focused state to the object when clicked focusable = c.LV_OBJ_FLAG_CLICK_FOCUSABLE, // add focused state to the object when clicked
@ -520,6 +767,7 @@ pub const ObjFlag = enum(c.lv_obj_flag_t) {
user2 = c.LV_OBJ_FLAG_USER_2, // custom flag, free to use by user user2 = c.LV_OBJ_FLAG_USER_2, // custom flag, free to use by user
user3 = c.LV_OBJ_FLAG_USER_3, // custom flag, free to use by user user3 = c.LV_OBJ_FLAG_USER_3, // custom flag, free to use by user
user4 = c.LV_OBJ_FLAG_USER_4, // custom flag, free to use by user user4 = c.LV_OBJ_FLAG_USER_4, // custom flag, free to use by user
};
}; };
/// possible states of a widget, equivalent to lv_state_t in C. /// possible states of a widget, equivalent to lv_state_t in C.
@ -614,112 +862,14 @@ pub const PosAlign = enum(c.lv_align_t) {
out_right_bottom = c.LV_ALIGN_OUT_RIGHT_BOTTOM, out_right_bottom = c.LV_ALIGN_OUT_RIGHT_BOTTOM,
}; };
/// flex layout alignments.
pub const FlexAlign = enum(c.lv_flex_align_t) {
start = c.LV_FLEX_ALIGN_START,
end = c.LV_FLEX_ALIGN_END,
center = c.LV_FLEX_ALIGN_CENTER,
space_evenly = c.LV_FLEX_ALIGN_SPACE_EVENLY,
space_around = c.LV_FLEX_ALIGN_SPACE_AROUND,
space_between = c.LV_FLEX_ALIGN_SPACE_BETWEEN,
};
/// flex layout cross-axis alignments.
pub const FlexAlignCross = enum(c.lv_flex_align_t) {
start = c.LV_FLEX_ALIGN_START,
end = c.LV_FLEX_ALIGN_END,
center = c.LV_FLEX_ALIGN_CENTER,
};
/// flex layout flow variations.
pub const FlexFlow = enum(c.lv_flex_flow_t) {
row = c.LV_FLEX_FLOW_ROW,
column = c.LV_FLEX_FLOW_COLUMN,
row_wrap = c.LV_FLEX_FLOW_ROW_WRAP,
row_reverse = c.LV_FLEX_FLOW_ROW_REVERSE,
row_wrap_reverse = c.LV_FLEX_FLOW_ROW_WRAP_REVERSE,
column_wrap = c.LV_FLEX_FLOW_COLUMN_WRAP,
column_reverse = c.LV_FLEX_FLOW_COLUMN_REVERSE,
column_wrap_reverse = c.LV_FLEX_FLOW_COLUMN_WRAP_REVERSE,
};
/// if parent is null, uses lv_scr_act.
pub fn createWindow(parent: ?*LvObj, header_height: i16, title: [*:0]const u8) !Window {
const pobj = parent orelse try activeScreen();
const winobj = lv_win_create(pobj, header_height) orelse return error.OutOfMemory;
if (lv_win_add_title(winobj, title) == null) {
return error.OutOfMemory;
}
return .{ .winobj = winobj };
}
pub const Window = struct {
winobj: *LvObj,
pub fn content(self: Window) *LvObj {
return lv_win_get_content(self.winobj);
}
};
pub const CreateLabelOpt = struct {
// LVGL defaults to .wrap
long_mode: ?enum(c.lv_label_long_mode_t) {
wrap = c.LV_LABEL_LONG_WRAP, // keep the object width, wrap the too long lines and expand the object height
dot = c.LV_LABEL_LONG_DOT, // keep the size and write dots at the end if the text is too long
scroll = c.LV_LABEL_LONG_SCROLL, // keep the size and roll the text back and forth
scroll_circular = c.LV_LABEL_LONG_SCROLL_CIRCULAR, // keep the size and roll the text circularly
clip = c.LV_LABEL_LONG_CLIP, // keep the size and clip the text out of it
} = null,
pos: ?PosAlign = null,
};
/// creates a new label object.
/// the text is heap-duplicated for the lifetime of the object and free'ed automatically.
pub fn createLabel(parent: *LvObj, text: [*:0]const u8, opt: CreateLabelOpt) !*LvObj {
var lb = lv_label_create(parent) orelse return error.OutOfMemory;
//lv_label_set_text_static(lb, text); // static doesn't work with .dot
lv_label_set_text(lb, text);
lv_label_set_recolor(lb, true);
//lv_obj_set_height(lb, sizeContent); // default
if (opt.long_mode) |m| {
lv_label_set_long_mode(lb, @enumToInt(m));
}
if (opt.pos) |p| {
lb.posAlign(p, 0, 0);
}
return lb;
}
/// formats label text using std.fmt.format and the provided buffer.
/// a label object is then created with the resulting text using createLabel.
/// the text is heap-dup'ed so no need to retain buf. see createLabel.
pub fn createLabelFmt(parent: *LvObj, buf: []u8, comptime format: []const u8, args: anytype, opt: CreateLabelOpt) !*LvObj {
const text = try std.fmt.bufPrintZ(buf, format, args);
return createLabel(parent, text, opt);
}
pub fn createButton(parent: *LvObj, label: [*:0]const u8) !*LvObj {
const btn = lv_btn_create(parent) orelse return error.OutOfMemory;
_ = try createLabel(btn, label, .{ .long_mode = .dot, .pos = .center });
return btn;
}
/// creates a spinner object with hardcoded dimensions and animation speed
/// used througout the GUI.
pub fn createSpinner(parent: *LvObj) !*LvObj {
const spin = lv_spinner_create(parent, 1000, 60) orelse return error.OutOfMemory;
lv_obj_set_size(spin, 20, 20);
const ind: StyleSelector = .{ .part = .indicator };
lv_obj_set_style_arc_width(spin, 4, ind.value());
return spin;
}
// ========================================================================== // ==========================================================================
// imports from nakamochi custom C code that extends LVGL // imports from nakamochi custom C code that extends LVGL
// ========================================================================== // ==========================================================================
/// returns a red button style. /// returns a red button style.
pub extern fn nm_style_btn_red() *LvStyle; // TODO: make it private pub extern fn nm_style_btn_red() *LvStyle; // TODO: make it private
/// returns a title style with a larger font.
pub extern fn nm_style_title() *LvStyle; // TODO: make it private
// the "native" lv_obj_set/get user_data are static inline, so make our own funcs. // the "native" lv_obj_set/get user_data are static inline, so make our own funcs.
extern "c" fn nm_obj_userdata(obj: *LvObj) ?*anyopaque; extern "c" fn nm_obj_userdata(obj: *LvObj) ?*anyopaque;
@ -744,17 +894,17 @@ extern fn lv_indev_get_next(indev: ?*LvIndev) ?*LvIndev;
/// timer handler is the busy-wait loop in LVGL. /// timer handler is the busy-wait loop in LVGL.
/// returns period after which it is to be run again, in ms. /// returns period after which it is to be run again, in ms.
extern fn lv_timer_handler() u32; extern fn lv_timer_handler() u32;
extern fn lv_timer_create(callback: TimerCallback, period_ms: u32, userdata: ?*anyopaque) ?*LvTimer; extern fn lv_timer_create(callback: LvTimer.Callback, period_ms: u32, userdata: ?*anyopaque) ?*LvTimer;
extern fn lv_timer_del(timer: *LvTimer) void; extern fn lv_timer_del(timer: *LvTimer) void;
extern fn lv_timer_set_repeat_count(timer: *LvTimer, n: i32) void; extern fn lv_timer_set_repeat_count(timer: *LvTimer, n: i32) void;
// events -------------------------------------------------------------------- // events --------------------------------------------------------------------
extern fn lv_event_get_code(e: *LvEvent) EventCode; extern fn lv_event_get_code(e: *LvEvent) LvEvent.Code;
extern fn lv_event_get_current_target(e: *LvEvent) *LvObj; extern fn lv_event_get_current_target(e: *LvEvent) *LvObj;
extern fn lv_event_get_target(e: *LvEvent) *LvObj; extern fn lv_event_get_target(e: *LvEvent) *LvObj;
extern fn lv_event_get_user_data(e: *LvEvent) ?*anyopaque; extern fn lv_event_get_user_data(e: *LvEvent) ?*anyopaque;
extern fn lv_obj_add_event_cb(obj: *LvObj, cb: LvEventCallback, filter: EventCode, userdata: ?*anyopaque) *LvEventDescr; extern fn lv_obj_add_event_cb(obj: *LvObj, cb: LvEvent.Callback, filter: LvEvent.Code, userdata: ?*anyopaque) *LvEvent.Descriptor;
// display and screen functions ---------------------------------------------- // display and screen functions ----------------------------------------------
@ -842,6 +992,10 @@ extern fn lv_label_set_recolor(label: *LvObj, enable: bool) void;
extern fn lv_spinner_create(parent: *LvObj, speed_ms: u32, arc_deg: u32) ?*LvObj; extern fn lv_spinner_create(parent: *LvObj, speed_ms: u32, arc_deg: u32) ?*LvObj;
extern fn lv_bar_create(parent: *LvObj) ?*LvObj;
extern fn lv_bar_set_value(bar: *LvObj, value: i32, c.lv_anim_enable_t) void;
extern fn lv_bar_set_range(bar: *LvObj, min: i32, max: i32) void;
extern fn lv_win_create(parent: *LvObj, header_height: c.lv_coord_t) ?*LvObj; extern fn lv_win_create(parent: *LvObj, header_height: c.lv_coord_t) ?*LvObj;
extern fn lv_win_add_title(win: *LvObj, title: [*:0]const u8) ?*LvObj; extern fn lv_win_add_title(win: *LvObj, title: [*:0]const u8) ?*LvObj;
extern fn lv_win_get_content(win: *LvObj) *LvObj; extern fn lv_win_get_content(win: *LvObj) *LvObj;

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

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

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