From a3935252125cc8567493481ed090dcf87e02fc04 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Wed, 31 Jul 2024 13:58:49 +0200 Subject: [PATCH] Support multiple info lines in UI Signed-off-by: AnErrupTion --- src/enums.zig | 1 + src/main.zig | 96 +++++++++++++++++++-------------- src/tui/TerminalBuffer.zig | 8 ++- src/tui/components/InfoLine.zig | 41 +++++++------- src/tui/components/Session.zig | 2 +- src/tui/components/generic.zig | 11 ++-- 6 files changed, 94 insertions(+), 65 deletions(-) diff --git a/src/enums.zig b/src/enums.zig index d62673b..84b011e 100644 --- a/src/enums.zig +++ b/src/enums.zig @@ -12,6 +12,7 @@ pub const DisplayServer = enum { }; pub const Input = enum { + info_line, session, login, password, diff --git a/src/main.zig b/src/main.zig index e5ea380..6f75c8a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -84,8 +84,7 @@ pub fn main() !void { var config: Config = undefined; var lang: Lang = undefined; var save: Save = undefined; - var info_line = InfoLine.init(allocator); - defer info_line.deinit(); + var config_load_failed = false; if (res.args.help != 0) { try clap.help(stderr, clap.Help, ¶ms, .{}); @@ -123,8 +122,7 @@ pub fn main() !void { defer allocator.free(config_path); config = config_ini.readFileToStruct(config_path, comment_characters, migrator.configFieldHandler) catch _config: { - // We're using a literal error message here since the language file hasn't yet been loaded - try info_line.addMessage("unable to parse config file", @intCast(interop.termbox.TB_DEFAULT), @intCast(interop.termbox.TB_RED | interop.termbox.TB_BOLD)); + config_load_failed = true; break :_config Config{}; }; @@ -156,8 +154,7 @@ pub fn main() !void { const config_path = build_options.data_directory ++ "/config.ini"; config = config_ini.readFileToStruct(config_path, comment_characters, migrator.configFieldHandler) catch _config: { - // literal error message, due to language file not yet available - try info_line.addMessage("unable to parse config file", @intCast(interop.termbox.TB_DEFAULT), @intCast(interop.termbox.TB_RED | interop.termbox.TB_BOLD)); + config_load_failed = true; break :_config Config{}; }; @@ -189,22 +186,8 @@ pub fn main() !void { shutdown_cmd = try temporary_allocator.dupe(u8, config.shutdown_cmd); restart_cmd = try temporary_allocator.dupe(u8, config.restart_cmd); - if (!build_options.enable_x11_support) try info_line.addMessage(lang.no_x11_support, config.bg, config.fg); - interop.setNumlock(config.numlock) catch {}; - if (config.initial_info_text) |text| { - try info_line.addMessage(text, config.bg, config.fg); - } else get_host_name: { - // Initialize information line with host name - var name_buf: [std.posix.HOST_NAME_MAX]u8 = undefined; - const hostname = std.posix.gethostname(&name_buf) catch { - try info_line.addMessage(lang.err_hostname, config.error_bg, config.error_fg); - break :get_host_name; - }; - try info_line.addMessage(hostname, config.bg, config.fg); - } - // Initialize termbox _ = termbox.tb_init(); defer _ = termbox.tb_shutdown(); @@ -225,9 +208,8 @@ pub fn main() !void { // Initialize terminal buffer const labels_max_length = @max(lang.login.len, lang.password.len); - // Get a random seed for the PRNG (used by animations) var seed: u64 = undefined; - try std.posix.getrandom(std.mem.asBytes(&seed)); + try std.posix.getrandom(std.mem.asBytes(&seed)); // Get a random seed for the PRNG (used by animations) var prng = std.Random.DefaultPrng.init(seed); const random = prng.random(); @@ -235,6 +217,13 @@ pub fn main() !void { var buffer = TerminalBuffer.init(config, labels_max_length, random); // Initialize components + var info_line = try InfoLine.init(allocator, &buffer, 255); + defer info_line.deinit(); + + if (config_load_failed) { + try info_line.addMessage("unable to parse config file", config.error_bg, config.error_fg); + } + var session = try Session.init(allocator, &buffer, config.max_desktop_len, lang); defer session.deinit(); @@ -248,6 +237,20 @@ pub fn main() !void { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); }; } + } else { + try info_line.addMessage(lang.no_x11_support, config.bg, config.fg); + } + + if (config.initial_info_text) |text| { + try info_line.addMessage(text, config.bg, config.fg); + } else get_host_name: { + // Initialize information line with host name + var name_buf: [std.posix.HOST_NAME_MAX]u8 = undefined; + const hostname = std.posix.gethostname(&name_buf) catch { + try info_line.addMessage(lang.err_hostname, config.error_bg, config.error_fg); + break :get_host_name; + }; + try info_line.addMessage(hostname, config.bg, config.fg); } try session.crawl(config.waylandsessions, .wayland); @@ -281,11 +284,13 @@ pub fn main() !void { buffer.drawBoxCenter(!config.hide_borders, config.blank_box); const coordinates = buffer.calculateComponentCoordinates(); + info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length); session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length); login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length); password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); switch (active_input) { + .info_line => info_line.label.handle(null, insert_mode), .session => session.label.handle(null, insert_mode), .login => login.handle(null, insert_mode) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); @@ -404,6 +409,7 @@ pub fn main() !void { if (resolution_changed) { const coordinates = buffer.calculateComponentCoordinates(); + info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length); session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length); login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length); password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); @@ -412,6 +418,7 @@ pub fn main() !void { } switch (active_input) { + .info_line => info_line.label.handle(null, insert_mode), .session => session.label.handle(null, insert_mode), .login => login.handle(null, insert_mode) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); @@ -438,7 +445,7 @@ pub fn main() !void { buffer.drawLabel(lang.login, label_x, label_y + 4); buffer.drawLabel(lang.password, label_x, label_y + 6); - try info_line.draw(buffer); + info_line.label.draw(); if (!config.hide_key_hints) { var length: usize = 0; @@ -489,22 +496,22 @@ pub fn main() !void { buffer.drawLabel(label_txt, buffer.box_x, buffer.box_y + buffer.box_height); } - draw_lock_state: { - const lock_state = interop.getLockState(config.console_dev) catch { - try info_line.addMessage(lang.err_console_dev, config.error_bg, config.error_fg); - break :draw_lock_state; - }; + // draw_lock_state: { + // const lock_state = interop.getLockState(config.console_dev) catch { + // try info_line.addMessage(lang.err_console_dev, config.error_bg, config.error_fg); + // break :draw_lock_state; + // }; - var lock_state_x = buffer.width - @min(buffer.width, lang.numlock.len); - const lock_state_y: usize = if (config.clock != null) 1 else 0; + // var lock_state_x = buffer.width - @min(buffer.width, lang.numlock.len); + // const lock_state_y: usize = if (config.clock != null) 1 else 0; - if (lock_state.numlock) buffer.drawLabel(lang.numlock, lock_state_x, lock_state_y); + // if (lock_state.numlock) buffer.drawLabel(lang.numlock, lock_state_x, lock_state_y); - if (lock_state_x >= lang.capslock.len + 1) { - lock_state_x -= lang.capslock.len + 1; - if (lock_state.capslock) buffer.drawLabel(lang.capslock, lock_state_x, lock_state_y); - } - } + // if (lock_state_x >= lang.capslock.len + 1) { + // lock_state_x -= lang.capslock.len + 1; + // if (lock_state.capslock) buffer.drawLabel(lang.capslock, lock_state_x, lock_state_y); + // } + // } session.label.draw(); login.draw(); @@ -595,13 +602,15 @@ pub fn main() !void { }, termbox.TB_KEY_CTRL_K, termbox.TB_KEY_ARROW_UP => { active_input = switch (active_input) { - .session, .login => .session, + .session, .info_line => .info_line, + .login => .session, .password => .login, }; update = true; }, termbox.TB_KEY_CTRL_J, termbox.TB_KEY_ARROW_DOWN => { active_input = switch (active_input) { + .info_line => .session, .session => .login, .login, .password => .password, }; @@ -609,15 +618,17 @@ pub fn main() !void { }, termbox.TB_KEY_TAB => { active_input = switch (active_input) { + .info_line => .session, .session => .login, .login => .password, - .password => .session, + .password => .info_line, }; update = true; }, termbox.TB_KEY_BACK_TAB => { active_input = switch (active_input) { - .session => .password, + .info_line => .password, + .session => .info_line, .login => .session, .password => .login, }; @@ -647,7 +658,7 @@ pub fn main() !void { try info_line.addMessage(lang.authenticating, config.bg, config.fg); InfoLine.clearRendered(allocator, buffer) catch {}; - try info_line.draw(buffer); + info_line.label.draw(); _ = termbox.tb_present(); session_pid = try std.posix.fork(); @@ -691,7 +702,8 @@ pub fn main() !void { switch (event.ch) { 'k' => { active_input = switch (active_input) { - .session, .login => .session, + .session, .info_line => .info_line, + .login => .session, .password => .login, }; update = true; @@ -699,6 +711,7 @@ pub fn main() !void { }, 'j' => { active_input = switch (active_input) { + .info_line => .session, .session => .login, .login, .password => .password, }; @@ -715,6 +728,7 @@ pub fn main() !void { } switch (active_input) { + .info_line => info_line.label.handle(&event, insert_mode), .session => session.label.handle(&event, insert_mode), .login => login.handle(&event, insert_mode) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); diff --git a/src/tui/TerminalBuffer.zig b/src/tui/TerminalBuffer.zig index 71bf8aa..2d36a54 100644 --- a/src/tui/TerminalBuffer.zig +++ b/src/tui/TerminalBuffer.zig @@ -142,17 +142,23 @@ pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) } pub fn calculateComponentCoordinates(self: TerminalBuffer) struct { + start_x: usize, x: usize, y: usize, + full_visible_length: usize, visible_length: usize, } { - const x = self.box_x + self.margin_box_h + self.labels_max_length + 1; + const start_x = self.box_x + self.margin_box_h; + const x = start_x + self.labels_max_length + 1; const y = self.box_y + self.margin_box_v; + const full_visible_length = self.box_x + self.box_width - self.margin_box_h - start_x; const visible_length = self.box_x + self.box_width - self.margin_box_h - x; return .{ + .start_x = start_x, .x = x, .y = y, + .full_visible_length = full_visible_length, .visible_length = visible_length, }; } diff --git a/src/tui/components/InfoLine.zig b/src/tui/components/InfoLine.zig index 1c41474..695c8f5 100644 --- a/src/tui/components/InfoLine.zig +++ b/src/tui/components/InfoLine.zig @@ -1,6 +1,11 @@ const std = @import("std"); -const utils = @import("../utils.zig"); const TerminalBuffer = @import("../TerminalBuffer.zig"); +const generic = @import("generic.zig"); +const utils = @import("../utils.zig"); + +const Allocator = std.mem.Allocator; + +const MessageLabel = generic.CyclableLabel(Message); const InfoLine = @This(); @@ -10,24 +15,23 @@ const Message = struct { bg: u16, fg: u16, }; -const MessageList = std.ArrayList(Message); -messages: MessageList, +label: MessageLabel, -pub fn init(allocator: std.mem.Allocator) InfoLine { +pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: usize) !InfoLine { return .{ - .messages = MessageList.init(allocator), + .label = try MessageLabel.init(allocator, buffer, max_length, drawItem), }; } pub fn deinit(self: InfoLine) void { - self.messages.deinit(); + self.label.deinit(); } pub fn addMessage(self: *InfoLine, text: []const u8, bg: u16, fg: u16) !void { if (text.len == 0) return; - try self.messages.append(.{ + try self.label.addItem(.{ .width = try utils.strWidth(text), .text = text, .bg = bg, @@ -35,18 +39,7 @@ pub fn addMessage(self: *InfoLine, text: []const u8, bg: u16, fg: u16) !void { }); } -pub fn draw(self: InfoLine, buffer: TerminalBuffer) !void { - if (self.messages.items.len == 0) return; - - const entry = self.messages.getLast(); - if (entry.width == 0 or buffer.box_width <= entry.width) return; - - const label_y = buffer.box_y + buffer.margin_box_v; - const x = buffer.box_x + ((buffer.box_width - entry.width) / 2); - TerminalBuffer.drawColorLabel(entry.text, x, label_y, entry.fg, entry.bg); -} - -pub fn clearRendered(allocator: std.mem.Allocator, buffer: TerminalBuffer) !void { +pub fn clearRendered(allocator: Allocator, buffer: TerminalBuffer) !void { // Draw over the area const y = buffer.box_y + buffer.margin_box_v; const spaces = try allocator.alloc(u8, buffer.box_width); @@ -56,3 +49,13 @@ pub fn clearRendered(allocator: std.mem.Allocator, buffer: TerminalBuffer) !void buffer.drawLabel(spaces, buffer.box_x, y); } + +fn drawItem(label: *MessageLabel, message: Message, _: usize, _: usize) bool { + if (message.width == 0 or label.buffer.box_width <= message.width) return false; + + const x = label.buffer.box_x + ((label.buffer.box_width - message.width) / 2); + label.first_char_x = x; + + TerminalBuffer.drawColorLabel(message.text, x, label.y, message.fg, message.bg); + return true; +} diff --git a/src/tui/components/Session.zig b/src/tui/components/Session.zig index f994ca2..0127a10 100644 --- a/src/tui/components/Session.zig +++ b/src/tui/components/Session.zig @@ -143,7 +143,7 @@ pub fn crawl(self: *Session, path: []const u8, display_server: DisplayServer) !v } } -fn drawItem(label: EnvironmentLabel, environment: Environment, x: usize, y: usize) bool { +fn drawItem(label: *EnvironmentLabel, environment: Environment, x: usize, y: usize) bool { const length = @min(environment.name.len, label.visible_length - 3); if (length == 0) return false; diff --git a/src/tui/components/generic.zig b/src/tui/components/generic.zig index 99d4a27..b5252b6 100644 --- a/src/tui/components/generic.zig +++ b/src/tui/components/generic.zig @@ -7,7 +7,7 @@ pub fn CyclableLabel(comptime ItemType: type) type { return struct { const Allocator = std.mem.Allocator; const ItemList = std.ArrayList(ItemType); - const DrawItemFn = *const fn (Self, ItemType, usize, usize) bool; + const DrawItemFn = *const fn (*Self, ItemType, usize, usize) bool; const termbox = interop.termbox; @@ -20,6 +20,7 @@ pub fn CyclableLabel(comptime ItemType: type) type { visible_length: usize, x: usize, y: usize, + first_char_x: usize, draw_item_fn: DrawItemFn, pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: usize, draw_item_fn: DrawItemFn) !Self { @@ -31,6 +32,7 @@ pub fn CyclableLabel(comptime ItemType: type) type { .visible_length = 0, .x = 0, .y = 0, + .first_char_x = 0, .draw_item_fn = draw_item_fn, }; } @@ -43,6 +45,7 @@ pub fn CyclableLabel(comptime ItemType: type) type { self.x = x; self.y = y; self.visible_length = visible_length; + self.first_char_x = x + 2; } pub fn addItem(self: *Self, item: ItemType) !void { @@ -69,10 +72,12 @@ pub fn CyclableLabel(comptime ItemType: type) type { } } - _ = termbox.tb_set_cursor(@intCast(self.x + 2), @intCast(self.y)); + _ = termbox.tb_set_cursor(@intCast(self.first_char_x), @intCast(self.y)); } - pub fn draw(self: Self) void { + pub fn draw(self: *Self) void { + if (self.list.items.len == 0) return; + const current_item = self.list.items[self.current]; const x = self.buffer.box_x + self.buffer.margin_box_h; const y = self.buffer.box_y + self.buffer.margin_box_v + 2;