diff --git a/src/animations/Cascade.zig b/src/animations/Cascade.zig new file mode 100644 index 0000000..06cfdfe --- /dev/null +++ b/src/animations/Cascade.zig @@ -0,0 +1,81 @@ +const std = @import("std"); +const math = std.math; + +const Cell = @import("../tui/Cell.zig"); +const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); +const Widget = @import("../tui/Widget.zig"); + +const Cascade = @This(); + +buffer: *TerminalBuffer, +current_auth_fails: *usize, +max_auth_fails: usize, + +pub fn init( + buffer: *TerminalBuffer, + current_auth_fails: *usize, + max_auth_fails: usize, +) Cascade { + return .{ + .buffer = buffer, + .current_auth_fails = current_auth_fails, + .max_auth_fails = max_auth_fails, + }; +} + +pub fn widget(self: *Cascade) Widget { + return Widget.init( + self, + null, + null, + draw, + null, + null, + ); +} + +fn draw(self: *Cascade) void { + while (self.current_auth_fails.* >= self.max_auth_fails) { + std.Thread.sleep(std.time.ns_per_ms * 10); + + var changed = false; + var y = self.buffer.height - 2; + + while (y > 0) : (y -= 1) { + for (0..self.buffer.width) |x| { + const cell = TerminalBuffer.getCell(x, y - 1); + const cell_under = TerminalBuffer.getCell(x, y); + + // This shouldn't happen under normal circumstances, but because + // this is a *secret* animation, there's no need to care that much + if (cell == null or cell_under == null) continue; + + const char: u8 = @truncate(cell.?.ch); + if (std.ascii.isWhitespace(char)) continue; + + const char_under: u8 = @truncate(cell_under.?.ch); + if (!std.ascii.isWhitespace(char_under)) continue; + + changed = true; + + if ((self.buffer.random.int(u16) % 10) > 7) continue; + + cell.?.put(x, y); + + var space = Cell.init( + ' ', + cell_under.?.fg, + cell_under.?.bg, + ); + space.put(x, y - 1); + } + } + + if (!changed) { + std.Thread.sleep(std.time.ns_per_s * 7); + self.current_auth_fails.* = 0; + } + + TerminalBuffer.presentBuffer(); + } +} diff --git a/src/animations/ColorMix.zig b/src/animations/ColorMix.zig index 21edc09..206986e 100644 --- a/src/animations/ColorMix.zig +++ b/src/animations/ColorMix.zig @@ -17,14 +17,22 @@ fn length(vec: Vec2) f32 { } terminal_buffer: *TerminalBuffer, +timeout: *bool, frames: u64, pattern_cos_mod: f32, pattern_sin_mod: f32, palette: [palette_len]Cell, -pub fn init(terminal_buffer: *TerminalBuffer, col1: u32, col2: u32, col3: u32) ColorMix { +pub fn init( + terminal_buffer: *TerminalBuffer, + col1: u32, + col2: u32, + col3: u32, + timeout: *bool, +) ColorMix { return .{ .terminal_buffer = terminal_buffer, + .timeout = timeout, .frames = 0, .pattern_cos_mod = terminal_buffer.random.float(f32) * math.pi * 2.0, .pattern_sin_mod = terminal_buffer.random.float(f32) * math.pi * 2.0, @@ -57,6 +65,8 @@ pub fn widget(self: *ColorMix) Widget { } fn draw(self: *ColorMix) void { + if (self.timeout.*) return; + self.frames +%= 1; const time: f32 = @as(f32, @floatFromInt(self.frames)) * time_scale; diff --git a/src/animations/Doom.zig b/src/animations/Doom.zig index e0a09b1..a657dbc 100644 --- a/src/animations/Doom.zig +++ b/src/animations/Doom.zig @@ -13,12 +13,22 @@ pub const SPREAD_MAX = 4; allocator: Allocator, terminal_buffer: *TerminalBuffer, +timeout: *bool, buffer: []u8, height: u8, spread: u8, fire: [STEPS + 1]Cell, -pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u32, middle_color: u32, bottom_color: u32, fire_height: u8, fire_spread: u8) !Doom { +pub fn init( + allocator: Allocator, + terminal_buffer: *TerminalBuffer, + top_color: u32, + middle_color: u32, + bottom_color: u32, + fire_height: u8, + fire_spread: u8, + timeout: *bool, +) !Doom { const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height); initBuffer(buffer, terminal_buffer.width); @@ -42,6 +52,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u return .{ .allocator = allocator, .terminal_buffer = terminal_buffer, + .timeout = timeout, .buffer = buffer, .height = @min(HEIGHT_MAX, fire_height), .spread = @min(SPREAD_MAX, fire_spread), @@ -71,6 +82,8 @@ fn realloc(self: *Doom) !void { } fn draw(self: *Doom) void { + if (self.timeout.*) return; + for (0..self.terminal_buffer.width) |x| { // We start from 1 so that we always have the topmost line when spreading fire for (1..self.terminal_buffer.height) |y| { diff --git a/src/animations/DurFile.zig b/src/animations/DurFile.zig index f3f2eb2..9ed9207 100644 --- a/src/animations/DurFile.zig +++ b/src/animations/DurFile.zig @@ -307,6 +307,7 @@ frames: u64, frame_size: UVec2, start_pos: IVec2, full_color: bool, +timeout: *bool, frame_time: u32, time_previous: i64, is_color_format_16: bool, @@ -357,7 +358,17 @@ fn calc_frame_size(terminal_buffer: *TerminalBuffer, dur_movie: *DurFormat) UVec return .{ frame_width, frame_height }; } -pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_file: *LogFile, file_path: []const u8, offset_alignment: DurOffsetAlignment, x_offset: i32, y_offset: i32, full_color: bool) !DurFile { +pub fn init( + allocator: Allocator, + terminal_buffer: *TerminalBuffer, + log_file: *LogFile, + file_path: []const u8, + offset_alignment: DurOffsetAlignment, + x_offset: i32, + y_offset: i32, + full_color: bool, + timeout: *bool, +) !DurFile { var dur_movie: DurFormat = .init(allocator); dur_movie.create_from_file(allocator, file_path) catch |err| switch (err) { @@ -395,6 +406,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_file: *L .frame_size = frame_size, .start_pos = start_pos, .full_color = full_color, + .timeout = timeout, .dur_movie = dur_movie, .frame_time = frame_time, .is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"), @@ -425,6 +437,8 @@ fn realloc(self: *DurFile) !void { } fn draw(self: *DurFile) void { + if (self.timeout.*) return; + const current_frame = self.dur_movie.frames.items[self.frames]; const buf_width: u32 = @intCast(self.terminal_buffer.width); diff --git a/src/animations/GameOfLife.zig b/src/animations/GameOfLife.zig index ac87226..d90590c 100644 --- a/src/animations/GameOfLife.zig +++ b/src/animations/GameOfLife.zig @@ -26,11 +26,20 @@ fg_color: u32, entropy_interval: usize, frame_delay: usize, initial_density: f32, +timeout: *bool, dead_cell: Cell, width: usize, height: usize, -pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u32, entropy_interval: usize, frame_delay: usize, initial_density: f32) !GameOfLife { +pub fn init( + allocator: Allocator, + terminal_buffer: *TerminalBuffer, + fg_color: u32, + entropy_interval: usize, + frame_delay: usize, + initial_density: f32, + timeout: *bool, +) !GameOfLife { const width = terminal_buffer.width; const height = terminal_buffer.height; const grid_size = width * height; @@ -49,6 +58,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u3 .entropy_interval = entropy_interval, .frame_delay = frame_delay, .initial_density = initial_density, + .timeout = timeout, .dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg }, .width = width, .height = height, @@ -94,6 +104,8 @@ fn realloc(self: *GameOfLife) !void { } fn draw(self: *GameOfLife) void { + if (self.timeout.*) return; + // Update game state at controlled frame rate self.frame_counter += 1; if (self.frame_counter >= self.frame_delay) { diff --git a/src/animations/Matrix.zig b/src/animations/Matrix.zig index a3c4138..25ff6d1 100644 --- a/src/animations/Matrix.zig +++ b/src/animations/Matrix.zig @@ -34,9 +34,18 @@ fg: u32, head_col: u32, min_codepoint: u16, max_codepoint: u16, +timeout: *bool, default_cell: Cell, -pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, head_col: u32, min_codepoint: u16, max_codepoint: u16) !Matrix { +pub fn init( + allocator: Allocator, + terminal_buffer: *TerminalBuffer, + fg: u32, + head_col: u32, + min_codepoint: u16, + max_codepoint: u16, + timeout: *bool, +) !Matrix { const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1)); const lines = try allocator.alloc(Line, terminal_buffer.width); @@ -53,6 +62,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, hea .head_col = head_col, .min_codepoint = min_codepoint, .max_codepoint = max_codepoint - min_codepoint, + .timeout = timeout, .default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg }, }; } @@ -84,6 +94,8 @@ fn realloc(self: *Matrix) !void { } fn draw(self: *Matrix) void { + if (self.timeout.*) return; + const buf_height = self.terminal_buffer.height; const buf_width = self.terminal_buffer.width; self.count += 1; diff --git a/src/main.zig b/src/main.zig index c5294d8..9e9113f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -15,6 +15,7 @@ const LogFile = ly_core.LogFile; const SharedError = ly_core.SharedError; const IniParser = ly_core.IniParser; +const Cascade = @import("animations/Cascade.zig"); const ColorMix = @import("animations/ColorMix.zig"); const Doom = @import("animations/Doom.zig"); const DurFile = @import("animations/DurFile.zig"); @@ -74,7 +75,6 @@ const UiState = struct { buffer: TerminalBuffer, labels_max_length: usize, animation_timed_out: bool, - animation: ?Widget, shutdown_label: Label, restart_label: Label, sleep_label: Label, @@ -517,6 +517,7 @@ pub fn main() !void { state.buffer.border_fg, state.buffer.fg, state.buffer.bg, + &updateBox, ); state.info_line = InfoLine.init( @@ -867,10 +868,9 @@ pub fn main() !void { } // Initialize the animation, if any + var animation: ?Widget = null; switch (state.config.animation) { - .none => { - state.animation = null; - }, + .none => {}, .doom => { var doom = try Doom.init( state.allocator, @@ -880,8 +880,9 @@ pub fn main() !void { state.config.doom_bottom_color, state.config.doom_fire_height, state.config.doom_fire_spread, + &state.animation_timed_out, ); - state.animation = doom.widget(); + animation = doom.widget(); }, .matrix => { var matrix = try Matrix.init( @@ -891,8 +892,9 @@ pub fn main() !void { state.config.cmatrix_head_col, state.config.cmatrix_min_codepoint, state.config.cmatrix_max_codepoint, + &state.animation_timed_out, ); - state.animation = matrix.widget(); + animation = matrix.widget(); }, .colormix => { var color_mix = ColorMix.init( @@ -900,8 +902,9 @@ pub fn main() !void { state.config.colormix_col1, state.config.colormix_col2, state.config.colormix_col3, + &state.animation_timed_out, ); - state.animation = color_mix.widget(); + animation = color_mix.widget(); }, .gameoflife => { var game_of_life = try GameOfLife.init( @@ -911,8 +914,9 @@ pub fn main() !void { state.config.gameoflife_entropy_interval, state.config.gameoflife_frame_delay, state.config.gameoflife_initial_density, + &state.animation_timed_out, ); - state.animation = game_of_life.widget(); + animation = game_of_life.widget(); }, .dur_file => { var dur = try DurFile.init( @@ -924,11 +928,18 @@ pub fn main() !void { state.config.dur_x_offset, state.config.dur_y_offset, state.config.full_color, + &state.animation_timed_out, ); - state.animation = dur.widget(); + animation = dur.widget(); }, } - defer if (state.animation) |*a| a.deinit(); + defer if (animation) |*a| a.deinit(); + + var cascade = Cascade.init( + &state.buffer, + &state.auth_fails, + state.config.auth_fails, + ); state.auth_fails = 0; state.run = true; @@ -966,9 +977,14 @@ pub fn main() !void { } } + // TODO: Layer system where we can put widgets in specific layers (to + // allow certain widgets to be below or above others, like animations) var widgets: std.ArrayList(Widget) = .empty; defer widgets.deinit(state.allocator); + if (animation) |a| { + try widgets.append(state.allocator, a); + } if (!state.config.hide_key_hints) { try widgets.append(state.allocator, state.shutdown_label.widget()); try widgets.append(state.allocator, state.restart_label.widget()); @@ -1006,9 +1022,12 @@ pub fn main() !void { if (!state.config.hide_version_string) { try widgets.append(state.allocator, state.version_label.widget()); } + if (state.config.auth_fails > 0) { + try widgets.append(state.allocator, cascade.widget()); + } // Position components and place cursor accordingly - try updateComponents(&state, widgets); + for (widgets.items) |*widget| try widget.update(&state); positionComponents(&state); switch (state.active_input) { @@ -1085,9 +1104,11 @@ pub fn main() !void { ); } + if (state.is_autologin) _ = try authenticate(&state); + while (state.run) { if (state.update) { - try updateComponents(&state, widgets); + for (widgets.items) |*widget| try widget.update(&state); switch (state.active_input) { .info_line => state.info_line.label.handle(null, state.insert_mode), @@ -1099,7 +1120,11 @@ pub fn main() !void { }, } - if (!try drawUi(&state, widgets)) continue; + try TerminalBuffer.clearScreen(false); + + for (widgets.items) |*widget| widget.draw(); + + TerminalBuffer.presentBuffer(); } var timeout: i32 = -1; @@ -1113,7 +1138,7 @@ pub fn main() !void { if (state.config.animation_timeout_sec > 0 and time.seconds - animation_time_start.seconds > state.config.animation_timeout_sec) { state.animation_timed_out = true; - if (state.animation) |*a| a.deinit(); + if (animation) |*a| a.deinit(); } } else if (state.config.bigclock != .none and state.config.clock == null) { const time = try interop.getTimeOfDay(); @@ -1155,25 +1180,11 @@ pub fn main() !void { } } - // Skip event polling if autologin is set, use simulated Enter key press instead - if (state.is_autologin) { - event = .{ - .type = termbox.TB_EVENT_KEY, - .key = termbox.TB_KEY_ENTER, - .ch = 0, - .w = 0, - .h = 0, - .x = 0, - .y = 0, - .mod = 0, - }; - } else { - const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout); + const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout); - state.update = timeout != -1; + state.update = timeout != -1; - if (event_error < 0) continue; - } + if (event_error < 0) continue; // Input of some kind was detected, so reset the inactivity timer inactivity_time_start = try interop.getTimeOfDay(); @@ -1184,7 +1195,7 @@ pub fn main() !void { try state.log_file.info("tui", "screen resolution updated to {d}x{d}", .{ state.buffer.width, state.buffer.height }); - if (state.animation) |*a| a.realloc() catch |err| { + if (animation) |*a| a.realloc() catch |err| { try state.info_line.addMessage( state.lang.err_alloc, state.config.error_bg, @@ -1197,6 +1208,21 @@ pub fn main() !void { ); }; + for (widgets.items) |*widget| { + widget.realloc() catch |err| { + try state.info_line.addMessage( + state.lang.err_alloc, + state.config.error_bg, + state.config.error_fg, + ); + try state.log_file.err( + "tui", + "failed to reallocate widget: {s}", + .{@errorName(err)}, + ); + }; + } + positionComponents(&state); state.update = true; @@ -1617,43 +1643,6 @@ fn increaseBrightnessCmd(ptr: *anyopaque) !bool { return false; } -fn updateComponents(state: *UiState, widgets: std.ArrayList(Widget)) !void { - for (widgets.items) |*widget| { - try widget.update(state); - } -} - -fn drawUi(state: *UiState, widgets: std.ArrayList(Widget)) !bool { - // If the user entered a wrong password 10 times in a row, play a cascade animation, else update normally - if (state.config.auth_fails > 0 and state.auth_fails >= state.config.auth_fails) { - std.Thread.sleep(std.time.ns_per_ms * 10); - state.update = state.buffer.cascade(); - - if (!state.update) { - std.Thread.sleep(std.time.ns_per_s * 7); - state.auth_fails = 0; - } - - TerminalBuffer.presentBuffer(); - return false; - } - - try TerminalBuffer.clearScreen(false); - - if (!state.animation_timed_out) if (state.animation) |*a| a.draw(); - - for (widgets.items) |*widget| { - widget.draw(); - } - - if (state.config.vi_mode) { - state.box.bottom_title = if (state.insert_mode) state.lang.insert else state.lang.normal; - } - - TerminalBuffer.presentBuffer(); - return true; -} - fn updateNumlock(self: *Label, ptr: *anyopaque) !void { var state: *UiState = @ptrCast(@alignCast(ptr)); @@ -1765,6 +1754,14 @@ fn updateBigClock(self: *BigLabel, ptr: *anyopaque) !void { self.setText(clock_str); } +fn updateBox(self: *CenteredBox, ptr: *anyopaque) !void { + const state: *UiState = @ptrCast(@alignCast(ptr)); + + if (state.config.vi_mode) { + self.bottom_title = if (state.insert_mode) state.lang.insert else state.lang.normal; + } +} + fn updateSessionSpecifier(self: *Label, ptr: *anyopaque) !void { const state: *UiState = @ptrCast(@alignCast(ptr)); diff --git a/src/tui/Cell.zig b/src/tui/Cell.zig index 2a6fc72..35d0cf0 100644 --- a/src/tui/Cell.zig +++ b/src/tui/Cell.zig @@ -17,5 +17,5 @@ pub fn init(ch: u32, fg: u32, bg: u32) Cell { pub fn put(self: Cell, x: usize, y: usize) void { if (self.ch == 0) return; - TerminalBuffer.setCell(x, y, self.ch, self.fg, self.bg); + TerminalBuffer.setCell(x, y, self); } diff --git a/src/tui/TerminalBuffer.zig b/src/tui/TerminalBuffer.zig index 8250acb..56b96da 100644 --- a/src/tui/TerminalBuffer.zig +++ b/src/tui/TerminalBuffer.zig @@ -172,19 +172,29 @@ pub fn presentBuffer() void { _ = termbox.tb_present(); } -pub fn setCell( - x: usize, - y: usize, - ch: u32, - fg: u32, - bg: u32, -) void { +pub fn getCell(x: usize, y: usize) ?Cell { + var maybe_cell: ?*termbox.tb_cell = undefined; + _ = termbox.tb_get_cell( + @intCast(x), + @intCast(y), + 1, + &maybe_cell, + ); + + if (maybe_cell) |cell| { + return Cell.init(cell.ch, cell.fg, cell.bg); + } + + return null; +} + +pub fn setCell(x: usize, y: usize, cell: Cell) void { _ = termbox.tb_set_cell( @intCast(x), @intCast(y), - ch, - fg, - bg, + cell.ch, + cell.fg, + cell.bg, ); } @@ -201,40 +211,6 @@ pub fn reclaim(self: TerminalBuffer) !void { } } -pub fn cascade(self: TerminalBuffer) bool { - var changed = false; - var y = self.height - 2; - - while (y > 0) : (y -= 1) { - for (0..self.width) |x| { - var cell: ?*termbox.tb_cell = undefined; - var cell_under: ?*termbox.tb_cell = undefined; - - _ = termbox.tb_get_cell(@intCast(x), @intCast(y - 1), 1, &cell); - _ = termbox.tb_get_cell(@intCast(x), @intCast(y), 1, &cell_under); - - // This shouldn't happen under normal circumstances, but because - // this is a *secret* animation, there's no need to care that much - if (cell == null or cell_under == null) continue; - - const char: u8 = @truncate(cell.?.ch); - if (std.ascii.isWhitespace(char)) continue; - - const char_under: u8 = @truncate(cell_under.?.ch); - if (!std.ascii.isWhitespace(char_under)) continue; - - changed = true; - - if ((self.random.int(u16) % 10) > 7) continue; - - _ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.?.ch, cell.?.fg, cell.?.bg); - _ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', cell_under.?.fg, cell_under.?.bg); - } - } - - return changed; -} - pub fn registerKeybind(self: *TerminalBuffer, keybind: []const u8, callback: KeybindCallbackFn) !void { var key = std.mem.zeroes(keyboard.Key); var iterator = std.mem.splitScalar(u8, keybind, '+'); diff --git a/src/tui/components/BigLabel.zig b/src/tui/components/BigLabel.zig index 0e6bf51..625647f 100644 --- a/src/tui/components/BigLabel.zig +++ b/src/tui/components/BigLabel.zig @@ -140,7 +140,7 @@ pub fn childrenPosition(self: BigLabel) Position { return self.children_pos; } -pub fn draw(self: *BigLabel) void { +fn draw(self: *BigLabel) void { for (self.text, 0..) |c, i| { const clock_cell = clockCell( c, @@ -159,7 +159,7 @@ pub fn draw(self: *BigLabel) void { } } -pub fn update(self: *BigLabel, context: *anyopaque) !void { +fn update(self: *BigLabel, context: *anyopaque) !void { if (self.update_fn) |update_fn| { return @call( .auto, diff --git a/src/tui/components/CenteredBox.zig b/src/tui/components/CenteredBox.zig index 38c566a..fd46057 100644 --- a/src/tui/components/CenteredBox.zig +++ b/src/tui/components/CenteredBox.zig @@ -19,6 +19,7 @@ bottom_title: ?[]const u8, border_fg: u32, title_fg: u32, bg: u32, +update_fn: ?*const fn (*CenteredBox, *anyopaque) anyerror!void, left_pos: Position, right_pos: Position, children_pos: Position, @@ -36,6 +37,7 @@ pub fn init( border_fg: u32, title_fg: u32, bg: u32, + update_fn: ?*const fn (*CenteredBox, *anyopaque) anyerror!void, ) CenteredBox { return .{ .buffer = buffer, @@ -50,6 +52,7 @@ pub fn init( .border_fg = border_fg, .title_fg = title_fg, .bg = bg, + .update_fn = update_fn, .left_pos = TerminalBuffer.START_POSITION, .right_pos = TerminalBuffer.START_POSITION, .children_pos = TerminalBuffer.START_POSITION, @@ -90,7 +93,7 @@ pub fn childrenPosition(self: CenteredBox) Position { return self.children_pos; } -pub fn draw(self: *CenteredBox) void { +fn draw(self: *CenteredBox) void { if (self.show_borders) { var left_up = Cell.init( self.buffer.box_chars.left_up, @@ -172,3 +175,13 @@ pub fn draw(self: *CenteredBox) void { ); } } + +fn update(self: *CenteredBox, ctx: *anyopaque) !void { + if (self.update_fn) |update_fn| { + return @call( + .auto, + update_fn, + .{ self, ctx }, + ); + } +} diff --git a/src/tui/components/InfoLine.zig b/src/tui/components/InfoLine.zig index d059c81..e3d9904 100644 --- a/src/tui/components/InfoLine.zig +++ b/src/tui/components/InfoLine.zig @@ -56,14 +56,6 @@ pub fn widget(self: *InfoLine) Widget { ); } -pub fn draw(self: *InfoLine) void { - self.label.draw(); -} - -pub fn handle(self: *InfoLine, maybe_key: ?keyboard.Key, insert_mode: bool) !void { - self.label.handle(maybe_key, insert_mode); -} - pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void { if (text.len == 0) return; @@ -91,6 +83,14 @@ pub fn clearRendered(self: InfoLine, allocator: Allocator) !void { ); } +fn draw(self: *InfoLine) void { + self.label.draw(); +} + +fn handle(self: *InfoLine, maybe_key: ?keyboard.Key, insert_mode: bool) !void { + self.label.handle(maybe_key, insert_mode); +} + fn drawItem(label: *MessageLabel, message: Message, x: usize, y: usize, width: usize) void { if (message.width == 0) return; diff --git a/src/tui/components/Label.zig b/src/tui/components/Label.zig index fcc7aa1..fb95a98 100644 --- a/src/tui/components/Label.zig +++ b/src/tui/components/Label.zig @@ -98,7 +98,7 @@ pub fn childrenPosition(self: Label) Position { return self.children_pos; } -pub fn draw(self: *Label) void { +fn draw(self: *Label) void { if (self.max_width) |width| { TerminalBuffer.drawConfinedText( self.text, @@ -120,7 +120,7 @@ pub fn draw(self: *Label) void { ); } -pub fn update(self: *Label, ctx: *anyopaque) !void { +fn update(self: *Label, ctx: *anyopaque) !void { if (self.update_fn) |update_fn| { return @call( .auto, diff --git a/src/tui/components/Session.zig b/src/tui/components/Session.zig index ced126c..00782b0 100644 --- a/src/tui/components/Session.zig +++ b/src/tui/components/Session.zig @@ -66,14 +66,6 @@ pub fn widget(self: *Session) Widget { ); } -pub fn draw(self: *Session) void { - self.label.draw(); -} - -pub fn handle(self: *Session, maybe_key: ?keyboard.Key, insert_mode: bool) !void { - self.label.handle(maybe_key, insert_mode); -} - pub fn addEnvironment(self: *Session, environment: Environment) !void { const env = Env{ .environment = environment, .index = self.label.list.items.len }; @@ -81,6 +73,14 @@ pub fn addEnvironment(self: *Session, environment: Environment) !void { addedSession(env, self.user_list); } +fn draw(self: *Session) void { + self.label.draw(); +} + +fn handle(self: *Session, maybe_key: ?keyboard.Key, insert_mode: bool) !void { + self.label.handle(maybe_key, insert_mode); +} + fn addedSession(env: Env, user_list: *UserList) void { const user = user_list.label.list.items[user_list.label.current]; if (!user.first_run) return; diff --git a/src/tui/components/Text.zig b/src/tui/components/Text.zig index e81feb6..23dec80 100644 --- a/src/tui/components/Text.zig +++ b/src/tui/components/Text.zig @@ -87,6 +87,13 @@ pub fn childrenPosition(self: Text) Position { return self.children_pos; } +pub fn clear(self: *Text) void { + self.text.clearRetainingCapacity(); + self.end = 0; + self.cursor = 0; + self.visible_start = 0; +} + pub fn handle(self: *Text, maybe_key: ?keyboard.Key, insert_mode: bool) !void { if (maybe_key) |key| { if (key.left or (!insert_mode and (key.h or key.backspace))) { @@ -117,7 +124,7 @@ pub fn handle(self: *Text, maybe_key: ?keyboard.Key, insert_mode: bool) !void { ); } -pub fn draw(self: *Text) void { +fn draw(self: *Text) void { if (self.masked) { if (self.maybe_mask) |mask| { if (self.width < 1) return; @@ -158,13 +165,6 @@ pub fn draw(self: *Text) void { ); } -pub fn clear(self: *Text) void { - self.text.clearRetainingCapacity(); - self.end = 0; - self.cursor = 0; - self.visible_start = 0; -} - fn goLeft(self: *Text) void { if (self.cursor == 0) return; if (self.visible_start > 0) self.visible_start -= 1;