From 5564fed6646b572f56892578d95fdadc50e62bd4 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Thu, 12 Feb 2026 00:27:07 +0100 Subject: [PATCH] Add Widget.calculateTimeout function Signed-off-by: AnErrupTion --- src/animations/Cascade.zig | 1 + src/animations/ColorMix.zig | 8 ++++ src/animations/Doom.zig | 8 ++++ src/animations/DurFile.zig | 8 ++++ src/animations/GameOfLife.zig | 8 ++++ src/animations/Matrix.zig | 8 ++++ src/main.zig | 59 ++++++++++++++++++++++-------- src/tui/Widget.zig | 27 ++++++++++++++ src/tui/components/BigLabel.zig | 16 ++++++++ src/tui/components/CenteredBox.zig | 1 + src/tui/components/InfoLine.zig | 1 + src/tui/components/Label.zig | 16 ++++++++ src/tui/components/Session.zig | 1 + src/tui/components/Text.zig | 1 + src/tui/components/UserList.zig | 1 + 15 files changed, 149 insertions(+), 15 deletions(-) diff --git a/src/animations/Cascade.zig b/src/animations/Cascade.zig index 2ec2452..32f2f6a 100644 --- a/src/animations/Cascade.zig +++ b/src/animations/Cascade.zig @@ -32,6 +32,7 @@ pub fn widget(self: *Cascade) Widget { draw, null, null, + null, ); } diff --git a/src/animations/ColorMix.zig b/src/animations/ColorMix.zig index a6ebd6d..278f646 100644 --- a/src/animations/ColorMix.zig +++ b/src/animations/ColorMix.zig @@ -24,6 +24,7 @@ start_time: TimeOfDay, terminal_buffer: *TerminalBuffer, animate: *bool, timeout_sec: u12, +frame_delay: u16, frames: u64, pattern_cos_mod: f32, pattern_sin_mod: f32, @@ -36,12 +37,14 @@ pub fn init( col3: u32, animate: *bool, timeout_sec: u12, + frame_delay: u16, ) !ColorMix { return .{ .start_time = try interop.getTimeOfDay(), .terminal_buffer = terminal_buffer, .animate = animate, .timeout_sec = timeout_sec, + .frame_delay = frame_delay, .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, @@ -71,6 +74,7 @@ pub fn widget(self: *ColorMix) Widget { draw, update, null, + calculateTimeout, ); } @@ -116,3 +120,7 @@ fn update(self: *ColorMix, _: *anyopaque) !void { self.animate.* = false; } } + +fn calculateTimeout(self: *ColorMix, _: *anyopaque) !?usize { + return self.frame_delay; +} diff --git a/src/animations/Doom.zig b/src/animations/Doom.zig index bb017ef..e580bd9 100644 --- a/src/animations/Doom.zig +++ b/src/animations/Doom.zig @@ -20,6 +20,7 @@ allocator: Allocator, terminal_buffer: *TerminalBuffer, animate: *bool, timeout_sec: u12, +frame_delay: u16, buffer: []u8, height: u8, spread: u8, @@ -35,6 +36,7 @@ pub fn init( fire_spread: u8, animate: *bool, timeout_sec: u12, + frame_delay: u16, ) !Doom { const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height); initBuffer(buffer, terminal_buffer.width); @@ -62,6 +64,7 @@ pub fn init( .terminal_buffer = terminal_buffer, .animate = animate, .timeout_sec = timeout_sec, + .frame_delay = frame_delay, .buffer = buffer, .height = @min(HEIGHT_MAX, fire_height), .spread = @min(SPREAD_MAX, fire_spread), @@ -78,6 +81,7 @@ pub fn widget(self: *Doom) Widget { draw, update, null, + calculateTimeout, ); } @@ -147,3 +151,7 @@ fn update(self: *Doom, _: *anyopaque) !void { self.animate.* = false; } } + +fn calculateTimeout(self: *Doom, _: *anyopaque) !?usize { + return self.frame_delay; +} diff --git a/src/animations/DurFile.zig b/src/animations/DurFile.zig index 4f425bd..9b24349 100644 --- a/src/animations/DurFile.zig +++ b/src/animations/DurFile.zig @@ -312,6 +312,7 @@ start_pos: IVec2, full_color: bool, animate: *bool, timeout_sec: u12, +frame_delay: u16, frame_time: u32, time_previous: i64, is_color_format_16: bool, @@ -373,6 +374,7 @@ pub fn init( full_color: bool, animate: *bool, timeout_sec: u12, + frame_delay: u16, ) !DurFile { var dur_movie: DurFormat = .init(allocator); @@ -414,6 +416,7 @@ pub fn init( .full_color = full_color, .animate = animate, .timeout_sec = timeout_sec, + .frame_delay = frame_delay, .dur_movie = dur_movie, .frame_time = frame_time, .is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"), @@ -431,6 +434,7 @@ pub fn widget(self: *DurFile) Widget { draw, update, null, + calculateTimeout, ); } @@ -508,3 +512,7 @@ fn update(self: *DurFile, _: *anyopaque) !void { self.animate.* = false; } } + +fn calculateTimeout(self: *DurFile, _: *anyopaque) !?usize { + return self.frame_delay; +} diff --git a/src/animations/GameOfLife.zig b/src/animations/GameOfLife.zig index 7ccfd49..929a7c0 100644 --- a/src/animations/GameOfLife.zig +++ b/src/animations/GameOfLife.zig @@ -33,6 +33,7 @@ frame_delay: usize, initial_density: f32, animate: *bool, timeout_sec: u12, +animation_frame_delay: u16, dead_cell: Cell, width: usize, height: usize, @@ -46,6 +47,7 @@ pub fn init( initial_density: f32, animate: *bool, timeout_sec: u12, + animation_frame_delay: u16, ) !GameOfLife { const width = terminal_buffer.width; const height = terminal_buffer.height; @@ -68,6 +70,7 @@ pub fn init( .initial_density = initial_density, .animate = animate, .timeout_sec = timeout_sec, + .animation_frame_delay = animation_frame_delay, .dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg }, .width = width, .height = height, @@ -88,6 +91,7 @@ pub fn widget(self: *GameOfLife) Widget { draw, update, null, + calculateTimeout, ); } @@ -149,6 +153,10 @@ fn update(self: *GameOfLife, _: *anyopaque) !void { } } +fn calculateTimeout(self: *GameOfLife, _: *anyopaque) !?usize { + return self.animation_frame_delay; +} + fn updateGeneration(self: *GameOfLife) void { // Conway's Game of Life rules with optimized neighbor counting for (0..self.height) |y| { diff --git a/src/animations/Matrix.zig b/src/animations/Matrix.zig index 4a6ef95..2ec2aad 100644 --- a/src/animations/Matrix.zig +++ b/src/animations/Matrix.zig @@ -41,6 +41,7 @@ min_codepoint: u16, max_codepoint: u16, animate: *bool, timeout_sec: u12, +frame_delay: u16, default_cell: Cell, pub fn init( @@ -52,6 +53,7 @@ pub fn init( max_codepoint: u16, animate: *bool, timeout_sec: u12, + frame_delay: u16, ) !Matrix { const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1)); const lines = try allocator.alloc(Line, terminal_buffer.width); @@ -72,6 +74,7 @@ pub fn init( .max_codepoint = max_codepoint - min_codepoint, .animate = animate, .timeout_sec = timeout_sec, + .frame_delay = frame_delay, .default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg }, }; } @@ -85,6 +88,7 @@ pub fn widget(self: *Matrix) Widget { draw, update, null, + calculateTimeout, ); } @@ -205,6 +209,10 @@ fn update(self: *Matrix, _: *anyopaque) !void { } } +fn calculateTimeout(self: *Matrix, _: *anyopaque) !?usize { + return self.frame_delay; +} + fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void { var y: usize = 0; while (y <= height) : (y += 1) { diff --git a/src/main.zig b/src/main.zig index dcd44ab..30a631f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -363,6 +363,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, null, + null, ); defer state.shutdown_label.deinit(); @@ -372,6 +373,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, null, + null, ); defer state.restart_label.deinit(); @@ -381,6 +383,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, null, + null, ); defer state.sleep_label.deinit(); @@ -390,6 +393,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, null, + null, ); defer state.hibernate_label.deinit(); @@ -399,6 +403,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, null, + null, ); defer state.brightness_down_label.deinit(); @@ -408,6 +413,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, null, + null, ); defer state.brightness_up_label.deinit(); @@ -458,6 +464,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, &updateNumlock, + null, ); defer state.numlock_label.deinit(); @@ -467,6 +474,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, &updateCapslock, + null, ); defer state.capslock_label.deinit(); @@ -476,6 +484,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, &updateBattery, + null, ); defer state.battery_label.deinit(); @@ -485,6 +494,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, &updateClock, + &calculateClockTimeout, ); defer state.clock_label.deinit(); @@ -499,6 +509,7 @@ pub fn main() !void { .fa => .fa, }, &updateBigClock, + &calculateBigClockTimeout, ); defer state.bigclock_label.deinit(); @@ -624,6 +635,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, &updateSessionSpecifier, + null, ); defer state.session_specifier_label.deinit(); @@ -644,6 +656,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, null, + null, ); defer state.login_label.deinit(); @@ -764,6 +777,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, null, + null, ); defer state.password_label.deinit(); @@ -786,6 +800,7 @@ pub fn main() !void { state.buffer.fg, state.buffer.bg, null, + null, ); defer state.version_label.deinit(); @@ -882,6 +897,7 @@ pub fn main() !void { state.config.doom_fire_spread, &state.animate, state.config.animation_timeout_sec, + state.config.animation_frame_delay, ); animation = doom.widget(); }, @@ -895,6 +911,7 @@ pub fn main() !void { state.config.cmatrix_max_codepoint, &state.animate, state.config.animation_timeout_sec, + state.config.animation_frame_delay, ); animation = matrix.widget(); }, @@ -906,6 +923,7 @@ pub fn main() !void { state.config.colormix_col3, &state.animate, state.config.animation_timeout_sec, + state.config.animation_frame_delay, ); animation = color_mix.widget(); }, @@ -919,6 +937,7 @@ pub fn main() !void { state.config.gameoflife_initial_density, &state.animate, state.config.animation_timeout_sec, + state.config.animation_frame_delay, ); animation = game_of_life.widget(); }, @@ -934,6 +953,7 @@ pub fn main() !void { state.config.full_color, &state.animate, state.config.animation_timeout_sec, + state.config.animation_frame_delay, ); animation = dur.widget(); }, @@ -1145,19 +1165,11 @@ pub fn main() !void { TerminalBuffer.presentBuffer(); } - var timeout: i32 = -1; - - // Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for the event indefinitely instead - if (state.animate) { - timeout = state.config.animation_frame_delay; - } else if (state.config.bigclock != .none and state.config.clock == null) { - const time = try interop.getTimeOfDay(); - - timeout = @intCast((60 - @rem(time.seconds, 60)) * 1000 - @divTrunc(time.microseconds, 1000) + 1); - } else if (state.config.clock != null) { - const time = try interop.getTimeOfDay(); - - timeout = @intCast(1000 - @divTrunc(time.microseconds, 1000) + 1); + var maybe_timeout: ?usize = null; + for (widgets.items) |*widget| { + if (try widget.calculateTimeout(&state)) |widget_timeout| { + if (maybe_timeout == null or widget_timeout < maybe_timeout.?) maybe_timeout = widget_timeout; + } } if (state.config.inactivity_cmd) |inactivity_cmd| { @@ -1190,9 +1202,9 @@ pub fn main() !void { } } - const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout); + const event_error = if (maybe_timeout) |timeout| termbox.tb_peek_event(&event, @intCast(timeout)) else termbox.tb_poll_event(&event); - state.update = timeout != -1; + state.update = maybe_timeout != null; if (event_error < 0) continue; @@ -1741,6 +1753,12 @@ fn updateClock(self: *Label, ptr: *anyopaque) !void { } } +fn calculateClockTimeout(_: *Label, _: *anyopaque) !?usize { + const time = try interop.getTimeOfDay(); + + return @intCast(1000 - @divTrunc(time.microseconds, 1000) + 1); +} + fn updateBigClock(self: *BigLabel, ptr: *anyopaque) !void { var state: *UiState = @ptrCast(@alignCast(ptr)); @@ -1766,6 +1784,17 @@ fn updateBigClock(self: *BigLabel, ptr: *anyopaque) !void { self.setText(clock_str); } +fn calculateBigClockTimeout(_: *BigLabel, ptr: *anyopaque) !?usize { + const state: *UiState = @ptrCast(@alignCast(ptr)); + const time = try interop.getTimeOfDay(); + + if (state.config.bigclock_seconds) { + return @intCast(1000 - @divTrunc(time.microseconds, 1000) + 1); + } + + return @intCast((60 - @rem(time.seconds, 60)) * 1000 - @divTrunc(time.microseconds, 1000) + 1); +} + fn updateBox(self: *CenteredBox, ptr: *anyopaque) !void { const state: *UiState = @ptrCast(@alignCast(ptr)); diff --git a/src/tui/Widget.zig b/src/tui/Widget.zig index dc9b609..98891fd 100644 --- a/src/tui/Widget.zig +++ b/src/tui/Widget.zig @@ -9,6 +9,7 @@ const VTable = struct { draw_fn: *const fn (ptr: *anyopaque) void, update_fn: ?*const fn (ptr: *anyopaque, ctx: *anyopaque) anyerror!void, handle_fn: ?*const fn (ptr: *anyopaque, maybe_key: ?keyboard.Key, insert_mode: bool) anyerror!void, + calculate_timeout_fn: ?*const fn (ptr: *anyopaque, ctx: *anyopaque) anyerror!?usize, }; id: u64, @@ -24,6 +25,7 @@ pub fn init( comptime draw_fn: fn (ptr: @TypeOf(pointer)) void, comptime update_fn: ?fn (ptr: @TypeOf(pointer), ctx: *anyopaque) anyerror!void, comptime handle_fn: ?fn (ptr: @TypeOf(pointer), maybe_key: ?keyboard.Key, insert_mode: bool) anyerror!void, + comptime calculate_timeout_fn: ?fn (ptr: @TypeOf(pointer), ctx: *anyopaque) anyerror!?usize, ) Widget { const Pointer = @TypeOf(pointer); const Impl = struct { @@ -77,12 +79,23 @@ pub fn init( ); } + pub fn calculateTimeoutImpl(ptr: *anyopaque, ctx: *anyopaque) !?usize { + const impl: Pointer = @ptrCast(@alignCast(ptr)); + + return @call( + .always_inline, + calculate_timeout_fn.?, + .{ impl, ctx }, + ); + } + const vtable = VTable{ .deinit_fn = if (deinit_fn != null) deinitImpl else null, .realloc_fn = if (realloc_fn != null) reallocImpl else null, .draw_fn = drawImpl, .update_fn = if (update_fn != null) updateImpl else null, .handle_fn = if (handle_fn != null) handleImpl else null, + .calculate_timeout_fn = if (calculate_timeout_fn != null) calculateTimeoutImpl else null, }; }; @@ -151,3 +164,17 @@ pub fn handle(self: *Widget, maybe_key: ?keyboard.Key, insert_mode: bool) !void ); } } + +pub fn calculateTimeout(self: *Widget, ctx: *anyopaque) !?usize { + const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer)); + + if (self.vtable.calculate_timeout_fn) |calculate_timeout_fn| { + return @call( + .auto, + calculate_timeout_fn, + .{ impl, ctx }, + ); + } + + return null; +} diff --git a/src/tui/components/BigLabel.zig b/src/tui/components/BigLabel.zig index 934102e..5159f66 100644 --- a/src/tui/components/BigLabel.zig +++ b/src/tui/components/BigLabel.zig @@ -52,6 +52,7 @@ fg: u32, bg: u32, locale: BigLabelLocale, update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void, +calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize, component_pos: Position, children_pos: Position, @@ -63,6 +64,7 @@ pub fn init( bg: u32, locale: BigLabelLocale, update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void, + calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize, ) BigLabel { return .{ .allocator = null, @@ -73,6 +75,7 @@ pub fn init( .bg = bg, .locale = locale, .update_fn = update_fn, + .calculate_timeout_fn = calculate_timeout_fn, .component_pos = TerminalBuffer.START_POSITION, .children_pos = TerminalBuffer.START_POSITION, }; @@ -91,6 +94,7 @@ pub fn widget(self: *BigLabel) Widget { draw, update, null, + calculateTimeout, ); } @@ -170,6 +174,18 @@ fn update(self: *BigLabel, context: *anyopaque) !void { } } +fn calculateTimeout(self: *BigLabel, ctx: *anyopaque) !?usize { + if (self.calculate_timeout_fn) |calculate_timeout_fn| { + return @call( + .auto, + calculate_timeout_fn, + .{ self, ctx }, + ); + } + + return null; +} + fn clockCell(char: u8, fg: u32, bg: u32, locale: BigLabelLocale) [CHAR_SIZE]Cell { var cells: [CHAR_SIZE]Cell = undefined; diff --git a/src/tui/components/CenteredBox.zig b/src/tui/components/CenteredBox.zig index 1e69be5..f0e7cd5 100644 --- a/src/tui/components/CenteredBox.zig +++ b/src/tui/components/CenteredBox.zig @@ -68,6 +68,7 @@ pub fn widget(self: *CenteredBox) Widget { draw, null, null, + null, ); } diff --git a/src/tui/components/InfoLine.zig b/src/tui/components/InfoLine.zig index c8d51b7..95578f5 100644 --- a/src/tui/components/InfoLine.zig +++ b/src/tui/components/InfoLine.zig @@ -54,6 +54,7 @@ pub fn widget(self: *InfoLine) Widget { draw, null, handle, + null, ); } diff --git a/src/tui/components/Label.zig b/src/tui/components/Label.zig index 7f5015d..f80793a 100644 --- a/src/tui/components/Label.zig +++ b/src/tui/components/Label.zig @@ -14,6 +14,7 @@ max_width: ?usize, fg: u32, bg: u32, update_fn: ?*const fn (*Label, *anyopaque) anyerror!void, +calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize, component_pos: Position, children_pos: Position, @@ -23,6 +24,7 @@ pub fn init( fg: u32, bg: u32, update_fn: ?*const fn (*Label, *anyopaque) anyerror!void, + calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize, ) Label { return .{ .allocator = null, @@ -31,6 +33,7 @@ pub fn init( .fg = fg, .bg = bg, .update_fn = update_fn, + .calculate_timeout_fn = calculate_timeout_fn, .component_pos = TerminalBuffer.START_POSITION, .children_pos = TerminalBuffer.START_POSITION, }; @@ -49,6 +52,7 @@ pub fn widget(self: *Label) Widget { draw, update, null, + calculateTimeout, ); } @@ -130,3 +134,15 @@ fn update(self: *Label, ctx: *anyopaque) !void { ); } } + +fn calculateTimeout(self: *Label, ctx: *anyopaque) !?usize { + if (self.calculate_timeout_fn) |calculate_timeout_fn| { + return @call( + .auto, + calculate_timeout_fn, + .{ self, ctx }, + ); + } + + return null; +} diff --git a/src/tui/components/Session.zig b/src/tui/components/Session.zig index 81460c0..c221ef6 100644 --- a/src/tui/components/Session.zig +++ b/src/tui/components/Session.zig @@ -64,6 +64,7 @@ pub fn widget(self: *Session) Widget { draw, null, handle, + null, ); } diff --git a/src/tui/components/Text.zig b/src/tui/components/Text.zig index 7e04d09..3890e79 100644 --- a/src/tui/components/Text.zig +++ b/src/tui/components/Text.zig @@ -63,6 +63,7 @@ pub fn widget(self: *Text) Widget { draw, null, handle, + null, ); } diff --git a/src/tui/components/UserList.zig b/src/tui/components/UserList.zig index f4d9179..dadf8dc 100644 --- a/src/tui/components/UserList.zig +++ b/src/tui/components/UserList.zig @@ -96,6 +96,7 @@ pub fn widget(self: *UserList) Widget { draw, null, handle, + null, ); }