From 7d1ab5cb78ea3b13a47d9f7ef37653a61975f2cd Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 6 Aug 2023 22:56:05 +0200 Subject: [PATCH] ui/lvgl: improve UI element types in zig 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. --- src/ngui.zig | 6 +- src/ui/c/ui.c | 5 + src/ui/lvgl.zig | 878 ++++++++++++++++++++++++++------------------ src/ui/poweroff.zig | 45 +-- src/ui/ui.zig | 11 +- src/ui/widget.zig | 72 ++-- 6 files changed, 580 insertions(+), 437 deletions(-) diff --git a/src/ngui.zig b/src/ngui.zig index 747306d..0641a1f 100644 --- a/src/ngui.zig +++ b/src/ngui.zig @@ -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}); }; { diff --git a/src/ui/c/ui.c b/src/ui/c/ui.c index b87a291..66d6dff 100644 --- a/src/ui/c/ui.c +++ b/src/ui/c/ui.c @@ -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); diff --git a/src/ui/lvgl.zig b/src/ui/lvgl.zig index 77bada1..9fcd2dd 100644 --- a/src/ui/lvgl.zig +++ b/src/ui/lvgl.zig @@ -46,6 +46,14 @@ pub fn loopCycle() u32 { /// represents lv_timer_t in C. 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 { 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. pub const LvIndev = opaque { pub fn first() ?*LvIndev { @@ -80,9 +80,74 @@ 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 fn code(self: *LvEvent) EventCode { + /// event callback, equivalent of lv_event_cb_t. + pub const Callback = *const fn (e: *LvEvent) callconv(.C) void; + + /// event descriptor returned from a callback setup. + pub const Descriptor = opaque {}; + + /// all possible codes for an event to trigger a function call, + /// equivalent to lv_event_code_t. + pub const Code = enum(c.lv_event_code_t) { + all = c.LV_EVENT_ALL, + + /// input device events + press = c.LV_EVENT_PRESSED, // the object has been pressed + pressing = c.LV_EVENT_PRESSING, // the object is being pressed (called continuously while pressing) + press_lost = c.LV_EVENT_PRESS_LOST, // the object is still being pressed but slid cursor/finger off of the object + short_click = c.LV_EVENT_SHORT_CLICKED, // the object was pressed for a short period of time, then released it. not called if scrolled. + long_press = c.LV_EVENT_LONG_PRESSED, // object has been pressed for at least `long_press_time`. not called if scrolled. + long_press_repeat = c.LV_EVENT_LONG_PRESSED_REPEAT, // called after `long_press_time` in every `long_press_repeat_time` ms. not called if scrolled. + click = c.LV_EVENT_CLICKED, // called on release if not scrolled (regardless to long press) + release = c.LV_EVENT_RELEASED, // called in every cases when the object has been released + scroll_begin = c.LV_EVENT_SCROLL_BEGIN, // scrolling begins. the event parameter is a pointer to the animation of the scroll. can be modified + scroll_end = c.LV_EVENT_SCROLL_END, // scrolling ends + scroll = c.LV_EVENT_SCROLL, // scrolling + gesture = c.LV_EVENT_GESTURE, // a gesture is detected. get the gesture with lv_indev_get_gesture_dir(lv_indev_get_act()) + key = c.LV_EVENT_KEY, // a key is sent to the object. get the key with lv_indev_get_key(lv_indev_get_act()) + focus = c.LV_EVENT_FOCUSED, // the object is focused + defocus = c.LV_EVENT_DEFOCUSED, // the object is defocused + leave = c.LV_EVENT_LEAVE, // the object is defocused but still selected + hit_test = c.LV_EVENT_HIT_TEST, // perform advanced hit-testing + + /// drawing events + cover_check = c.LV_EVENT_COVER_CHECK, // check if the object fully covers an area. the event parameter is lv_cover_check_info_t * + refr_ext_draw_size = c.LV_EVENT_REFR_EXT_DRAW_SIZE, // get the required extra draw area around the object (e.g. for shadow). the event parameter is lv_coord_t * to store the size. + draw_main_begin = c.LV_EVENT_DRAW_MAIN_BEGIN, // starting the main drawing phase + draw_main = c.LV_EVENT_DRAW_MAIN, // perform the main drawing + draw_main_end = c.LV_EVENT_DRAW_MAIN_END, // finishing the main drawing phase + draw_post_begin = c.LV_EVENT_DRAW_POST_BEGIN, // starting the post draw phase (when all children are drawn) + draw_post = c.LV_EVENT_DRAW_POST, // perform the post draw phase (when all children are drawn) + draw_post_end = c.LV_EVENT_DRAW_POST_END, // finishing the post draw phase (when all children are drawn) + draw_part_begin = c.LV_EVENT_DRAW_PART_BEGIN, // starting to draw a part. the event parameter is lv_obj_draw_dsc_t * + draw_part_end = c.LV_EVENT_DRAW_PART_END, // finishing to draw a part. the event parameter is lv_obj_draw_dsc_t * + + /// special events + value_changed = c.LV_EVENT_VALUE_CHANGED, // the object's value has changed (i.e. slider moved) + insert = c.LV_EVENT_INSERT, // a text is inserted to the object. the event data is char * being inserted. + refresh = c.LV_EVENT_REFRESH, // notify the object to refresh something on it (for the user) + ready = c.LV_EVENT_READY, // a process has finished + cancel = c.LV_EVENT_CANCEL, // a process has been cancelled + + /// other events + delete = c.LV_EVENT_DELETE, // object is being deleted + child_changed = c.LV_EVENT_CHILD_CHANGED, // child was removed, added, or its size, position were changed + child_created = c.LV_EVENT_CHILD_CREATED, // child was created, always bubbles up to all parents + child_deleted = c.LV_EVENT_CHILD_DELETED, // child was deleted, always bubbles up to all parents + screen_unload_start = c.LV_EVENT_SCREEN_UNLOAD_START, // a screen unload started, fired immediately when scr_load is called + screen_load_start = c.LV_EVENT_SCREEN_LOAD_START, // a screen load started, fired when the screen change delay is expired + screen_loaded = c.LV_EVENT_SCREEN_LOADED, // a screen was loaded + screen_unloaded = c.LV_EVENT_SCREEN_UNLOADED, // a screen was unloaded + size_changed = c.LV_EVENT_SIZE_CHANGED, // object coordinates/size have 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 + 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); } @@ -98,84 +163,23 @@ pub const LvEvent = opaque { } }; -/// 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, - - /// input device events - press = c.LV_EVENT_PRESSED, // the object has been pressed - pressing = c.LV_EVENT_PRESSING, // the object is being pressed (called continuously while pressing) - press_lost = c.LV_EVENT_PRESS_LOST, // the object is still being pressed but slid cursor/finger off of the object - short_click = c.LV_EVENT_SHORT_CLICKED, // the object was pressed for a short period of time, then released it. not called if scrolled. - long_press = c.LV_EVENT_LONG_PRESSED, // object has been pressed for at least `long_press_time`. not called if scrolled. - long_press_repeat = c.LV_EVENT_LONG_PRESSED_REPEAT, // called after `long_press_time` in every `long_press_repeat_time` ms. not called if scrolled. - click = c.LV_EVENT_CLICKED, // called on release if not scrolled (regardless to long press) - release = c.LV_EVENT_RELEASED, // called in every cases when the object has been released - scroll_begin = c.LV_EVENT_SCROLL_BEGIN, // scrolling begins. the event parameter is a pointer to the animation of the scroll. can be modified - scroll_end = c.LV_EVENT_SCROLL_END, // scrolling ends - scroll = c.LV_EVENT_SCROLL, // scrolling - gesture = c.LV_EVENT_GESTURE, // a gesture is detected. get the gesture with lv_indev_get_gesture_dir(lv_indev_get_act()) - key = c.LV_EVENT_KEY, // a key is sent to the object. get the key with lv_indev_get_key(lv_indev_get_act()) - focus = c.LV_EVENT_FOCUSED, // the object is focused - defocus = c.LV_EVENT_DEFOCUSED, // the object is defocused - leave = c.LV_EVENT_LEAVE, // the object is defocused but still selected - hit_test = c.LV_EVENT_HIT_TEST, // perform advanced hit-testing - - /// drawing events - cover_check = c.LV_EVENT_COVER_CHECK, // check if the object fully covers an area. the event parameter is lv_cover_check_info_t * - refr_ext_draw_size = c.LV_EVENT_REFR_EXT_DRAW_SIZE, // get the required extra draw area around the object (e.g. for shadow). the event parameter is lv_coord_t * to store the size. - draw_main_begin = c.LV_EVENT_DRAW_MAIN_BEGIN, // starting the main drawing phase - draw_main = c.LV_EVENT_DRAW_MAIN, // perform the main drawing - draw_main_end = c.LV_EVENT_DRAW_MAIN_END, // finishing the main drawing phase - draw_post_begin = c.LV_EVENT_DRAW_POST_BEGIN, // starting the post draw phase (when all children are drawn) - draw_post = c.LV_EVENT_DRAW_POST, // perform the post draw phase (when all children are drawn) - draw_post_end = c.LV_EVENT_DRAW_POST_END, // finishing the post draw phase (when all children are drawn) - draw_part_begin = c.LV_EVENT_DRAW_PART_BEGIN, // starting to draw a part. the event parameter is lv_obj_draw_dsc_t * - draw_part_end = c.LV_EVENT_DRAW_PART_END, // finishing to draw a part. the event parameter is lv_obj_draw_dsc_t * - - /// special events - value_changed = c.LV_EVENT_VALUE_CHANGED, // the object's value has changed (i.e. slider moved) - insert = c.LV_EVENT_INSERT, // a text is inserted to the object. the event data is char * being inserted. - refresh = c.LV_EVENT_REFRESH, // notify the object to refresh something on it (for the user) - ready = c.LV_EVENT_READY, // a process has finished - cancel = c.LV_EVENT_CANCEL, // a process has been cancelled - - /// other events - delete = c.LV_EVENT_DELETE, // object is being deleted - child_changed = c.LV_EVENT_CHILD_CHANGED, // child was removed, added, or its size, position were changed - child_created = c.LV_EVENT_CHILD_CREATED, // child was created, always bubbles up to all parents - child_deleted = c.LV_EVENT_CHILD_DELETED, // child was deleted, always bubbles up to all parents - screen_unload_start = c.LV_EVENT_SCREEN_UNLOAD_START, // a screen unload started, fired immediately when scr_load is called - screen_load_start = c.LV_EVENT_SCREEN_LOAD_START, // a screen load started, fired when the screen change delay is expired - screen_loaded = c.LV_EVENT_SCREEN_LOADED, // a screen was loaded - screen_unloaded = c.LV_EVENT_SCREEN_UNLOADED, // a screen was unloaded - size_changed = c.LV_EVENT_SIZE_CHANGED, // object coordinates/size have 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 - get_self_size = c.LV_EVENT_GET_SELF_SIZE, // get the internal size of a widget -}; - /// represents lv_disp_t in C. -pub const LvDisp = opaque {}; - -/// returns display horizontal resolution. -pub fn displayHoriz() Coord { - return lv_disp_get_hor_res(null); -} +pub const LvDisp = opaque { + /// returns display horizontal resolution. + /// the display must be already initialized. + pub fn horiz() Coord { + return lv_disp_get_hor_res(null); + } -/// returns display vertical resolution. -pub fn displayVert() Coord { - return lv_disp_get_ver_res(null); -} + /// returns display vertical resolution. + /// the display must be already initialized. + pub fn vert() Coord { + return lv_disp_get_ver_res(null); + } +}; /// forces redraw of dirty areas. -pub fn displayRedraw() void { +pub fn redraw() void { lv_refr_now(null); } @@ -190,38 +194,18 @@ pub fn idleTime() u32 { 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. -pub const LvStyle = opaque {}; - -/// indicates which parts and in which states to apply a style to an object. -pub const StyleSelector = struct { - state: State = .default, - part: Part = .main, - - /// produce an int value suitable for lv_xxx functions. - fn value(self: StyleSelector) c.lv_style_selector_t { - return @enumToInt(self.part) | @enumToInt(self.state); - } +pub const LvStyle = opaque { + /// indicates which parts and in which states to apply a style to an object. + pub const Selector = struct { + part: Part = .main, + state: State = .default, + + /// produces an int value suitable for lv_xxx functions. + fn value(self: Selector) c.lv_style_selector_t { + return @enumToInt(self.part) | @enumToInt(self.state); + } + }; }; /// a simplified color type compatible with LVGL which defines lv_color_t @@ -299,227 +283,491 @@ pub const Palette = enum(c.lv_palette_t) { blue_grey = c.LV_PALETTE_BLUE_GREY, grey = c.LV_PALETTE_GREY, none = c.LV_PALETTE_NONE, -}; -/// lightening or darkening levels of the main palette colors. -pub const PaletteModLevel = enum(u8) { - level1 = 1, - level2 = 2, - level3 = 3, - level4 = 4, - level5 = 5, -}; + /// lightening or darkening levels of the main palette colors. + pub const ModLevel = enum(u8) { + level1 = 1, + level2 = 2, + level3 = 3, + level4 = 4, + level5 = 5, + }; -/// returns main color from the predefined palette. -pub inline fn paletteMain(p: Palette) Color { - return lv_palette_main(@enumToInt(p)); -} + /// returns main color from the predefined palette. + pub inline fn main(p: Palette) Color { + return lv_palette_main(@enumToInt(p)); + } -/// makes the main color from the predefined palette lighter according to the -/// specified level. -pub inline fn paletteLighten(p: Palette, l: PaletteModLevel) Color { - return lv_palette_lighten(@enumToInt(p), @enumToInt(l)); -} + /// makes the main color from the predefined palette lighter according to the + /// specified level. + pub inline fn lighten(p: Palette, l: ModLevel) Color { + return lv_palette_lighten(@enumToInt(p), @enumToInt(l)); + } -/// makes the main color from the predefined palette darker according to the -/// specified level. -pub inline fn paletteDarken(p: Palette, l: PaletteModLevel) Color { - return lv_palette_darken(@enumToInt(p), @enumToInt(l)); -} + /// makes the main color from the predefined palette darker according to the + /// specified level. + pub inline fn darken(p: Palette, l: ModLevel) Color { + return lv_palette_darken(@enumToInt(p), @enumToInt(l)); + } +}; -/// represents lv_obj_t type in C. -pub const LvObj = opaque { +/// a set of methods applicable to every kind of lv_xxx object. +pub const BaseObjMethods = struct { /// deallocates all resources used by the object, including its children. /// user data pointers are untouched. - pub fn destroy(self: *LvObj) void { - lv_obj_del(self); + pub fn destroy(self: anytype) void { + lv_obj_del(self.lvobj); } /// deallocates all resources used by the object's children. - pub fn deleteChildren(self: *LvObj) void { - lv_obj_clean(self); + pub fn deleteChildren(self: anytype) void { + 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. + pub fn setFlag(self: anytype, v: LvObj.Flag) void { + lv_obj_add_flag(self.lvobj, @enumToInt(v)); } - /// sets or clears an object flag. - pub fn setFlag(self: *LvObj, onoff: enum { on, off }, v: ObjFlag) void { - switch (onoff) { - .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. - pub fn hasFlag(self: *LvObj, v: ObjFlag) bool { - return lv_obj_has_flag(self, @enumToInt(v)); + pub fn hasFlag(self: anytype, v: LvObj.Flag) bool { + return lv_obj_has_flag(self.lvobj, @enumToInt(v)); } /// returns a user data pointer associated with the object. - pub fn userdata(self: *LvObj) ?*anyopaque { - return nm_obj_userdata(self); + pub fn userdata(self: anytype) ?*anyopaque { + return nm_obj_userdata(self.lvobj); } /// associates an arbitrary data with the object. /// the pointer can be accessed using LvObj.userdata fn. - pub fn setUserdata(self: *LvObj, data: ?*const anyopaque) void { - nm_obj_set_userdata(self, data); + pub fn setUserdata(self: anytype, data: ?*const anyopaque) void { + 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. - pub fn setWidth(self: *LvObj, val: Coord) void { - lv_obj_set_width(self, val); + pub fn setWidth(self: anytype, val: Coord) void { + lv_obj_set_width(self.lvobj, val); } /// sets object vertical length. - pub fn setHeight(self: *LvObj, val: Coord) void { - lv_obj_set_height(self, val); + pub fn setHeight(self: anytype, val: Coord) void { + lv_obj_set_height(self.lvobj, val); } /// sets object height to its child contents. - pub fn setHeightToContent(self: *LvObj) void { - lv_obj_set_height(self, sizeContent); + pub fn setHeightToContent(self: anytype) void { + lv_obj_set_height(self.lvobj, sizeContent); } /// sets both width and height to 100%. - pub fn resizeToMax(self: *LvObj) void { - lv_obj_set_size(self, sizePercent(100), sizePercent(100)); + pub fn resizeToMax(self: anytype) void { + lv_obj_set_size(self.lvobj, sizePercent(100), sizePercent(100)); } /// selects which side to pad in setPad func. pub const PadSelector = enum { all, left, right, top, bottom, row, column }; /// 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) { .all => { const vsel = sel.value(); - lv_obj_set_style_pad_left(self, v, vsel); - lv_obj_set_style_pad_right(self, v, vsel); - lv_obj_set_style_pad_top(self, v, vsel); - lv_obj_set_style_pad_bottom(self, v, vsel); + lv_obj_set_style_pad_left(self.lvobj, v, vsel); + lv_obj_set_style_pad_right(self.lvobj, v, vsel); + lv_obj_set_style_pad_top(self.lvobj, v, vsel); + lv_obj_set_style_pad_bottom(self.lvobj, v, vsel); }, - .left => lv_obj_set_style_pad_left(self, v, sel.value()), - .right => lv_obj_set_style_pad_right(self, v, sel.value()), - .top => lv_obj_set_style_pad_top(self, v, sel.value()), - .bottom => lv_obj_set_style_pad_bottom(self, v, sel.value()), - .row => lv_obj_set_style_pad_row(self, v, sel.value()), - .column => lv_obj_set_style_pad_column(self, v, sel.value()), + .left => lv_obj_set_style_pad_left(self.lvobj, v, sel.value()), + .right => lv_obj_set_style_pad_right(self.lvobj, v, sel.value()), + .top => lv_obj_set_style_pad_top(self.lvobj, v, sel.value()), + .bottom => lv_obj_set_style_pad_bottom(self.lvobj, v, sel.value()), + .row => lv_obj_set_style_pad_row(self.lvobj, 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. - pub fn center(self: *LvObj) void { + pub fn center(self: anytype) void { self.posAlign(.center, 0, 0); } /// aligns object position. the offset is relative to the specified alignment a. - pub fn posAlign(self: *LvObj, a: PosAlign, xoffset: Coord, yoffset: Coord) void { - lv_obj_align(self, @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)); + pub fn posAlign(self: anytype, a: PosAlign, xoffset: Coord, yoffset: Coord) void { + lv_obj_align(self.lvobj, @enumToInt(a), xoffset, yoffset); } /// sets flex layout growth property; same meaning as in CSS flex. - pub fn flexGrow(self: *LvObj, val: u8) void { - lv_obj_set_flex_grow(self, 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)); + pub fn flexGrow(self: anytype, val: u8) void { + lv_obj_set_flex_grow(self.lvobj, val); } /// 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 { - lv_obj_add_style(self, v, sel.value()); + pub fn addStyle(self: anytype, v: *LvStyle, sel: LvStyle.Selector) void { + lv_obj_add_style(self.lvobj, v, sel.value()); } /// removes all styles from the object. - pub fn removeAllStyle(self: *LvObj) void { - const sel = StyleSelector{ .part = .any, .state = .any }; - lv_obj_remove_style(self, null, sel.value()); + pub fn removeAllStyle(self: anytype) void { + const sel = LvStyle.Selector{ .part = .any, .state = .any }; + lv_obj_remove_style(self.lvobj, null, sel.value()); } /// removes only the background styles from the object. - pub fn removeBackgroundStyle(self: *LvObj) void { - const sel = StyleSelector{ .part = .main, .state = .any }; - lv_obj_remove_style(self, null, sel.value()); + pub fn removeBackgroundStyle(self: anytype) void { + const sel = LvStyle.Selector{ .part = .main, .state = .any }; + lv_obj_remove_style(self.lvobj, null, sel.value()); } /// sets a desired background color to objects parts/states. - pub fn setBackgroundColor(self: *LvObj, v: Color, sel: StyleSelector) void { - lv_obj_set_style_bg_color(self, v, sel.value()); + pub fn setBackgroundColor(self: anytype, v: Color, sel: LvStyle.Selector) void { + 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 }; + } + + /// returns active screen of the default display. + pub fn active() !Screen { + const o = lv_disp_get_scr_act(null) orelse return error.NoDisplay; + return .{ .lvobj = o }; } - /// sets the color of a text, typically a label object. - pub fn setTextColor(self: *LvObj, v: Color, sel: StyleSelector) void { - lv_obj_set_style_text_color(self, v, sel.value()); + /// makes a screen active. + pub fn load(scr: Screen) void { + lv_disp_load_scr(scr.obj); } }; -pub fn createObject(parent: *LvObj) !*LvObj { - return lv_obj_create(parent) orelse error.OutOfMemory; -} +/// used as a base parent for many other elements. +pub const Container = struct { + lvobj: *LvObj, -pub fn createFlexObject(parent: *LvObj, flow: FlexFlow) !*LvObj { - var o = try createObject(parent); - o.flexFlow(flow); - return o; -} + pub usingnamespace BaseObjMethods; + pub usingnamespace WidgetMethods; -pub fn createTopObject() !*LvObj { - const toplayer = lv_disp_get_layer_top(null); - return lv_obj_create(toplayer) orelse error.OutOfMemory; -} + pub fn new(parent: anytype) !Container { + const o = lv_obj_create(parent.lvobj) orelse return error.OutOfMemory; + return .{ .lvobj = o }; + } -/// feature-flags controlling object's behavior. -/// OR'ed values are possible. -pub const ObjFlag = enum(c.lv_obj_flag_t) { - 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 - focusable = c.LV_OBJ_FLAG_CLICK_FOCUSABLE, // add focused state to the object when clicked - checkable = c.LV_OBJ_FLAG_CHECKABLE, // toggle checked state when the object is clicked - scrollable = c.LV_OBJ_FLAG_SCROLLABLE, // make the object scrollable - scroll_elastic = c.LV_OBJ_FLAG_SCROLL_ELASTIC, // allow scrolling inside but with slower speed - scroll_momentum = c.LV_OBJ_FLAG_SCROLL_MOMENTUM, // make the object scroll further when "thrown" - scroll_one = c.LV_OBJ_FLAG_SCROLL_ONE, // allow scrolling only one snappable children - scroll_chain_hor = c.LV_OBJ_FLAG_SCROLL_CHAIN_HOR, // allow propagating the horizontal scroll to a parent - scroll_chain_ver = c.LV_OBJ_FLAG_SCROLL_CHAIN_VER, // allow propagating the vertical scroll to a parent - scroll_chain = c.LV_OBJ_FLAG_SCROLL_CHAIN, - scroll_on_focus = c.LV_OBJ_FLAG_SCROLL_ON_FOCUS, // automatically scroll object to make it visible when focused - scroll_with_arrow = c.LV_OBJ_FLAG_SCROLL_WITH_ARROW, // allow scrolling the focused object with arrow keys - snappable = c.LV_OBJ_FLAG_SNAPPABLE, // if scroll snap is enabled on the parent it can snap to this object - press_lock = c.LV_OBJ_FLAG_PRESS_LOCK, // keep the object pressed even if the press slid from the object - event_bubble = c.LV_OBJ_FLAG_EVENT_BUBBLE, // propagate the events to the parent too - gesture_bubble = c.LV_OBJ_FLAG_GESTURE_BUBBLE, // propagate the gestures to the parent - ignore_layout = c.LV_OBJ_FLAG_IGNORE_LAYOUT, // make the object position-able by the layouts - floating = c.LV_OBJ_FLAG_FLOATING, // do not scroll the object when the parent scrolls and ignore layout - overflow_visible = c.LV_OBJ_FLAG_OVERFLOW_VISIBLE, // do not clip the children's content to the parent's boundary - - user1 = c.LV_OBJ_FLAG_USER_1, // 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 - user4 = c.LV_OBJ_FLAG_USER_4, // custom flag, free to use by user + /// 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); + return .{ .lvobj = toplayer }; + } + + /// applies flex layout to the container. + pub fn flex(self: Container, flow: FlexLayout.Flow, opt: FlexLayout.AlignOpt) FlexLayout { + 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 + 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 + checkable = c.LV_OBJ_FLAG_CHECKABLE, // toggle checked state when the object is clicked + scrollable = c.LV_OBJ_FLAG_SCROLLABLE, // make the object scrollable + scroll_elastic = c.LV_OBJ_FLAG_SCROLL_ELASTIC, // allow scrolling inside but with slower speed + scroll_momentum = c.LV_OBJ_FLAG_SCROLL_MOMENTUM, // make the object scroll further when "thrown" + scroll_one = c.LV_OBJ_FLAG_SCROLL_ONE, // allow scrolling only one snappable children + scroll_chain_hor = c.LV_OBJ_FLAG_SCROLL_CHAIN_HOR, // allow propagating the horizontal scroll to a parent + scroll_chain_ver = c.LV_OBJ_FLAG_SCROLL_CHAIN_VER, // allow propagating the vertical scroll to a parent + scroll_chain = c.LV_OBJ_FLAG_SCROLL_CHAIN, + scroll_on_focus = c.LV_OBJ_FLAG_SCROLL_ON_FOCUS, // automatically scroll object to make it visible when focused + scroll_with_arrow = c.LV_OBJ_FLAG_SCROLL_WITH_ARROW, // allow scrolling the focused object with arrow keys + snappable = c.LV_OBJ_FLAG_SNAPPABLE, // if scroll snap is enabled on the parent it can snap to this object + press_lock = c.LV_OBJ_FLAG_PRESS_LOCK, // keep the object pressed even if the press slid from the object + event_bubble = c.LV_OBJ_FLAG_EVENT_BUBBLE, // propagate the events to the parent too + gesture_bubble = c.LV_OBJ_FLAG_GESTURE_BUBBLE, // propagate the gestures to the parent + ignore_layout = c.LV_OBJ_FLAG_IGNORE_LAYOUT, // make the object position-able by the layouts + floating = c.LV_OBJ_FLAG_FLOATING, // do not scroll the object when the parent scrolls and ignore layout + overflow_visible = c.LV_OBJ_FLAG_OVERFLOW_VISIBLE, // do not clip the children's content to the parent's boundary + + user1 = c.LV_OBJ_FLAG_USER_1, // 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 + 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. @@ -614,112 +862,14 @@ pub const PosAlign = enum(c.lv_align_t) { 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 // ========================================================================== /// returns a red button style. 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. 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. /// returns period after which it is to be run again, in ms. 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_set_repeat_count(timer: *LvTimer, n: i32) void; // 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_target(e: *LvEvent) *LvObj; 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 ---------------------------------------------- @@ -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_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_add_title(win: *LvObj, title: [*:0]const u8) ?*LvObj; extern fn lv_win_get_content(win: *LvObj) *LvObj; diff --git a/src/ui/poweroff.zig b/src/ui/poweroff.zig index aa408f4..cb81be5 100644 --- a/src/ui/poweroff.zig +++ b/src/ui/poweroff.zig @@ -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); } } diff --git a/src/ui/ui.zig b/src/ui/ui.zig index 8e3e5d3..9c50a47 100644 --- a/src/ui/ui.zig +++ b/src/ui/ui.zig @@ -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}, .{}); } diff --git a/src/ui/widget.zig b/src/ui/widget.zig index a179215..cce766d 100644 --- a/src/ui/widget.zig +++ b/src/ui/widget.zig @@ -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); } }