ngui: port some LVGL-based UI code from C to zig
this commit includes lots of new code in ui/lvgl.zig and ui/widget.zig to work with LVGL directly from zig. in fact, some C code is moved from ui/c/ui.c to ui/xxx.zig. in addition, a new module ui/widget.zig is where all custom UI elements will reside. at the moment it comprises of the ported topdrop and a new modal func. the latter is an alternative to LVGL's lv_msgbox popup. as a practical example, the commit replaces the power off confirmation popup with the new modal window.pull/20/head
parent
c73867ea4d
commit
4297c139a1
|
@ -12,3 +12,4 @@ BinPackArguments: false
|
||||||
BinPackParameters: false
|
BinPackParameters: false
|
||||||
AllowAllArgumentsOnNextLine: true
|
AllowAllArgumentsOnNextLine: true
|
||||||
AllowAllParametersOfDeclarationOnNextLine: true
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
|
|
48
src/ngui.zig
48
src/ngui.zig
|
@ -1,7 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const mem = std.mem;
|
|
||||||
const time = std.time;
|
const time = std.time;
|
||||||
const Thread = std.Thread;
|
|
||||||
|
|
||||||
const comm = @import("comm.zig");
|
const comm = @import("comm.zig");
|
||||||
const types = @import("types.zig");
|
const types = @import("types.zig");
|
||||||
|
@ -24,11 +22,11 @@ extern "c" fn ui_update_network_status(text: [*:0]const u8, wifi_list: ?[*:0]con
|
||||||
|
|
||||||
/// global heap allocator used throughout the gui program.
|
/// global heap allocator used throughout the gui program.
|
||||||
/// TODO: thread-safety?
|
/// TODO: thread-safety?
|
||||||
var gpa: mem.Allocator = undefined;
|
var gpa: std.mem.Allocator = undefined;
|
||||||
|
|
||||||
/// the mutex must be held before any call reaching into lv_xxx functions.
|
/// the mutex must be held before any call reaching into lv_xxx functions.
|
||||||
/// all nm_xxx functions assume it is the case since they are invoked from lvgl c code.
|
/// all nm_xxx functions assume it is the case since they are invoked from lvgl c code.
|
||||||
var ui_mutex: Thread.Mutex = .{};
|
var ui_mutex: std.Thread.Mutex = .{};
|
||||||
/// the program runs until quit is true.
|
/// the program runs until quit is true.
|
||||||
var quit: bool = false;
|
var quit: bool = false;
|
||||||
var state: enum {
|
var state: enum {
|
||||||
|
@ -43,7 +41,7 @@ var state: enum {
|
||||||
/// user attention.
|
/// user attention.
|
||||||
/// safe for concurrent use except wakeup.reset() is UB during another thread
|
/// safe for concurrent use except wakeup.reset() is UB during another thread
|
||||||
/// wakeup.wait()'ing or timedWait'ing.
|
/// wakeup.wait()'ing or timedWait'ing.
|
||||||
var wakeup = Thread.ResetEvent{};
|
var wakeup = std.Thread.ResetEvent{};
|
||||||
|
|
||||||
/// a monotonic clock for reporting elapsed ticks to LVGL.
|
/// a monotonic clock for reporting elapsed ticks to LVGL.
|
||||||
/// the timer runs throughout the whole duration of the UI program.
|
/// the timer runs throughout the whole duration of the UI program.
|
||||||
|
@ -62,14 +60,13 @@ export fn nm_get_curr_tick() u32 {
|
||||||
|
|
||||||
export fn nm_check_idle_time(_: *lvgl.LvTimer) void {
|
export fn nm_check_idle_time(_: *lvgl.LvTimer) void {
|
||||||
const standby_idle_ms = 60000; // 60sec
|
const standby_idle_ms = 60000; // 60sec
|
||||||
const idle_ms = lvgl.lv_disp_get_inactive_time(null);
|
const idle_ms = lvgl.idleTime();
|
||||||
logger.debug("idle: {d}", .{idle_ms});
|
|
||||||
if (idle_ms > standby_idle_ms and state != .alert) {
|
if (idle_ms > standby_idle_ms and state != .alert) {
|
||||||
state = .standby;
|
state = .standby;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// initiate system shutdown.
|
/// initiate system shutdown leading to power off.
|
||||||
export fn nm_sys_shutdown() void {
|
export fn nm_sys_shutdown() void {
|
||||||
logger.info("initiating system shutdown", .{});
|
logger.info("initiating system shutdown", .{});
|
||||||
const msg = comm.Message.poweroff;
|
const msg = comm.Message.poweroff;
|
||||||
|
@ -84,7 +81,7 @@ export fn nm_tab_settings_active() void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn nm_request_network_status(t: *lvgl.LvTimer) void {
|
export fn nm_request_network_status(t: *lvgl.LvTimer) void {
|
||||||
lvgl.lv_timer_del(t);
|
t.destroy();
|
||||||
const msg: comm.Message = .{ .get_network_report = .{ .scan = false } };
|
const msg: comm.Message = .{ .get_network_report = .{ .scan = false } };
|
||||||
comm.write(gpa, stdout, msg) catch |err| logger.err("nm_request_network_status: {any}", .{err});
|
comm.write(gpa, stdout, msg) catch |err| logger.err("nm_request_network_status: {any}", .{err});
|
||||||
}
|
}
|
||||||
|
@ -92,8 +89,8 @@ export fn nm_request_network_status(t: *lvgl.LvTimer) void {
|
||||||
/// ssid and password args must not outlive this function.
|
/// ssid and password args must not outlive this function.
|
||||||
export fn nm_wifi_start_connect(ssid: [*:0]const u8, password: [*:0]const u8) void {
|
export fn nm_wifi_start_connect(ssid: [*:0]const u8, password: [*:0]const u8) void {
|
||||||
const msg = comm.Message{ .wifi_connect = .{
|
const msg = comm.Message{ .wifi_connect = .{
|
||||||
.ssid = mem.span(ssid),
|
.ssid = std.mem.span(ssid),
|
||||||
.password = mem.span(password),
|
.password = std.mem.span(password),
|
||||||
} };
|
} };
|
||||||
logger.info("connect to wifi [{s}]", .{msg.wifi_connect.ssid});
|
logger.info("connect to wifi [{s}]", .{msg.wifi_connect.ssid});
|
||||||
comm.write(gpa, stdout, msg) catch |err| {
|
comm.write(gpa, stdout, msg) catch |err| {
|
||||||
|
@ -108,7 +105,7 @@ fn updateNetworkStatus(report: comm.Message.NetworkReport) !void {
|
||||||
var wifi_list: ?[:0]const u8 = null;
|
var wifi_list: ?[:0]const u8 = null;
|
||||||
var wifi_list_ptr: ?[*:0]const u8 = null;
|
var wifi_list_ptr: ?[*:0]const u8 = null;
|
||||||
if (report.wifi_scan_networks.len > 0) {
|
if (report.wifi_scan_networks.len > 0) {
|
||||||
wifi_list = try mem.joinZ(gpa, "\n", report.wifi_scan_networks);
|
wifi_list = try std.mem.joinZ(gpa, "\n", report.wifi_scan_networks);
|
||||||
wifi_list_ptr = wifi_list.?.ptr;
|
wifi_list_ptr = wifi_list.?.ptr;
|
||||||
}
|
}
|
||||||
defer if (wifi_list) |v| gpa.free(v);
|
defer if (wifi_list) |v| gpa.free(v);
|
||||||
|
@ -124,7 +121,7 @@ fn updateNetworkStatus(report: comm.Message.NetworkReport) !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report.ipaddrs.len > 0) {
|
if (report.ipaddrs.len > 0) {
|
||||||
const ipaddrs = try mem.join(gpa, "\n", report.ipaddrs);
|
const ipaddrs = try std.mem.join(gpa, "\n", report.ipaddrs);
|
||||||
defer gpa.free(ipaddrs);
|
defer gpa.free(ipaddrs);
|
||||||
try w.print("\n\nIP addresses:\n{s}", .{ipaddrs});
|
try w.print("\n\nIP addresses:\n{s}", .{ipaddrs});
|
||||||
}
|
}
|
||||||
|
@ -137,10 +134,10 @@ 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.lv_timer_create(nm_request_network_status, 1000, null)) |t| {
|
if (lvgl.createTimer(nm_request_network_status, 1000, null)) |t| {
|
||||||
lvgl.lv_timer_set_repeat_count(t, 1);
|
t.setRepeatCount(1);
|
||||||
} else {
|
} else |err| {
|
||||||
logger.err("lv_timer_create network status failed: OOM?", .{});
|
logger.err("network status timer failed: {any}", .{err});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,7 +158,8 @@ fn commThread() void {
|
||||||
fn commThreadLoopCycle() !void {
|
fn commThreadLoopCycle() !void {
|
||||||
const msg = comm.read(gpa, stdin) catch |err| {
|
const msg = comm.read(gpa, stdin) catch |err| {
|
||||||
if (err == error.EndOfStream) {
|
if (err == error.EndOfStream) {
|
||||||
// pointless to continue running if comms is broken
|
// pointless to continue running if comms is broken.
|
||||||
|
// a parent/supervisor is expected to restart ngui.
|
||||||
ui_mutex.lock();
|
ui_mutex.lock();
|
||||||
quit = true;
|
quit = true;
|
||||||
ui_mutex.unlock();
|
ui_mutex.unlock();
|
||||||
|
@ -199,21 +197,19 @@ pub fn main() anyerror!void {
|
||||||
};
|
};
|
||||||
|
|
||||||
// start comms with daemon in a seaparate thread.
|
// start comms with daemon in a seaparate thread.
|
||||||
const th = try Thread.spawn(.{}, commThread, .{});
|
const th = try std.Thread.spawn(.{}, commThread, .{});
|
||||||
th.detach();
|
th.detach();
|
||||||
|
|
||||||
// run idle timer indefinitely
|
// run idle timer indefinitely
|
||||||
if (lvgl.lv_timer_create(nm_check_idle_time, 2000, null)) |t| {
|
_ = lvgl.createTimer(nm_check_idle_time, 2000, null) catch |err| {
|
||||||
lvgl.lv_timer_set_repeat_count(t, -1);
|
logger.err("idle timer: lvgl.CreateTimer failed: {any}", .{err});
|
||||||
} else {
|
};
|
||||||
logger.err("lv_timer_create idle failed: OOM?", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
// main UI thread; must never block unless in idle/sleep mode
|
// main UI thread; must never block unless in idle/sleep mode
|
||||||
// TODO: handle sigterm
|
// TODO: handle sigterm
|
||||||
while (true) {
|
while (true) {
|
||||||
ui_mutex.lock();
|
ui_mutex.lock();
|
||||||
var till_next_ms = lvgl.lv_timer_handler();
|
var till_next_ms = lvgl.loopCycle();
|
||||||
const do_quit = quit;
|
const do_quit = quit;
|
||||||
const do_state = state;
|
const do_state = state;
|
||||||
ui_mutex.unlock();
|
ui_mutex.unlock();
|
||||||
|
@ -235,7 +231,7 @@ pub fn main() anyerror!void {
|
||||||
comm.write(gpa, stdout, comm.Message.wakeup) catch |err| {
|
comm.write(gpa, stdout, comm.Message.wakeup) catch |err| {
|
||||||
logger.err("comm.write wakeup: {any}", .{err});
|
logger.err("comm.write wakeup: {any}", .{err});
|
||||||
};
|
};
|
||||||
lvgl.lv_disp_trig_activity(null);
|
lvgl.resetIdle();
|
||||||
}
|
}
|
||||||
ui_mutex.unlock();
|
ui_mutex.unlock();
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* function declarations with nm_ prefix are typically defined in zig,
|
* function declarations with nm_ prefix are typically defined in zig,
|
||||||
* while extern'ed function definitions with nm_ prefix are typically called from zig.
|
* while function definitions with nm_ prefix are extern'ed to call from zig.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define _DEFAULT_SOURCE /* needed for usleep() */
|
#define _DEFAULT_SOURCE /* needed for usleep() */
|
||||||
|
@ -26,6 +26,11 @@ void nm_tab_settings_active();
|
||||||
*/
|
*/
|
||||||
int nm_wifi_start_connect(const char *ssid, const char *password);
|
int nm_wifi_start_connect(const char *ssid, const char *password);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback fn when "power off" button is pressed.
|
||||||
|
*/
|
||||||
|
void nm_poweroff_btn_callback(lv_event_t *e);
|
||||||
|
|
||||||
static lv_style_t style_title;
|
static lv_style_t style_title;
|
||||||
static lv_style_t style_text_muted;
|
static lv_style_t style_text_muted;
|
||||||
static lv_style_t style_btn_red;
|
static lv_style_t style_btn_red;
|
||||||
|
@ -33,16 +38,33 @@ static const lv_font_t *font_large;
|
||||||
static lv_obj_t *virt_keyboard;
|
static lv_obj_t *virt_keyboard;
|
||||||
static lv_obj_t *tabview; /* main tabs content parent; lv_tabview_create */
|
static lv_obj_t *tabview; /* main tabs content parent; lv_tabview_create */
|
||||||
|
|
||||||
lv_obj_t *nm_make_topdrop()
|
/**
|
||||||
|
* returns user-managed data previously set on an object with nm_obj_set_userdata.
|
||||||
|
* the returned value may be NULL.
|
||||||
|
*/
|
||||||
|
extern void *nm_obj_userdata(lv_obj_t *obj)
|
||||||
{
|
{
|
||||||
lv_obj_t *topdrop = lv_obj_create(lv_layer_top());
|
return obj->user_data;
|
||||||
if (!topdrop) {
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
lv_obj_set_style_bg_color(topdrop, lv_color_black(), 0);
|
|
||||||
lv_obj_clear_flag(topdrop, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
/**
|
||||||
lv_obj_set_size(topdrop, LV_PCT(100), LV_PCT(100));
|
* set or "attach" user-managed data to an object.
|
||||||
return topdrop;
|
* the data pointer may be NULL.
|
||||||
|
*/
|
||||||
|
extern void nm_obj_set_userdata(lv_obj_t *obj, void *data)
|
||||||
|
{
|
||||||
|
obj->user_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a "red" style for a button. useful to attract particular attention
|
||||||
|
* to a potentially "dangerous" operation.
|
||||||
|
*
|
||||||
|
* the returned value is static. available only after nm_ui_init.
|
||||||
|
*/
|
||||||
|
extern lv_style_t *nm_style_btn_red()
|
||||||
|
{
|
||||||
|
return &style_btn_red;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void textarea_event_cb(lv_event_t *e)
|
static void textarea_event_cb(lv_event_t *e)
|
||||||
|
@ -125,35 +147,6 @@ static void wifi_connect_btn_callback(lv_event_t *e)
|
||||||
nm_wifi_start_connect(buf, lv_textarea_get_text(settings.wifi_pwd_obj));
|
nm_wifi_start_connect(buf, lv_textarea_get_text(settings.wifi_pwd_obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void power_halt_btn_callback(lv_event_t *e)
|
|
||||||
{
|
|
||||||
if (e->user_data) { /* ptr to msgbox */
|
|
||||||
lv_obj_t *msgbox = e->user_data;
|
|
||||||
/* first button is "proceed", do shutdown */
|
|
||||||
if (lv_msgbox_get_active_btn(msgbox) == 0) {
|
|
||||||
/* shutdown confirmed */
|
|
||||||
nm_sys_shutdown();
|
|
||||||
}
|
|
||||||
/* shutdown aborted or passthrough from confirmed shutdown.
|
|
||||||
* in the latter case, ui is still running for a brief moment,
|
|
||||||
* until ngui terminates. */
|
|
||||||
lv_msgbox_close(msgbox);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* first button must always be a "proceed", do shutdown;
|
|
||||||
* text is irrelevant */
|
|
||||||
static const char *btns[] = {"PROCEED", "ABORT", NULL};
|
|
||||||
lv_obj_t *msgbox = lv_msgbox_create(NULL, /* modal */
|
|
||||||
"SHUTDOWN", /* title */
|
|
||||||
"are you sure?", /* text */
|
|
||||||
btns, /* */
|
|
||||||
false /* close btn */);
|
|
||||||
lv_obj_center(msgbox);
|
|
||||||
lv_obj_add_event_cb(msgbox, power_halt_btn_callback, LV_EVENT_VALUE_CHANGED, msgbox);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void create_settings_panel(lv_obj_t *parent)
|
static void create_settings_panel(lv_obj_t *parent)
|
||||||
{
|
{
|
||||||
/********************
|
/********************
|
||||||
|
@ -221,7 +214,7 @@ static void create_settings_panel(lv_obj_t *parent)
|
||||||
settings.power_halt_btn_obj = power_halt_btn;
|
settings.power_halt_btn_obj = power_halt_btn;
|
||||||
lv_obj_set_height(power_halt_btn, LV_SIZE_CONTENT);
|
lv_obj_set_height(power_halt_btn, LV_SIZE_CONTENT);
|
||||||
lv_obj_add_style(power_halt_btn, &style_btn_red, 0);
|
lv_obj_add_style(power_halt_btn, &style_btn_red, 0);
|
||||||
lv_obj_add_event_cb(power_halt_btn, power_halt_btn_callback, LV_EVENT_CLICKED, NULL);
|
lv_obj_add_event_cb(power_halt_btn, nm_poweroff_btn_callback, LV_EVENT_CLICKED, NULL);
|
||||||
lv_obj_t *power_halt_btn_label = lv_label_create(power_halt_btn);
|
lv_obj_t *power_halt_btn_label = lv_label_create(power_halt_btn);
|
||||||
lv_label_set_text_static(power_halt_btn_label, "SHUTDOWN");
|
lv_label_set_text_static(power_halt_btn_label, "SHUTDOWN");
|
||||||
lv_obj_center(power_halt_btn_label);
|
lv_obj_center(power_halt_btn_label);
|
||||||
|
|
|
@ -29,12 +29,12 @@ pub fn initInput() !void {
|
||||||
|
|
||||||
/// deactivate and remove all input devices.
|
/// deactivate and remove all input devices.
|
||||||
pub fn deinitInput() void {
|
pub fn deinitInput() void {
|
||||||
var indev = lvgl.lv_indev_get_next(null);
|
var indev: ?*lvgl.LvIndev = lvgl.LvIndev.first();
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (indev) |d| {
|
while (indev) |d| {
|
||||||
lvgl.lv_indev_delete(d);
|
d.destroy();
|
||||||
count += 1;
|
count += 1;
|
||||||
indev = lvgl.lv_indev_get_next(null);
|
indev = lvgl.LvIndev.first();
|
||||||
}
|
}
|
||||||
logger.debug("deinited {d} indev(s)", .{count});
|
logger.debug("deinited {d} indev(s)", .{count});
|
||||||
}
|
}
|
||||||
|
|
805
src/ui/lvgl.zig
805
src/ui/lvgl.zig
|
@ -1,8 +1,23 @@
|
||||||
///! LVGL types in zig
|
///! LVGL types in zig.
|
||||||
|
///
|
||||||
|
/// lv_xxx functions are directly linked against LVGL lib.
|
||||||
|
/// other functions, without the lv_ prefix are defined in zig and provide type safety
|
||||||
|
/// and extra functionality, sometimes composed of multiple calls to lv_xxx.
|
||||||
|
///
|
||||||
|
/// nm_xxx functions defined here are exported with "c" convention to allow
|
||||||
|
/// calls from C code.
|
||||||
|
///
|
||||||
|
/// the module usage must be started with a call to init.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const c = @cImport({
|
||||||
|
@cInclude("lvgl/lvgl.h");
|
||||||
|
});
|
||||||
|
|
||||||
/// initalize LVGL internals.
|
// logs LV_LOG_xxx messages from LVGL lib.
|
||||||
/// must be called before any other UI functions.
|
const logger = std.log.scoped(.lvgl);
|
||||||
|
|
||||||
|
/// initalizes LVGL internals. must be called before any other UI functionality
|
||||||
|
/// is invoked, typically at program startup.
|
||||||
pub fn init() void {
|
pub fn init() void {
|
||||||
init_once.call();
|
init_once.call();
|
||||||
}
|
}
|
||||||
|
@ -14,46 +29,770 @@ fn lvglInit() void {
|
||||||
lv_init();
|
lv_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
// logs LV_LOG_xxx messages from LVGL lib
|
|
||||||
const lvgl_logger = std.log.scoped(.lvgl);
|
|
||||||
|
|
||||||
export fn nm_lvgl_log(msg: [*:0]const u8) void {
|
export fn nm_lvgl_log(msg: [*:0]const u8) void {
|
||||||
const s = std.mem.span(msg);
|
const s = std.mem.span(msg);
|
||||||
// info level log messages are by default printed only in Debug and ReleaseSafe build modes.
|
// info level log messages are by default printed only in Debug
|
||||||
lvgl_logger.debug("{s}", .{std.mem.trimRight(u8, s, "\n")});
|
// and ReleaseSafe build modes.
|
||||||
|
logger.debug("{s}", .{std.mem.trimRight(u8, s, "\n")});
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "c" fn lv_init() void;
|
/// the busy-wait loop cycle wrapper for LVGL.
|
||||||
extern "c" fn lv_log_register_print_cb(*const fn (msg: [*:0]const u8) callconv(.C) void) void;
|
/// a program main loop must call this periodically.
|
||||||
|
/// returns the period after which it is to be called again, in ms.
|
||||||
|
pub fn loopCycle() u32 {
|
||||||
|
return lv_timer_handler();
|
||||||
|
}
|
||||||
|
|
||||||
/// represents lv_timer_t in C.
|
/// represents lv_timer_t in C.
|
||||||
pub const LvTimer = opaque {};
|
pub const LvTimer = opaque {
|
||||||
pub const LvTimerCallback = *const fn (timer: *LvTimer) callconv(.C) void;
|
pub fn destroy(self: *LvTimer) void {
|
||||||
/// the timer handler is the LVGL busy-wait loop iteration.
|
lv_timer_del(self);
|
||||||
pub extern "c" fn lv_timer_handler() u32;
|
}
|
||||||
pub extern "c" fn lv_timer_create(callback: LvTimerCallback, period_ms: u32, userdata: ?*anyopaque) ?*LvTimer;
|
|
||||||
pub extern "c" fn lv_timer_del(timer: *LvTimer) void;
|
|
||||||
pub extern "c" fn lv_timer_set_repeat_count(timer: *LvTimer, n: i32) void;
|
|
||||||
|
|
||||||
/// represents lv_obj_t type in C.
|
/// after the repeat count is reached, the timer is destroy'ed automatically.
|
||||||
pub const LvObj = opaque {};
|
/// to run the timer indefinitely, use -1 for repeat count.
|
||||||
/// delete and deallocate an object and all its children from UI tree.
|
pub fn setRepeatCount(self: *LvTimer, n: i32) void {
|
||||||
pub extern "c" fn lv_obj_del(obj: *LvObj) void;
|
lv_timer_set_repeat_count(self, n);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
/// deallocate and delete an input device from LVGL registry.
|
pub fn first() ?*LvIndev {
|
||||||
pub extern "c" fn lv_indev_delete(indev: *LvIndev) void;
|
return lv_indev_get_next(null);
|
||||||
/// return next device in the list or head if indev is null.
|
}
|
||||||
pub extern "c" fn lv_indev_get_next(indev: ?*LvIndev) ?*LvIndev;
|
|
||||||
|
pub fn next(self: *LvIndev) ?*LvIndev {
|
||||||
|
return lv_indev_get_next(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *LvIndev) void {
|
||||||
|
lv_indev_delete(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// a zig representation of lv_event_t, required by all event callbacks.
|
||||||
|
pub const LvEvent = opaque {
|
||||||
|
pub fn code(self: *LvEvent) EventCode {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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.
|
/// represents lv_disp_t in C.
|
||||||
pub const LvDisp = opaque {};
|
pub const LvDisp = opaque {};
|
||||||
/// return elapsed time since last user activity on a specific display
|
|
||||||
/// or any if disp is null.
|
/// returns display horizontal resolution.
|
||||||
pub extern "c" fn lv_disp_get_inactive_time(disp: ?*LvDisp) u32;
|
pub fn displayHoriz() Coord {
|
||||||
/// make it so as if a user activity happened.
|
return lv_disp_get_hor_res(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns display vertical resolution.
|
||||||
|
pub fn displayVert() Coord {
|
||||||
|
return lv_disp_get_ver_res(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// forces redraw of dirty areas.
|
||||||
|
pub fn displayRedraw() void {
|
||||||
|
lv_refr_now(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// resets user incativity time, as if a UI interaction happened "now".
|
||||||
|
pub fn resetIdle() void {
|
||||||
|
lv_disp_trig_activity(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns user inactivity time in ms.
|
||||||
|
/// inactivity is the time elapsed since the last UI action.
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// a simplified color type compatible with LVGL which defines lv_color_t
|
||||||
|
/// as a union containing an bit-fields struct unsupported in zig cImport.
|
||||||
|
pub const Color = u16; // originally c.lv_color_t; TODO: comptime switch for u32
|
||||||
|
|
||||||
|
//typedef union {
|
||||||
|
// struct {
|
||||||
|
//#if LV_COLOR_16_SWAP == 0
|
||||||
|
// uint16_t blue : 5;
|
||||||
|
// uint16_t green : 6;
|
||||||
|
// uint16_t red : 5;
|
||||||
|
//#else
|
||||||
|
// uint16_t green_h : 3;
|
||||||
|
// uint16_t red : 5;
|
||||||
|
// uint16_t blue : 5;
|
||||||
|
// uint16_t green_l : 3;
|
||||||
|
//#endif
|
||||||
|
// } ch;
|
||||||
|
// uint16_t full;
|
||||||
|
//} lv_color16_t;
|
||||||
|
//
|
||||||
|
//# define LV_COLOR_MAKE16(r8, g8, b8) {{(uint8_t)((b8 >> 3) & 0x1FU), (uint8_t)((g8 >> 2) & 0x3FU), (uint8_t)((r8 >> 3) & 0x1FU)}}
|
||||||
|
//
|
||||||
|
// TODO: RGB32
|
||||||
|
//typedef union {
|
||||||
|
// struct {
|
||||||
|
// uint8_t blue;
|
||||||
|
// uint8_t green;
|
||||||
|
// uint8_t red;
|
||||||
|
// uint8_t alpha;
|
||||||
|
// } ch;
|
||||||
|
// uint32_t full;
|
||||||
|
//} lv_color32_t;
|
||||||
|
const RGB16 = packed struct {
|
||||||
|
b: u5,
|
||||||
|
g: u6,
|
||||||
|
r: u5,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// rgb produces a Color value base on the red, green and blue components.
|
||||||
|
pub inline fn rgb(r: u8, g: u8, b: u8) Color {
|
||||||
|
const c16 = RGB16{
|
||||||
|
.b = @truncate(u5, b >> 3),
|
||||||
|
.g = @truncate(u6, g >> 2),
|
||||||
|
.r = @truncate(u5, r >> 3),
|
||||||
|
};
|
||||||
|
return @bitCast(Color, c16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// black color
|
||||||
|
pub const Black = rgb(0, 0, 0);
|
||||||
|
/// white color
|
||||||
|
pub const White = rgb(0xff, 0xff, 0xff);
|
||||||
|
|
||||||
|
/// Palette defines a set of colors, typically used in a theme.
|
||||||
|
pub const Palette = enum(c.lv_palette_t) {
|
||||||
|
red = c.LV_PALETTE_RED,
|
||||||
|
pink = c.LV_PALETTE_PINK,
|
||||||
|
purple = c.LV_PALETTE_PURPLE,
|
||||||
|
deep_purple = c.LV_PALETTE_DEEP_PURPLE,
|
||||||
|
indigo = c.LV_PALETTE_INDIGO,
|
||||||
|
blue = c.LV_PALETTE_BLUE,
|
||||||
|
light_blue = c.LV_PALETTE_LIGHT_BLUE,
|
||||||
|
cyan = c.LV_PALETTE_CYAN,
|
||||||
|
teal = c.LV_PALETTE_TEAL,
|
||||||
|
green = c.LV_PALETTE_GREEN,
|
||||||
|
light_green = c.LV_PALETTE_LIGHT_GREEN,
|
||||||
|
lime = c.LV_PALETTE_LIME,
|
||||||
|
yellow = c.LV_PALETTE_YELLOW,
|
||||||
|
amber = c.LV_PALETTE_AMBER,
|
||||||
|
orange = c.LV_PALETTE_ORANGE,
|
||||||
|
deep_orange = c.LV_PALETTE_DEEP_ORANGE,
|
||||||
|
brown = c.LV_PALETTE_BROWN,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// returns main color from the predefined palette.
|
||||||
|
pub inline fn paletteMain(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 darker according to the
|
||||||
|
/// specified level.
|
||||||
|
pub inline fn paletteDarken(p: Palette, l: PaletteModLevel) Color {
|
||||||
|
return lv_palette_darken(@enumToInt(p), @enumToInt(l));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// represents lv_obj_t type in C.
|
||||||
|
pub const LvObj = opaque {
|
||||||
|
/// deallocates all the resources used by the object, including its children.
|
||||||
|
/// user data pointers are untouched.
|
||||||
|
pub fn destroy(self: *LvObj) void {
|
||||||
|
lv_obj_del(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reports whether the object has v flag set.
|
||||||
|
pub fn hasFlag(self: *LvObj, v: ObjFlag) bool {
|
||||||
|
return lv_obj_has_flag(self, @enumToInt(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns a user data pointer associated with the object.
|
||||||
|
pub fn userdata(self: *LvObj) ?*anyopaque {
|
||||||
|
return nm_obj_userdata(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets object horizontal length.
|
||||||
|
pub fn setWidth(self: *LvObj, val: Coord) void {
|
||||||
|
lv_obj_set_width(self, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets object vertical length.
|
||||||
|
pub fn setHeight(self: *LvObj, val: Coord) void {
|
||||||
|
lv_obj_set_height(self, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets object height to its child contents.
|
||||||
|
pub fn setHeightToContent(self: *LvObj) void {
|
||||||
|
lv_obj_set_height(self, sizeContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets both width and height to 100%.
|
||||||
|
pub fn resizeToMax(self: *LvObj) void {
|
||||||
|
lv_obj_set_size(self, sizePercent(100), sizePercent(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// selects which side to pad in setPad func.
|
||||||
|
pub const PadSelector = enum { 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 {
|
||||||
|
switch (p) {
|
||||||
|
.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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 createObject(parent: *LvObj) !*LvObj {
|
||||||
|
return lv_obj_create(parent) orelse error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createFlexObject(parent: *LvObj, flow: FlexFlow) !*LvObj {
|
||||||
|
var o = try createObject(parent);
|
||||||
|
o.flexFlow(flow);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createTopObject() !*LvObj {
|
||||||
|
const toplayer = lv_disp_get_layer_top(null);
|
||||||
|
return lv_obj_create(toplayer) orelse error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
};
|
||||||
|
|
||||||
|
/// possible states of a widget, equivalent to lv_state_t in C.
|
||||||
|
/// OR'ed values are possible.
|
||||||
|
pub const State = enum(c.lv_state_t) {
|
||||||
|
default = c.LV_STATE_DEFAULT,
|
||||||
|
checked = c.LV_STATE_CHECKED,
|
||||||
|
focused = c.LV_STATE_FOCUSED,
|
||||||
|
focuse_key = c.LV_STATE_FOCUS_KEY,
|
||||||
|
edited = c.LV_STATE_EDITED,
|
||||||
|
hovered = c.LV_STATE_HOVERED,
|
||||||
|
pressed = c.LV_STATE_PRESSED,
|
||||||
|
scrolled = c.LV_STATE_SCROLLED,
|
||||||
|
disabled = c.LV_STATE_DISABLED,
|
||||||
|
|
||||||
|
user1 = c.LV_STATE_USER_1,
|
||||||
|
user2 = c.LV_STATE_USER_2,
|
||||||
|
user3 = c.LV_STATE_USER_3,
|
||||||
|
user4 = c.LV_STATE_USER_4,
|
||||||
|
|
||||||
|
any = c.LV_STATE_ANY, // special value can be used in some functions to target all states
|
||||||
|
};
|
||||||
|
|
||||||
|
/// possible parts of a widget, equivalent to lv_part_t in C.
|
||||||
|
/// a part is an internal building block of a widget. for example, a slider
|
||||||
|
/// consists of background, an indicator and a knob.
|
||||||
|
pub const Part = enum(c.lv_part_t) {
|
||||||
|
main = c.LV_PART_MAIN, // a background like rectangle
|
||||||
|
scrollbar = c.LV_PART_SCROLLBAR,
|
||||||
|
indicator = c.LV_PART_INDICATOR, // an indicator for slider, bar, switch, or the tick box of the checkbox
|
||||||
|
knob = c.LV_PART_KNOB, // like a handle to grab to adjust the value
|
||||||
|
selected = c.LV_PART_SELECTED, // the currently selected option or section
|
||||||
|
item = c.LV_PART_ITEMS, // used when the widget has multiple similar elements like table cells
|
||||||
|
ticks = c.LV_PART_TICKS, // ticks on a scale like in a chart or a meter
|
||||||
|
cursor = c.LV_PART_CURSOR, // a specific place in text areas or a chart
|
||||||
|
|
||||||
|
custom1 = c.LV_PART_CUSTOM_FIRST, // extension point for custom widgets
|
||||||
|
|
||||||
|
any = c.LV_PART_ANY, // special value can be used in some functions to target all parts
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Coord is a pixel unit for all x/y coordinates and dimensions.
|
||||||
|
pub const Coord = c.lv_coord_t;
|
||||||
|
|
||||||
|
/// converts a percentage value between 0 and 1000 to a regular unit.
|
||||||
|
/// equivalent to LV_PCT in C.
|
||||||
|
pub inline fn sizePercent(v: Coord) Coord {
|
||||||
|
return if (v < 0) LV_COORD_SET_SPEC(1000 - v) else LV_COORD_SET_SPEC(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a special constant setting a [part of] widget to its contents.
|
||||||
|
/// equivalent to LV_SIZE_CONTENT in C.
|
||||||
|
pub const sizeContent = LV_COORD_SET_SPEC(2001);
|
||||||
|
|
||||||
|
// from lv_area.h
|
||||||
|
//#if LV_USE_LARGE_COORD
|
||||||
|
//#define _LV_COORD_TYPE_SHIFT (29U)
|
||||||
|
//#else
|
||||||
|
//#define _LV_COORD_TYPE_SHIFT (13U)
|
||||||
|
//#endif
|
||||||
|
const _LV_COORD_TYPE_SHIFT = 13; // TODO: comptime switch between 13 and 29?
|
||||||
|
const _LV_COORD_TYPE_SPEC = 1 << _LV_COORD_TYPE_SHIFT;
|
||||||
|
|
||||||
|
inline fn LV_COORD_SET_SPEC(x: Coord) Coord {
|
||||||
|
return x | _LV_COORD_TYPE_SPEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// object position alignments.
|
||||||
|
pub const PosAlign = enum(c.lv_align_t) {
|
||||||
|
default = c.LV_ALIGN_DEFAULT,
|
||||||
|
top_left = c.LV_ALIGN_TOP_LEFT,
|
||||||
|
top_mid = c.LV_ALIGN_TOP_MID,
|
||||||
|
top_right = c.LV_ALIGN_TOP_RIGHT,
|
||||||
|
bottom_left = c.LV_ALIGN_BOTTOM_LEFT,
|
||||||
|
bottom_mid = c.LV_ALIGN_BOTTOM_MID,
|
||||||
|
bottom_right = c.LV_ALIGN_BOTTOM_RIGHT,
|
||||||
|
left_mid = c.LV_ALIGN_LEFT_MID,
|
||||||
|
right_mid = c.LV_ALIGN_RIGHT_MID,
|
||||||
|
center = c.LV_ALIGN_CENTER,
|
||||||
|
|
||||||
|
out_top_left = c.LV_ALIGN_OUT_TOP_LEFT,
|
||||||
|
out_top_mid = c.LV_ALIGN_OUT_TOP_MID,
|
||||||
|
out_top_right = c.LV_ALIGN_OUT_TOP_RIGHT,
|
||||||
|
out_bottom_left = c.LV_ALIGN_OUT_BOTTOM_LEFT,
|
||||||
|
out_bottom_mid = c.LV_ALIGN_OUT_BOTTOM_MID,
|
||||||
|
out_bottom_right = c.LV_ALIGN_OUT_BOTTOM_RIGHT,
|
||||||
|
out_left_top = c.LV_ALIGN_OUT_LEFT_TOP,
|
||||||
|
out_left_mid = c.LV_ALIGN_OUT_LEFT_MID,
|
||||||
|
out_left_bottom = c.LV_ALIGN_OUT_LEFT_BOTTOM,
|
||||||
|
out_right_top = c.LV_ALIGN_OUT_RIGHT_TOP,
|
||||||
|
out_right_mid = c.LV_ALIGN_OUT_RIGHT_MID,
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const Window = struct {
|
||||||
|
winobj: *LvObj,
|
||||||
|
|
||||||
|
pub fn content(self: Window) *LvObj {
|
||||||
|
return lv_win_get_content(self.winobj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CreateLabelOpt = struct {
|
||||||
|
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
|
||||||
|
},
|
||||||
|
pos: enum {
|
||||||
|
none,
|
||||||
|
centered,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
lv_label_set_long_mode(lb, @enumToInt(opt.long_mode));
|
||||||
|
if (opt.pos == .centered) {
|
||||||
|
lb.center();
|
||||||
|
}
|
||||||
|
return lb;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = .centered });
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// 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_set_userdata(obj: *LvObj, data: ?*const anyopaque) void;
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// imports from LVGL C code
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
extern fn lv_init() void;
|
||||||
|
extern fn lv_log_register_print_cb(*const fn (msg: [*:0]const u8) callconv(.C) void) void;
|
||||||
|
|
||||||
|
// input devices ------------------------------------------------------------
|
||||||
|
|
||||||
|
/// deallocate and delete an input device from LVGL registry.
|
||||||
|
extern fn lv_indev_delete(indev: *LvIndev) void;
|
||||||
|
/// return next device in the list or head if indev is null.
|
||||||
|
extern fn lv_indev_get_next(indev: ?*LvIndev) ?*LvIndev;
|
||||||
|
|
||||||
|
// timers -------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 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_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_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;
|
||||||
|
|
||||||
|
// display and screen functions ----------------------------------------------
|
||||||
|
|
||||||
|
/// returns pointer to the default display.
|
||||||
|
extern fn lv_disp_get_default() *LvDisp;
|
||||||
|
/// returns elapsed time since last user activity on a specific display or any if disp is null.
|
||||||
|
extern fn lv_disp_get_inactive_time(disp: ?*LvDisp) u32;
|
||||||
|
/// makes it so as if a user activity happened.
|
||||||
/// this resets an internal counter in lv_disp_get_inactive_time.
|
/// this resets an internal counter in lv_disp_get_inactive_time.
|
||||||
pub extern "c" fn lv_disp_trig_activity(disp: ?*LvDisp) void;
|
extern fn lv_disp_trig_activity(disp: ?*LvDisp) void;
|
||||||
/// force redraw dirty areas.
|
/// forces redraw of dirty areas.
|
||||||
pub extern "c" fn lv_refr_now(disp: ?*LvDisp) void;
|
extern fn lv_refr_now(disp: ?*LvDisp) void;
|
||||||
|
|
||||||
|
extern fn lv_disp_get_hor_res(disp: ?*LvDisp) c.lv_coord_t;
|
||||||
|
extern fn lv_disp_get_ver_res(disp: ?*LvDisp) c.lv_coord_t;
|
||||||
|
|
||||||
|
/// returns a pointer to the active screen on a given display
|
||||||
|
/// or default if null. if no display is registered, returns null.
|
||||||
|
extern fn lv_disp_get_scr_act(disp: ?*LvDisp) ?*LvObj;
|
||||||
|
/// returns the top layer on a given display or default if null.
|
||||||
|
/// top layer is the same on every screen, above the normal screen layer.
|
||||||
|
extern fn lv_disp_get_layer_top(disp: ?*LvDisp) *LvObj;
|
||||||
|
/// makes a screen active without animation.
|
||||||
|
extern fn lv_disp_load_scr(scr: *LvObj) void;
|
||||||
|
|
||||||
|
// styling and colors --------------------------------------------------------
|
||||||
|
|
||||||
|
/// initalizes a style struct. must be called only once per style.
|
||||||
|
extern fn lv_style_init(style: *LvStyle) void;
|
||||||
|
extern fn lv_style_set_bg_color(style: *LvStyle, color: Color) void;
|
||||||
|
|
||||||
|
extern fn lv_obj_add_style(obj: *LvObj, style: *LvStyle, sel: c.lv_style_selector_t) void;
|
||||||
|
extern fn lv_obj_remove_style(obj: *LvObj, style: ?*LvStyle, sel: c.lv_style_selector_t) void;
|
||||||
|
extern fn lv_obj_remove_style_all(obj: *LvObj) void;
|
||||||
|
extern fn lv_obj_set_style_bg_color(obj: *LvObj, val: Color, sel: c.lv_style_selector_t) void;
|
||||||
|
extern fn lv_obj_set_style_pad_left(obj: *LvObj, val: c.lv_coord_t, sel: c.lv_style_selector_t) void;
|
||||||
|
extern fn lv_obj_set_style_pad_right(obj: *LvObj, val: c.lv_coord_t, sel: c.lv_style_selector_t) void;
|
||||||
|
extern fn lv_obj_set_style_pad_top(obj: *LvObj, val: c.lv_coord_t, sel: c.lv_style_selector_t) void;
|
||||||
|
extern fn lv_obj_set_style_pad_bottom(obj: *LvObj, val: c.lv_coord_t, sel: c.lv_style_selector_t) void;
|
||||||
|
extern fn lv_obj_set_style_pad_row(obj: *LvObj, val: c.lv_coord_t, sel: c.lv_style_selector_t) void;
|
||||||
|
extern fn lv_obj_set_style_pad_column(obj: *LvObj, val: c.lv_coord_t, sel: c.lv_style_selector_t) void;
|
||||||
|
|
||||||
|
// TODO: port these to zig
|
||||||
|
extern fn lv_palette_main(c.lv_palette_t) Color;
|
||||||
|
extern fn lv_palette_lighten(c.lv_palette_t, level: u8) Color;
|
||||||
|
extern fn lv_palette_darken(c.lv_palette_t, level: u8) Color;
|
||||||
|
|
||||||
|
// objects and widgets -------------------------------------------------------
|
||||||
|
|
||||||
|
/// creates a new base object at the specific parent or a new screen if the parent is null.
|
||||||
|
extern fn lv_obj_create(parent: ?*LvObj) ?*LvObj;
|
||||||
|
/// deletes and deallocates an object and all its children from UI tree.
|
||||||
|
extern fn lv_obj_del(obj: *LvObj) void;
|
||||||
|
|
||||||
|
extern fn lv_obj_add_flag(obj: *LvObj, v: c.lv_obj_flag_t) void;
|
||||||
|
extern fn lv_obj_clear_flag(obj: *LvObj, v: c.lv_obj_flag_t) void;
|
||||||
|
extern fn lv_obj_has_flag(obj: *LvObj, v: c.lv_obj_flag_t) bool;
|
||||||
|
|
||||||
|
extern fn lv_obj_align(obj: *LvObj, a: c.lv_align_t, x: c.lv_coord_t, y: c.lv_coord_t) void;
|
||||||
|
extern fn lv_obj_set_height(obj: *LvObj, h: c.lv_coord_t) void;
|
||||||
|
extern fn lv_obj_set_width(obj: *LvObj, w: c.lv_coord_t) void;
|
||||||
|
extern fn lv_obj_set_size(obj: *LvObj, w: c.lv_coord_t, h: c.lv_coord_t) void;
|
||||||
|
|
||||||
|
extern fn lv_obj_set_flex_flow(obj: *LvObj, flow: c.lv_flex_flow_t) void;
|
||||||
|
extern fn lv_obj_set_flex_grow(obj: *LvObj, val: u8) void;
|
||||||
|
extern fn lv_obj_set_flex_align(obj: *LvObj, main: c.lv_flex_align_t, cross: c.lv_flex_align_t, track: c.lv_flex_align_t) void;
|
||||||
|
|
||||||
|
extern fn lv_btn_create(parent: *LvObj) ?*LvObj;
|
||||||
|
|
||||||
|
extern fn lv_btnmatrix_create(parent: *LvObj) ?*LvObj;
|
||||||
|
extern fn lv_btnmatrix_set_selected_btn(obj: *LvObj, id: u16) void;
|
||||||
|
extern fn lv_btnmatrix_set_map(obj: *LvObj, map: [*]const [*:0]const u8) void;
|
||||||
|
extern fn lv_btnmatrix_set_btn_ctrl(obj: *LvObj, id: u16, ctrl: c.lv_btnmatrix_ctrl_t) void;
|
||||||
|
extern fn lv_btnmatrix_set_btn_ctrl_all(obj: *LvObj, ctrl: c.lv_btnmatrix_ctrl_t) void;
|
||||||
|
|
||||||
|
extern fn lv_label_create(parent: *LvObj) ?*LvObj;
|
||||||
|
extern fn lv_label_set_text(label: *LvObj, text: [*:0]const u8) void;
|
||||||
|
extern fn lv_label_set_text_static(label: *LvObj, text: [*:0]const u8) void;
|
||||||
|
extern fn lv_label_set_long_mode(label: *LvObj, mode: c.lv_label_long_mode_t) void;
|
||||||
|
extern fn lv_label_set_recolor(label: *LvObj, enable: bool) 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;
|
||||||
|
|
|
@ -4,7 +4,7 @@ const Thread = std.Thread;
|
||||||
|
|
||||||
const lvgl = @import("lvgl.zig");
|
const lvgl = @import("lvgl.zig");
|
||||||
const drv = @import("drv.zig");
|
const drv = @import("drv.zig");
|
||||||
const ui = @import("ui.zig");
|
const widget = @import("widget.zig");
|
||||||
|
|
||||||
const logger = std.log.scoped(.screen);
|
const logger = std.log.scoped(.screen);
|
||||||
|
|
||||||
|
@ -14,10 +14,10 @@ const logger = std.log.scoped(.screen);
|
||||||
/// a touch event triggers no accidental action.
|
/// a touch event triggers no accidental action.
|
||||||
pub fn sleep(wake: *const Thread.ResetEvent) void {
|
pub fn sleep(wake: *const Thread.ResetEvent) void {
|
||||||
drv.deinitInput();
|
drv.deinitInput();
|
||||||
ui.topdrop(.show);
|
widget.topdrop(.show);
|
||||||
defer {
|
defer {
|
||||||
drv.initInput() catch |err| logger.err("drv.initInput: {any}", .{err});
|
drv.initInput() catch |err| logger.err("drv.initInput: {any}", .{err});
|
||||||
ui.topdrop(.remove);
|
widget.topdrop(.remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
const watcher = drv.InputWatcher() catch |err| {
|
const watcher = drv.InputWatcher() catch |err| {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
///! see lv_symbols_def.h
|
///! see lv_symbols_def.h
|
||||||
pub const Warning = &[_]u8{ 0xef, 0x81, 0xb1 };
|
|
||||||
pub const Ok = &[_]u8{ 0xef, 0x80, 0x8c };
|
pub const Ok = &[_]u8{ 0xef, 0x80, 0x8c };
|
||||||
|
pub const Power = &[_]u8{ 0xef, 0x80, 0x91 };
|
||||||
|
pub const Warning = &[_]u8{ 0xef, 0x81, 0xb1 };
|
||||||
|
|
|
@ -2,12 +2,13 @@ const std = @import("std");
|
||||||
|
|
||||||
const lvgl = @import("lvgl.zig");
|
const lvgl = @import("lvgl.zig");
|
||||||
const drv = @import("drv.zig");
|
const drv = @import("drv.zig");
|
||||||
|
const symbol = @import("symbol.zig");
|
||||||
|
const widget = @import("widget.zig");
|
||||||
|
|
||||||
const logger = std.log.scoped(.ui);
|
const logger = std.log.scoped(.ui);
|
||||||
|
|
||||||
extern "c" fn nm_ui_init(disp: *lvgl.LvDisp) c_int;
|
extern "c" fn nm_ui_init(disp: *lvgl.LvDisp) c_int;
|
||||||
extern "c" fn nm_make_topdrop() ?*lvgl.LvObj;
|
extern fn nm_sys_shutdown() void;
|
||||||
extern "c" fn nm_remove_topdrop() void;
|
|
||||||
|
|
||||||
pub fn init() !void {
|
pub fn init() !void {
|
||||||
lvgl.init();
|
lvgl.init();
|
||||||
|
@ -23,26 +24,29 @@ pub fn init() !void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// unsafe for concurrent use.
|
/// called when "power off" button is pressed.
|
||||||
pub fn topdrop(onoff: enum { show, remove }) void {
|
export fn nm_poweroff_btn_callback(e: *lvgl.LvEvent) void {
|
||||||
// a static construct: there can be only one global topdrop.
|
_ = e;
|
||||||
// see https://ziglang.org/documentation/master/#Static-Local-Variables
|
const proceed: [*:0]const u8 = "PROCEED";
|
||||||
const S = struct {
|
const abort: [*:0]const u8 = "CANCEL";
|
||||||
var lv_obj: ?*lvgl.LvObj = null;
|
const title = " " ++ symbol.Power ++ " SHUTDOWN";
|
||||||
|
const text =
|
||||||
|
\\ARE YOU SURE?
|
||||||
|
\\
|
||||||
|
\\once shut down,
|
||||||
|
\\payments cannot go through via bitcoin or lightning networks
|
||||||
|
\\until the node is powered back on.
|
||||||
|
;
|
||||||
|
widget.modal(title, text, &.{ proceed, abort }, poweroffModalCallback) catch |err| {
|
||||||
|
logger.err("shutdown btn: modal: {any}", .{err});
|
||||||
};
|
};
|
||||||
switch (onoff) {
|
}
|
||||||
.show => {
|
|
||||||
if (S.lv_obj != null) {
|
fn poweroffModalCallback(btn_idx: usize) void {
|
||||||
|
// proceed = 0, cancel = 1
|
||||||
|
if (btn_idx != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
S.lv_obj = nm_make_topdrop();
|
// proceed with shutdown
|
||||||
lvgl.lv_refr_now(null);
|
nm_sys_shutdown();
|
||||||
},
|
|
||||||
.remove => {
|
|
||||||
if (S.lv_obj) |v| {
|
|
||||||
lvgl.lv_obj_del(v);
|
|
||||||
S.lv_obj = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const lvgl = @import("lvgl.zig");
|
||||||
|
|
||||||
|
const logger = std.log.scoped(.ui);
|
||||||
|
|
||||||
|
/// creates an opposite of a backdrop: a plain black square on the top layer
|
||||||
|
/// covering the whole screen. useful for standby/sleep mode on systems where
|
||||||
|
/// cutting screen power is unsupported.
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
const S = struct {
|
||||||
|
var lv_obj: ?*lvgl.LvObj = null;
|
||||||
|
};
|
||||||
|
switch (onoff) {
|
||||||
|
.show => {
|
||||||
|
if (S.lv_obj != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const o = lvgl.createTopObject() catch |err| {
|
||||||
|
logger.err("topdrop: lvgl.createTopObject: {any}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
o.setFlag(.on, .ignore_layout);
|
||||||
|
o.resizeToMax();
|
||||||
|
o.setBackgroundColor(lvgl.Black, .{});
|
||||||
|
|
||||||
|
S.lv_obj = o;
|
||||||
|
lvgl.displayRedraw();
|
||||||
|
},
|
||||||
|
.remove => {
|
||||||
|
if (S.lv_obj) |o| {
|
||||||
|
o.destroy();
|
||||||
|
S.lv_obj = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// modal callback func type. it receives 0-based index of a button item
|
||||||
|
/// provided as btns arg to modal.
|
||||||
|
pub const ModalButtonCallbackFn = *const fn (index: usize) void;
|
||||||
|
|
||||||
|
/// shows a non-dismissible window using the whole screen real estate;
|
||||||
|
/// for use in place of lv_msgbox_create.
|
||||||
|
///
|
||||||
|
/// while all heap-alloc'ed resources are free'd automatically right before cb is called,
|
||||||
|
/// the value of title, text and btns args must live at least as long as cb; they are
|
||||||
|
/// memory-managed by the callers.
|
||||||
|
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(@ptrCast(?*const anyopaque, cb));
|
||||||
|
|
||||||
|
const wincont = win.content();
|
||||||
|
wincont.flexFlow(.column);
|
||||||
|
wincont.flexAlign(.start, .center, .center);
|
||||||
|
const msg = try lvgl.createLabel(wincont, text, .{ .long_mode = .wrap, .pos = .centered });
|
||||||
|
msg.setWidth(lvgl.displayHoriz() - 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);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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();
|
||||||
|
cb(btn_index);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue