From bca38856b1aa68a001aca8429ce4b78778247d0c Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Sun, 8 Feb 2026 14:53:36 +0100 Subject: [PATCH] Completely refactor widget placement code Signed-off-by: AnErrupTion --- src/main.zig | 133 +++++++++++++++++--------- src/tui/Position.zig | 32 +++++++ src/tui/TerminalBuffer.zig | 85 +---------------- src/tui/components/CenteredBox.zig | 147 +++++++++++++++++++++++++++++ src/tui/components/InfoLine.zig | 44 ++++++--- src/tui/components/Session.zig | 32 +++++-- src/tui/components/Text.zig | 71 ++++++++++---- src/tui/components/UserList.zig | 33 +++++-- src/tui/components/generic.zig | 109 +++++++++++++++------ 9 files changed, 477 insertions(+), 209 deletions(-) create mode 100644 src/tui/Position.zig create mode 100644 src/tui/components/CenteredBox.zig diff --git a/src/main.zig b/src/main.zig index 692dae6..e36165c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -30,6 +30,7 @@ const DisplayServer = enums.DisplayServer; const Environment = @import("Environment.zig"); const Entry = Environment.Entry; const Animation = @import("tui/Animation.zig"); +const CenteredBox = @import("tui/components/CenteredBox.zig"); const InfoLine = @import("tui/components/InfoLine.zig"); const Session = @import("tui/components/Session.zig"); const Text = @import("tui/components/Text.zig"); @@ -63,9 +64,11 @@ const UiState = struct { auth_fails: u64, update: bool, buffer: *TerminalBuffer, + labels_max_length: usize, animation_timed_out: bool, animation: *?Animation, can_draw_battery: bool, + box: *CenteredBox, info_line: *InfoLine, animate: bool, resolution_changed: bool, @@ -300,11 +303,7 @@ pub fn main() !void { .fg = config.fg, .bg = config.bg, .border_fg = config.border_fg, - .margin_box_h = config.margin_box_h, - .margin_box_v = config.margin_box_v, - .input_len = config.input_len, .full_color = config.full_color, - .labels_max_length = labels_max_length, .is_tty = true, }; var buffer = try TerminalBuffer.init(buffer_options, &log_file, random); @@ -321,7 +320,23 @@ pub fn main() !void { std.posix.sigaction(std.posix.SIG.TERM, &act, null); // Initialize components - var info_line = InfoLine.init(allocator, &buffer); + var box = CenteredBox.init( + &buffer, + config.margin_box_h, + config.margin_box_v, + (2 * config.margin_box_h) + config.input_len + 1 + labels_max_length, + 7 + (2 * config.margin_box_v), + !config.hide_borders, + config.blank_box, + config.box_title, + null, + ); + + var info_line = InfoLine.init( + allocator, + &buffer, + box.width - 2 * box.horizontal_margin, + ); defer info_line.deinit(); if (maybe_res == null) { @@ -365,10 +380,24 @@ pub fn main() !void { var login: UserList = undefined; - var session = Session.init(allocator, &buffer, &login); + var session = Session.init( + allocator, + &buffer, + &login, + box.width - 2 * box.horizontal_margin - labels_max_length - 1, + config.text_in_center, + ); defer session.deinit(); - login = try UserList.init(allocator, &buffer, usernames, &saved_users, &session); + login = try UserList.init( + allocator, + &buffer, + usernames, + &saved_users, + &session, + box.width - 2 * box.horizontal_margin - labels_max_length - 1, + config.text_in_center, + ); defer login.deinit(); addOtherEnvironment(&session, lang, .shell, null) catch |err| { @@ -430,7 +459,13 @@ pub fn main() !void { try log_file.err("sys", "no users found", .{}); } - var password = Text.init(allocator, &buffer, true, config.asterisk); + var password = Text.init( + allocator, + &buffer, + true, + config.asterisk, + box.width - 2 * box.horizontal_margin - labels_max_length - 1, + ); defer password.deinit(); var is_autologin = false; @@ -467,9 +502,11 @@ pub fn main() !void { .auth_fails = 0, .update = true, .buffer = &buffer, + .labels_max_length = labels_max_length, .animation_timed_out = false, .animation = &animation, .can_draw_battery = true, + .box = &box, .info_line = &info_line, .animate = config.animation != .none, .resolution_changed = false, @@ -512,25 +549,21 @@ pub fn main() !void { } } - // Place components on the screen - { - buffer.drawBoxCenter(!config.hide_borders, config.blank_box); + // Position components + state.box.position(TerminalBuffer.START_POSITION); + state.info_line.label.positionY(state.box.childrenPosition()); + state.session.label.positionY(state.info_line.label.childrenPosition().addY(1).addX(state.labels_max_length + 1)); + state.login.label.positionY(state.session.label.childrenPosition().addY(1)); + state.password.positionY(state.login.label.childrenPosition().addY(1)); - const coordinates = buffer.calculateComponentCoordinates(); - info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length, null); - session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length, config.text_in_center); - login.label.position(coordinates.x, coordinates.y + 4, coordinates.visible_length, config.text_in_center); - password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); - - switch (state.active_input) { - .info_line => info_line.label.handle(null, state.insert_mode), - .session => session.label.handle(null, state.insert_mode), - .login => login.label.handle(null, state.insert_mode), - .password => password.handle(null, state.insert_mode) catch |err| { - try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); - try log_file.err("tui", "failed to handle password input: {s}", .{@errorName(err)}); - }, - } + switch (state.active_input) { + .info_line => info_line.label.handle(null, state.insert_mode), + .session => session.label.handle(null, state.insert_mode), + .login => login.label.handle(null, state.insert_mode), + .password => password.handle(null, state.insert_mode) catch |err| { + try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); + try log_file.err("tui", "failed to handle password input: {s}", .{@errorName(err)}); + }, } // Initialize the animation, if any @@ -782,7 +815,7 @@ pub fn main() !void { if (!config.allow_empty_password and password.text.items.len == 0) { // Let's not log this message for security reasons try info_line.addMessage(lang.err_empty_password, config.error_bg, config.error_fg); - InfoLine.clearRendered(allocator, buffer) catch |err| { + info_line.clearRendered(allocator) catch |err| { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); try log_file.err("tui", "failed to clear info line: {s}", .{@errorName(err)}); }; @@ -792,7 +825,7 @@ pub fn main() !void { } try info_line.addMessage(lang.authenticating, config.bg, config.fg); - InfoLine.clearRendered(allocator, buffer) catch |err| { + info_line.clearRendered(allocator) catch |err| { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); try log_file.err("tui", "failed to clear info line: {s}", .{@errorName(err)}); }; @@ -1002,7 +1035,7 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool state.can_draw_battery = true; } - if (config.bigclock != .none and state.buffer.box_height + (bigclock.HEIGHT + 2) * 2 < state.buffer.height) { + if (config.bigclock != .none and state.box.height + (bigclock.HEIGHT + 2) * 2 < state.buffer.height) { var format_buf: [16:0]u8 = undefined; var clock_buf: [32:0]u8 = undefined; // We need the slice/c-string returned by `bufPrintZ`. @@ -1013,7 +1046,7 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool if (config.bigclock_12hr) "%P" else "", }); const xo = state.buffer.width / 2 - @min(state.buffer.width, (format.len * (bigclock.WIDTH + 1))) / 2; - const yo = (state.buffer.height - state.buffer.box_height) / 2 - bigclock.HEIGHT - 2; + const yo = (state.buffer.height - state.box.height) / 2 - bigclock.HEIGHT - 2; const clock_str = interop.timeAsString(&clock_buf, format); @@ -1024,14 +1057,14 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool } } - state.buffer.drawBoxCenter(!config.hide_borders, config.blank_box); + state.box.draw(); if (state.resolution_changed) { - const coordinates = state.buffer.calculateComponentCoordinates(); - state.info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length, null); - state.session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length, config.text_in_center); - state.login.label.position(coordinates.x, coordinates.y + 4, coordinates.visible_length, config.text_in_center); - state.password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); + state.box.position(TerminalBuffer.START_POSITION); + state.info_line.label.positionY(state.box.childrenPosition()); + state.session.label.positionY(state.info_line.label.childrenPosition().addY(1).addX(state.labels_max_length + 1)); + state.login.label.positionY(state.session.label.childrenPosition().addY(1)); + state.password.positionY(state.login.label.childrenPosition().addY(1)); state.resolution_changed = false; } @@ -1062,11 +1095,22 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool state.buffer.drawLabel(clock_str, state.buffer.width - @min(state.buffer.width, clock_str.len) - config.edge_margin, config.edge_margin); } - const label_x = state.buffer.box_x + state.buffer.margin_box_h; - const label_y = state.buffer.box_y + state.buffer.margin_box_v; - - state.buffer.drawLabel(lang.login, label_x, label_y + 4); - state.buffer.drawLabel(lang.password, label_x, label_y + 6); + const env = state.session.label.list.items[state.session.label.current]; + state.buffer.drawLabel( + env.environment.specifier, + state.box.childrenPosition().x, + state.session.label.component_pos.y, + ); + state.buffer.drawLabel( + lang.login, + state.box.childrenPosition().x, + state.login.label.component_pos.y, + ); + state.buffer.drawLabel( + lang.password, + state.box.childrenPosition().x, + state.password.component_pos.y, + ); state.info_line.label.draw(); @@ -1122,13 +1166,8 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool } } - if (config.box_title) |title| { - state.buffer.drawConfinedLabel(title, state.buffer.box_x, state.buffer.box_y - 1, state.buffer.box_width); - } - if (config.vi_mode) { - const label_txt = if (state.insert_mode) lang.insert else lang.normal; - state.buffer.drawLabel(label_txt, state.buffer.box_x, state.buffer.box_y + state.buffer.box_height); + state.box.bottom_title = if (state.insert_mode) lang.insert else lang.normal; } if (!config.hide_keyboard_locks and state.can_get_lock_state) draw_lock_state: { diff --git a/src/tui/Position.zig b/src/tui/Position.zig new file mode 100644 index 0000000..1a61f61 --- /dev/null +++ b/src/tui/Position.zig @@ -0,0 +1,32 @@ +const Position = @This(); + +x: usize, +y: usize, + +pub fn init(x: usize, y: usize) Position { + return .{ + .x = x, + .y = y, + }; +} + +pub fn add(self: Position, other: Position) Position { + return .{ + .x = self.x + other.x, + .y = self.y + other.y, + }; +} + +pub fn addX(self: Position, x: usize) Position { + return .{ + .x = self.x + x, + .y = self.y, + }; +} + +pub fn addY(self: Position, y: usize) Position { + return .{ + .x = self.x, + .y = self.y + y, + }; +} diff --git a/src/tui/TerminalBuffer.zig b/src/tui/TerminalBuffer.zig index 8f72e07..d02a858 100644 --- a/src/tui/TerminalBuffer.zig +++ b/src/tui/TerminalBuffer.zig @@ -7,6 +7,7 @@ const LogFile = ly_core.LogFile; pub const termbox = @import("termbox2"); const Cell = @import("Cell.zig"); +const Position = @import("Position.zig"); const TerminalBuffer = @This(); @@ -14,11 +15,7 @@ pub const InitOptions = struct { fg: u32, bg: u32, border_fg: u32, - margin_box_h: u8, - margin_box_v: u8, - input_len: u8, full_color: bool, - labels_max_length: usize, is_tty: bool, }; @@ -60,6 +57,8 @@ pub const Color = struct { pub const ECOL_WHITE = 8; }; +pub const START_POSITION = Position.init(0, 0); + random: Random, width: usize, height: usize, @@ -76,13 +75,6 @@ box_chars: struct { left: u32, right: u32, }, -labels_max_length: usize, -box_x: usize, -box_y: usize, -box_width: usize, -box_height: usize, -margin_box_v: u8, -margin_box_h: u8, blank_cell: Cell, full_color: bool, termios: ?std.posix.termios, @@ -134,13 +126,6 @@ pub fn init(options: InitOptions, log_file: *LogFile, random: Random) !TerminalB .left = '|', .right = '|', }, - .labels_max_length = options.labels_max_length, - .box_x = 0, - .box_y = 0, - .box_width = (2 * options.margin_box_h) + options.input_len + 1 + options.labels_max_length, - .box_height = 7 + (2 * options.margin_box_v), - .margin_box_v = options.margin_box_v, - .margin_box_h = options.margin_box_h, .blank_cell = Cell.init(' ', options.fg, options.bg), .full_color = options.full_color, // Needed to reclaim the TTY after giving up its control @@ -216,70 +201,6 @@ pub fn cascade(self: TerminalBuffer) bool { return changed; } -pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void { - if (self.width < 2 or self.height < 2) return; - const x1 = (self.width - @min(self.width - 2, self.box_width)) / 2; - const y1 = (self.height - @min(self.height - 2, self.box_height)) / 2; - const x2 = (self.width + @min(self.width, self.box_width)) / 2; - const y2 = (self.height + @min(self.height, self.box_height)) / 2; - - self.box_x = x1; - self.box_y = y1; - - if (show_borders) { - _ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg); - _ = termbox.tb_set_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg); - _ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg); - _ = termbox.tb_set_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg); - - var c1 = Cell.init(self.box_chars.top, self.border_fg, self.bg); - var c2 = Cell.init(self.box_chars.bottom, self.border_fg, self.bg); - - for (0..self.box_width) |i| { - c1.put(x1 + i, y1 - 1); - c2.put(x1 + i, y2); - } - - c1.ch = self.box_chars.left; - c2.ch = self.box_chars.right; - - for (0..self.box_height) |i| { - c1.put(x1 - 1, y1 + i); - c2.put(x2, y1 + i); - } - } - - if (blank_box) { - for (0..self.box_height) |y| { - for (0..self.box_width) |x| { - self.blank_cell.put(x1 + x, y1 + y); - } - } - } -} - -pub fn calculateComponentCoordinates(self: TerminalBuffer) struct { - start_x: usize, - x: usize, - y: usize, - full_visible_length: usize, - visible_length: usize, -} { - 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, - }; -} - pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize) void { drawColorLabel(text, x, y, self.fg, self.bg); } diff --git a/src/tui/components/CenteredBox.zig b/src/tui/components/CenteredBox.zig new file mode 100644 index 0000000..da0028b --- /dev/null +++ b/src/tui/components/CenteredBox.zig @@ -0,0 +1,147 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const Cell = @import("../Cell.zig"); +const TerminalBuffer = @import("../TerminalBuffer.zig"); +const Position = @import("../Position.zig"); +const termbox = TerminalBuffer.termbox; + +const CenteredBox = @This(); + +buffer: *TerminalBuffer, +horizontal_margin: usize, +vertical_margin: usize, +width: usize, +height: usize, +show_borders: bool, +blank_box: bool, +top_title: ?[]const u8, +bottom_title: ?[]const u8, +left_pos: Position, +right_pos: Position, +children_pos: Position, + +pub fn init( + buffer: *TerminalBuffer, + horizontal_margin: usize, + vertical_margin: usize, + width: usize, + height: usize, + show_borders: bool, + blank_box: bool, + top_title: ?[]const u8, + bottom_title: ?[]const u8, +) CenteredBox { + return .{ + .buffer = buffer, + .horizontal_margin = horizontal_margin, + .vertical_margin = vertical_margin, + .width = width, + .height = height, + .show_borders = show_borders, + .blank_box = blank_box, + .top_title = top_title, + .bottom_title = bottom_title, + .left_pos = TerminalBuffer.START_POSITION, + .right_pos = TerminalBuffer.START_POSITION, + .children_pos = TerminalBuffer.START_POSITION, + }; +} + +pub fn position(self: *CenteredBox, original_pos: Position) void { + if (self.buffer.width < 2 or self.buffer.height < 2) return; + + self.left_pos = Position.init( + (self.buffer.width - @min(self.buffer.width - 2, self.width)) / 2, + (self.buffer.height - @min(self.buffer.height - 2, self.height)) / 2, + ).add(original_pos); + + self.right_pos = Position.init( + (self.buffer.width + @min(self.buffer.width, self.width)) / 2, + (self.buffer.height + @min(self.buffer.height, self.height)) / 2, + ).add(original_pos); + + self.children_pos = Position.init( + self.left_pos.x + self.horizontal_margin, + self.left_pos.y + self.vertical_margin, + ).add(original_pos); +} + +pub fn childrenPosition(self: CenteredBox) Position { + return self.children_pos; +} + +pub fn draw(self: CenteredBox) void { + if (self.show_borders) { + _ = termbox.tb_set_cell( + @intCast(self.left_pos.x - 1), + @intCast(self.left_pos.y - 1), + self.buffer.box_chars.left_up, + self.buffer.border_fg, + self.buffer.bg, + ); + _ = termbox.tb_set_cell( + @intCast(self.right_pos.x), + @intCast(self.left_pos.y - 1), + self.buffer.box_chars.right_up, + self.buffer.border_fg, + self.buffer.bg, + ); + _ = termbox.tb_set_cell( + @intCast(self.left_pos.x - 1), + @intCast(self.right_pos.y), + self.buffer.box_chars.left_down, + self.buffer.border_fg, + self.buffer.bg, + ); + _ = termbox.tb_set_cell( + @intCast(self.right_pos.x), + @intCast(self.right_pos.y), + self.buffer.box_chars.right_down, + self.buffer.border_fg, + self.buffer.bg, + ); + + var c1 = Cell.init(self.buffer.box_chars.top, self.buffer.border_fg, self.buffer.bg); + var c2 = Cell.init(self.buffer.box_chars.bottom, self.buffer.border_fg, self.buffer.bg); + + for (0..self.width) |i| { + c1.put(self.left_pos.x + i, self.left_pos.y - 1); + c2.put(self.left_pos.x + i, self.right_pos.y); + } + + c1.ch = self.buffer.box_chars.left; + c2.ch = self.buffer.box_chars.right; + + for (0..self.height) |i| { + c1.put(self.left_pos.x - 1, self.left_pos.y + i); + c2.put(self.right_pos.x, self.left_pos.y + i); + } + } + + if (self.blank_box) { + for (0..self.height) |y| { + for (0..self.width) |x| { + self.buffer.blank_cell.put(self.left_pos.x + x, self.left_pos.y + y); + } + } + } + + if (self.top_title) |title| { + self.buffer.drawConfinedLabel( + title, + self.left_pos.x, + self.left_pos.y - 1, + self.width, + ); + } + + if (self.bottom_title) |title| { + self.buffer.drawConfinedLabel( + title, + self.left_pos.x, + self.left_pos.y + self.height, + self.width, + ); + } +} diff --git a/src/tui/components/InfoLine.zig b/src/tui/components/InfoLine.zig index 9e103cf..7e85c0b 100644 --- a/src/tui/components/InfoLine.zig +++ b/src/tui/components/InfoLine.zig @@ -17,9 +17,21 @@ const Message = struct { label: MessageLabel, -pub fn init(allocator: Allocator, buffer: *TerminalBuffer) InfoLine { +pub fn init( + allocator: Allocator, + buffer: *TerminalBuffer, + width: usize, +) InfoLine { return .{ - .label = MessageLabel.init(allocator, buffer, drawItem, null, null), + .label = MessageLabel.init( + allocator, + buffer, + drawItem, + null, + null, + width, + true, + ), }; } @@ -38,23 +50,31 @@ pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void { }); } -pub fn clearRendered(allocator: Allocator, buffer: TerminalBuffer) !void { +pub fn clearRendered(self: InfoLine, allocator: Allocator) !void { // Draw over the area - const y = buffer.box_y + buffer.margin_box_v; - const spaces = try allocator.alloc(u8, buffer.box_width); + const spaces = try allocator.alloc(u8, self.label.width - 2); defer allocator.free(spaces); @memset(spaces, ' '); - buffer.drawLabel(spaces, buffer.box_x, y); + self.label.buffer.drawLabel( + spaces, + self.label.component_pos.x + 2, + self.label.component_pos.y, + ); } -fn drawItem(label: *MessageLabel, message: Message, _: usize, _: usize) bool { - if (message.width == 0 or label.buffer.box_width <= message.width) return false; +fn drawItem(label: *MessageLabel, message: Message, x: usize, y: usize, width: usize) void { + if (message.width == 0 or width <= message.width) return; - const x = label.buffer.box_x + ((label.buffer.box_width - message.width) / 2); - label.first_char_x = x + message.width; + const x_offset = if (label.text_in_center) (width - message.width) / 2 else 0; - TerminalBuffer.drawColorLabel(message.text, x, label.y, message.fg, message.bg); - return true; + label.item_width = message.width + x_offset; + TerminalBuffer.drawColorLabel( + message.text, + x + x_offset, + y, + message.fg, + message.bg, + ); } diff --git a/src/tui/components/Session.zig b/src/tui/components/Session.zig index fd2aa3f..baa15ed 100644 --- a/src/tui/components/Session.zig +++ b/src/tui/components/Session.zig @@ -19,9 +19,23 @@ const Session = @This(); label: EnvironmentLabel, user_list: *UserList, -pub fn init(allocator: Allocator, buffer: *TerminalBuffer, user_list: *UserList) Session { +pub fn init( + allocator: Allocator, + buffer: *TerminalBuffer, + user_list: *UserList, + width: usize, + text_in_center: bool, +) Session { return .{ - .label = EnvironmentLabel.init(allocator, buffer, drawItem, sessionChanged, user_list), + .label = EnvironmentLabel.init( + allocator, + buffer, + drawItem, + sessionChanged, + user_list, + width, + text_in_center, + ), .user_list = user_list, }; } @@ -55,14 +69,12 @@ fn sessionChanged(env: Env, maybe_user_list: ?*UserList) void { } } -fn drawItem(label: *EnvironmentLabel, env: Env, x: usize, y: usize) bool { - const length = @min(env.environment.name.len, label.visible_length - 3); - if (length == 0) return false; +fn drawItem(label: *EnvironmentLabel, env: Env, x: usize, y: usize, width: usize) void { + const length = @min(env.environment.name.len, width - 3); + if (length == 0) return; - const nx = if (label.text_in_center) (label.x + (label.visible_length - env.environment.name.len) / 2) else (label.x + 2); - label.first_char_x = nx + env.environment.name.len; + const x_offset = if (label.text_in_center) (width - length) / 2 else 0; - label.buffer.drawLabel(env.environment.specifier, x, y); - label.buffer.drawLabel(env.environment.name, nx, label.y); - return true; + label.item_width = length + x_offset; + label.buffer.drawLabel(env.environment.name, x + x_offset, y); } diff --git a/src/tui/components/Text.zig b/src/tui/components/Text.zig index 4d3ba35..c31bcb4 100644 --- a/src/tui/components/Text.zig +++ b/src/tui/components/Text.zig @@ -2,6 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const TerminalBuffer = @import("../TerminalBuffer.zig"); +const Position = @import("../Position.zig"); const termbox = TerminalBuffer.termbox; const DynamicString = std.ArrayListUnmanaged(u8); @@ -14,13 +15,19 @@ text: DynamicString, end: usize, cursor: usize, visible_start: usize, -visible_length: usize, -x: usize, -y: usize, +width: usize, +component_pos: Position, +children_pos: Position, masked: bool, maybe_mask: ?u32, -pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_mask: ?u32) Text { +pub fn init( + allocator: Allocator, + buffer: *TerminalBuffer, + masked: bool, + maybe_mask: ?u32, + width: usize, +) Text { return .{ .allocator = allocator, .buffer = buffer, @@ -28,9 +35,9 @@ pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_m .end = 0, .cursor = 0, .visible_start = 0, - .visible_length = 0, - .x = 0, - .y = 0, + .width = width, + .component_pos = TerminalBuffer.START_POSITION, + .children_pos = TerminalBuffer.START_POSITION, .masked = masked, .maybe_mask = maybe_mask, }; @@ -40,10 +47,26 @@ pub fn deinit(self: *Text) void { self.text.deinit(self.allocator); } -pub fn position(self: *Text, x: usize, y: usize, visible_length: usize) void { - self.x = x; - self.y = y; - self.visible_length = visible_length; +pub fn positionX(self: *Text, original_pos: Position) void { + self.component_pos = original_pos; + self.children_pos = original_pos.addX(self.width); +} + +pub fn positionY(self: *Text, original_pos: Position) void { + self.component_pos = original_pos; + self.children_pos = original_pos.addY(1); +} + +pub fn positionXY(self: *Text, original_pos: Position) void { + self.component_pos = original_pos; + self.children_pos = Position.init( + self.width, + 1, + ).add(original_pos); +} + +pub fn childrenPosition(self: Text) Position { + return self.children_pos; } pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !void { @@ -79,36 +102,44 @@ pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) ! } if (self.masked and self.maybe_mask == null) { - _ = termbox.tb_set_cursor(@intCast(self.x), @intCast(self.y)); + _ = termbox.tb_set_cursor(@intCast(self.component_pos.x), @intCast(self.component_pos.y)); return; } - _ = termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y)); + _ = termbox.tb_set_cursor( + @intCast(self.component_pos.x + (self.cursor - self.visible_start)), + @intCast(self.component_pos.y), + ); } pub fn draw(self: Text) void { if (self.masked) { if (self.maybe_mask) |mask| { - const length = @min(self.text.items.len, self.visible_length - 1); + const length = @min(self.text.items.len, self.width - 1); if (length == 0) return; - self.buffer.drawCharMultiple(mask, self.x, self.y, length); + self.buffer.drawCharMultiple( + mask, + self.component_pos.x, + self.component_pos.y, + length, + ); } return; } - const length = @min(self.text.items.len, self.visible_length); + const length = @min(self.text.items.len, self.width); if (length == 0) return; const visible_slice = vs: { - if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) { - break :vs self.text.items[self.visible_start..(self.visible_length + self.visible_start)]; + if (self.text.items.len > self.width and self.cursor < self.text.items.len) { + break :vs self.text.items[self.visible_start..(self.width + self.visible_start)]; } else { break :vs self.text.items[self.visible_start..]; } }; - self.buffer.drawLabel(visible_slice, self.x, self.y); + self.buffer.drawLabel(visible_slice, self.component_pos.x, self.component_pos.y); } pub fn clear(self: *Text) void { @@ -127,7 +158,7 @@ fn goLeft(self: *Text) void { fn goRight(self: *Text) void { if (self.cursor >= self.end) return; - if (self.cursor - self.visible_start == self.visible_length - 1) self.visible_start += 1; + if (self.cursor - self.visible_start == self.width - 1) self.visible_start += 1; self.cursor += 1; } diff --git a/src/tui/components/UserList.zig b/src/tui/components/UserList.zig index b8d712d..9433a0e 100644 --- a/src/tui/components/UserList.zig +++ b/src/tui/components/UserList.zig @@ -19,9 +19,25 @@ const UserList = @This(); label: UserLabel, -pub fn init(allocator: Allocator, buffer: *TerminalBuffer, usernames: StringList, saved_users: *SavedUsers, session: *Session) !UserList { +pub fn init( + allocator: Allocator, + buffer: *TerminalBuffer, + usernames: StringList, + saved_users: *SavedUsers, + session: *Session, + width: usize, + text_in_center: bool, +) !UserList { var userList = UserList{ - .label = UserLabel.init(allocator, buffer, drawItem, usernameChanged, session), + .label = UserLabel.init( + allocator, + buffer, + drawItem, + usernameChanged, + session, + width, + text_in_center, + ), }; for (usernames.items) |username| { @@ -75,13 +91,12 @@ fn usernameChanged(user: User, maybe_session: ?*Session) void { } } -fn drawItem(label: *UserLabel, user: User, _: usize, _: usize) bool { - const length = @min(user.name.len, label.visible_length - 3); - if (length == 0) return false; +fn drawItem(label: *UserLabel, user: User, x: usize, y: usize, width: usize) void { + const length = @min(user.name.len, width - 3); + if (length == 0) return; - const x = if (label.text_in_center) (label.x + (label.visible_length - user.name.len) / 2) else (label.x + 2); - label.first_char_x = x + user.name.len; + const x_offset = if (label.text_in_center) (width - length) / 2 else 0; - label.buffer.drawLabel(user.name, x, label.y); - return true; + label.item_width = length + x_offset; + label.buffer.drawLabel(user.name, x + x_offset, y); } diff --git a/src/tui/components/generic.zig b/src/tui/components/generic.zig index 333ded1..479b14f 100644 --- a/src/tui/components/generic.zig +++ b/src/tui/components/generic.zig @@ -1,12 +1,13 @@ const std = @import("std"); const TerminalBuffer = @import("../TerminalBuffer.zig"); +const Position = @import("../Position.zig"); pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) type { return struct { const Allocator = std.mem.Allocator; const ItemList = std.ArrayListUnmanaged(ItemType); - const DrawItemFn = *const fn (*Self, ItemType, usize, usize) bool; + const DrawItemFn = *const fn (*Self, ItemType, usize, usize, usize) void; const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void; const termbox = TerminalBuffer.termbox; @@ -17,26 +18,34 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ buffer: *TerminalBuffer, list: ItemList, current: usize, - visible_length: usize, - x: usize, - y: usize, - first_char_x: usize, + width: usize, + component_pos: Position, + children_pos: Position, text_in_center: bool, + item_width: usize, draw_item_fn: DrawItemFn, change_item_fn: ?ChangeItemFn, change_item_arg: ?ChangeItemType, - pub fn init(allocator: Allocator, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn, change_item_fn: ?ChangeItemFn, change_item_arg: ?ChangeItemType) Self { + pub fn init( + allocator: Allocator, + buffer: *TerminalBuffer, + draw_item_fn: DrawItemFn, + change_item_fn: ?ChangeItemFn, + change_item_arg: ?ChangeItemType, + width: usize, + text_in_center: bool, + ) Self { return .{ .allocator = allocator, .buffer = buffer, .list = .empty, .current = 0, - .visible_length = 0, - .x = 0, - .y = 0, - .first_char_x = 0, - .text_in_center = false, + .width = width, + .component_pos = TerminalBuffer.START_POSITION, + .children_pos = TerminalBuffer.START_POSITION, + .text_in_center = text_in_center, + .item_width = 0, .draw_item_fn = draw_item_fn, .change_item_fn = change_item_fn, .change_item_arg = change_item_arg, @@ -47,14 +56,29 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ self.list.deinit(self.allocator); } - pub fn position(self: *Self, x: usize, y: usize, visible_length: usize, text_in_center: ?bool) void { - self.x = x; - self.y = y; - self.visible_length = visible_length; - self.first_char_x = x + 2; - if (text_in_center) |value| { - self.text_in_center = value; - } + pub fn positionX(self: *Self, original_pos: Position) void { + self.component_pos = original_pos; + self.item_width = self.component_pos.x + 2; + self.children_pos = original_pos.addX(self.width); + } + + pub fn positionY(self: *Self, original_pos: Position) void { + self.component_pos = original_pos; + self.item_width = self.component_pos.x + 2; + self.children_pos = original_pos.addY(1); + } + + pub fn positionXY(self: *Self, original_pos: Position) void { + self.component_pos = original_pos; + self.item_width = self.component_pos.x + 2; + self.children_pos = Position.init( + self.width, + 1, + ).add(original_pos); + } + + pub fn childrenPosition(self: Self) Position { + return self.children_pos; } pub fn addItem(self: *Self, item: ItemType) !void { @@ -81,28 +105,51 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ } } - _ = termbox.tb_set_cursor(@intCast(self.first_char_x), @intCast(self.y)); + _ = termbox.tb_set_cursor( + @intCast(self.component_pos.x + self.item_width + 2), + @intCast(self.component_pos.y), + ); } pub fn draw(self: *Self) void { if (self.list.items.len == 0) return; + _ = termbox.tb_set_cell( + @intCast(self.component_pos.x), + @intCast(self.component_pos.y), + '<', + self.buffer.fg, + self.buffer.bg, + ); + _ = termbox.tb_set_cell( + @intCast(self.component_pos.x + self.width - 1), + @intCast(self.component_pos.y), + '>', + self.buffer.fg, + self.buffer.bg, + ); + 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; + const x = self.component_pos.x + 2; + const y = self.component_pos.y; + const width = self.width - 2; - const continue_drawing = @call(.auto, self.draw_item_fn, .{ self, current_item, x, y }); - if (!continue_drawing) return; - - _ = termbox.tb_set_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg); - _ = termbox.tb_set_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg); + @call( + .auto, + self.draw_item_fn, + .{ self, current_item, x, y, width }, + ); } fn goLeft(self: *Self) void { self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1; if (self.change_item_fn) |change_item_fn| { - @call(.auto, change_item_fn, .{ self.list.items[self.current], self.change_item_arg }); + @call( + .auto, + change_item_fn, + .{ self.list.items[self.current], self.change_item_arg }, + ); } } @@ -110,7 +157,11 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1; if (self.change_item_fn) |change_item_fn| { - @call(.auto, change_item_fn, .{ self.list.items[self.current], self.change_item_arg }); + @call( + .auto, + change_item_fn, + .{ self.list.items[self.current], self.change_item_arg }, + ); } } };