From 60e3380375e4752be713f020d4fd9067c1eaeeb5 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Sat, 21 Mar 2026 16:19:33 +0100 Subject: [PATCH] Switch to single-instance Widget model And make widget() functions return pointers to widgets instead of just widgets Signed-off-by: AnErrupTion --- ly-ui/src/TerminalBuffer.zig | 16 ++++++++-------- ly-ui/src/components/BigLabel.zig | 8 ++++++-- ly-ui/src/components/CenteredBox.zig | 8 ++++++-- ly-ui/src/components/Label.zig | 8 ++++++-- ly-ui/src/components/Text.zig | 8 ++++++-- src/animations/Cascade.zig | 8 ++++++-- 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/components/InfoLine.zig | 8 ++++++-- src/components/Session.zig | 8 ++++++-- src/components/UserList.zig | 8 ++++++-- src/main.zig | 14 +++++++------- 15 files changed, 93 insertions(+), 41 deletions(-) diff --git a/ly-ui/src/TerminalBuffer.zig b/ly-ui/src/TerminalBuffer.zig index b2383af..034916c 100644 --- a/ly-ui/src/TerminalBuffer.zig +++ b/ly-ui/src/TerminalBuffer.zig @@ -169,8 +169,8 @@ pub fn runEventLoop( self: *TerminalBuffer, allocator: Allocator, shared_error: SharedError, - layers: [][]Widget, - active_widget: Widget, + layers: [][]*Widget, + active_widget: *Widget, inactivity_delay: u16, position_widgets_fn: *const fn (*anyopaque) anyerror!void, inactivity_event_fn: ?*const fn (*anyopaque) anyerror!void, @@ -189,7 +189,7 @@ pub fn runEventLoop( var i: usize = 0; for (layers) |layer| { - for (layer) |*widget| { + for (layer) |widget| { try widget.update(context); if (widget.vtable.handle_fn != null) { @@ -210,7 +210,7 @@ pub fn runEventLoop( while (self.run) { if (self.update) { for (layers) |layer| { - for (layer) |*widget| { + for (layer) |widget| { try widget.update(context); } } @@ -229,7 +229,7 @@ pub fn runEventLoop( try TerminalBuffer.clearScreen(false); for (layers) |layer| { - for (layer) |*widget| { + for (layer) |widget| { widget.draw(); } } @@ -239,7 +239,7 @@ pub fn runEventLoop( var maybe_timeout: ?usize = null; for (layers) |layer| { - for (layer) |*widget| { + for (layer) |widget| { if (try widget.calculateTimeout(context)) |widget_timeout| { if (maybe_timeout == null or widget_timeout < maybe_timeout.?) maybe_timeout = widget_timeout; } @@ -275,7 +275,7 @@ pub fn runEventLoop( ); for (layers) |layer| { - for (layer) |*widget| { + for (layer) |widget| { widget.realloc() catch |err| { shared_error.writeError(error.WidgetReallocationFailed); try self.log_file.err( @@ -326,7 +326,7 @@ pub fn getActiveWidget(self: *TerminalBuffer) *Widget { return self.handlable_widgets.items[self.active_widget_index]; } -pub fn setActiveWidget(self: *TerminalBuffer, widget: Widget) void { +pub fn setActiveWidget(self: *TerminalBuffer, widget: *Widget) void { for (self.handlable_widgets.items, 0..) |widg, i| { if (widg.id == widget.id) self.active_widget_index = i; } diff --git a/ly-ui/src/components/BigLabel.zig b/ly-ui/src/components/BigLabel.zig index 156a498..bd9af6e 100644 --- a/ly-ui/src/components/BigLabel.zig +++ b/ly-ui/src/components/BigLabel.zig @@ -44,6 +44,7 @@ pub const BigLabelLocale = enum { fa, }; +instance: ?Widget = null, allocator: ?Allocator = null, buffer: *TerminalBuffer, text: []const u8, @@ -67,6 +68,7 @@ pub fn init( calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize, ) BigLabel { return .{ + .instance = null, .allocator = null, .buffer = buffer, .text = text, @@ -85,8 +87,9 @@ pub fn deinit(self: *BigLabel) void { if (self.allocator) |allocator| allocator.free(self.text); } -pub fn widget(self: *BigLabel) Widget { - return Widget.init( +pub fn widget(self: *BigLabel) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "BigLabel", null, self, @@ -97,6 +100,7 @@ pub fn widget(self: *BigLabel) Widget { null, calculateTimeout, ); + return &self.instance.?; } pub fn setTextAlloc( diff --git a/ly-ui/src/components/CenteredBox.zig b/ly-ui/src/components/CenteredBox.zig index 934a83c..f68709e 100644 --- a/ly-ui/src/components/CenteredBox.zig +++ b/ly-ui/src/components/CenteredBox.zig @@ -7,6 +7,7 @@ const Widget = @import("../Widget.zig"); const CenteredBox = @This(); +instance: ?Widget = null, buffer: *TerminalBuffer, horizontal_margin: usize, vertical_margin: usize, @@ -40,6 +41,7 @@ pub fn init( update_fn: ?*const fn (*CenteredBox, *anyopaque) anyerror!void, ) CenteredBox { return .{ + .instance = null, .buffer = buffer, .horizontal_margin = horizontal_margin, .vertical_margin = vertical_margin, @@ -59,8 +61,9 @@ pub fn init( }; } -pub fn widget(self: *CenteredBox) Widget { - return Widget.init( +pub fn widget(self: *CenteredBox) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "CenteredBox", null, self, @@ -71,6 +74,7 @@ pub fn widget(self: *CenteredBox) Widget { null, null, ); + return &self.instance.?; } pub fn positionXY(self: *CenteredBox, original_pos: Position) void { diff --git a/ly-ui/src/components/Label.zig b/ly-ui/src/components/Label.zig index 9f47820..9874fcb 100644 --- a/ly-ui/src/components/Label.zig +++ b/ly-ui/src/components/Label.zig @@ -8,6 +8,7 @@ const Position = @import("../Position.zig"); const TerminalBuffer = @import("../TerminalBuffer.zig"); const Widget = @import("../Widget.zig"); +instance: ?Widget, allocator: ?Allocator, text: []const u8, max_width: ?usize, @@ -27,6 +28,7 @@ pub fn init( calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize, ) Label { return .{ + .instance = null, .allocator = null, .text = text, .max_width = max_width, @@ -43,8 +45,9 @@ pub fn deinit(self: *Label) void { if (self.allocator) |allocator| allocator.free(self.text); } -pub fn widget(self: *Label) Widget { - return Widget.init( +pub fn widget(self: *Label) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "Label", null, self, @@ -55,6 +58,7 @@ pub fn widget(self: *Label) Widget { null, calculateTimeout, ); + return &self.instance.?; } pub fn setTextAlloc( diff --git a/ly-ui/src/components/Text.zig b/ly-ui/src/components/Text.zig index 0ad2128..fc04fe7 100644 --- a/ly-ui/src/components/Text.zig +++ b/ly-ui/src/components/Text.zig @@ -10,6 +10,7 @@ const DynamicString = std.ArrayListUnmanaged(u8); const Text = @This(); +instance: ?Widget, allocator: Allocator, buffer: *TerminalBuffer, text: DynamicString, @@ -38,6 +39,7 @@ pub fn init( ) !*Text { var self = try allocator.create(Text); self.* = Text{ + .instance = null, .allocator = allocator, .buffer = buffer, .text = .empty, @@ -70,8 +72,9 @@ pub fn deinit(self: *Text) void { self.allocator.destroy(self); } -pub fn widget(self: *Text) Widget { - return Widget.init( +pub fn widget(self: *Text) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "Text", self.keybinds, self, @@ -82,6 +85,7 @@ pub fn widget(self: *Text) Widget { handle, null, ); + return &self.instance.?; } pub fn positionX(self: *Text, original_pos: Position) void { diff --git a/src/animations/Cascade.zig b/src/animations/Cascade.zig index 970d99a..b11c728 100644 --- a/src/animations/Cascade.zig +++ b/src/animations/Cascade.zig @@ -8,6 +8,7 @@ const Widget = ly_ui.Widget; const Cascade = @This(); +instance: ?Widget = null, buffer: *TerminalBuffer, current_auth_fails: *usize, max_auth_fails: usize, @@ -18,14 +19,16 @@ pub fn init( max_auth_fails: usize, ) Cascade { return .{ + .instance = null, .buffer = buffer, .current_auth_fails = current_auth_fails, .max_auth_fails = max_auth_fails, }; } -pub fn widget(self: *Cascade) Widget { - return Widget.init( +pub fn widget(self: *Cascade) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "Cascade", null, self, @@ -36,6 +39,7 @@ pub fn widget(self: *Cascade) Widget { null, null, ); + return &self.instance.?; } fn draw(self: *Cascade) void { diff --git a/src/animations/ColorMix.zig b/src/animations/ColorMix.zig index 227205f..0715825 100644 --- a/src/animations/ColorMix.zig +++ b/src/animations/ColorMix.zig @@ -21,6 +21,7 @@ fn length(vec: Vec2) f32 { return math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]); } +instance: ?Widget = null, start_time: TimeOfDay, terminal_buffer: *TerminalBuffer, animate: *bool, @@ -41,6 +42,7 @@ pub fn init( frame_delay: u16, ) !ColorMix { return .{ + .instance = null, .start_time = try interop.getTimeOfDay(), .terminal_buffer = terminal_buffer, .animate = animate, @@ -66,8 +68,9 @@ pub fn init( }; } -pub fn widget(self: *ColorMix) Widget { - return Widget.init( +pub fn widget(self: *ColorMix) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "ColorMix", null, self, @@ -78,6 +81,7 @@ pub fn widget(self: *ColorMix) Widget { null, calculateTimeout, ); + return &self.instance.?; } fn draw(self: *ColorMix) void { diff --git a/src/animations/Doom.zig b/src/animations/Doom.zig index 8fa842b..9b0abae 100644 --- a/src/animations/Doom.zig +++ b/src/animations/Doom.zig @@ -16,6 +16,7 @@ pub const STEPS = 12; pub const HEIGHT_MAX = 9; pub const SPREAD_MAX = 4; +instance: ?Widget = null, start_time: TimeOfDay, allocator: Allocator, terminal_buffer: *TerminalBuffer, @@ -60,6 +61,7 @@ pub fn init( }; return .{ + .instance = null, .start_time = try interop.getTimeOfDay(), .allocator = allocator, .terminal_buffer = terminal_buffer, @@ -73,8 +75,9 @@ pub fn init( }; } -pub fn widget(self: *Doom) Widget { - return Widget.init( +pub fn widget(self: *Doom) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "Doom", null, self, @@ -85,6 +88,7 @@ pub fn widget(self: *Doom) Widget { null, calculateTimeout, ); + return &self.instance.?; } fn deinit(self: *Doom) void { diff --git a/src/animations/DurFile.zig b/src/animations/DurFile.zig index 04fc8ff..b6aceae 100644 --- a/src/animations/DurFile.zig +++ b/src/animations/DurFile.zig @@ -304,6 +304,7 @@ const VEC_Y = 1; const DurFile = @This(); +instance: ?Widget = null, start_time: TimeOfDay, allocator: Allocator, terminal_buffer: *TerminalBuffer, @@ -408,6 +409,7 @@ pub fn init( const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?); return .{ + .instance = null, .start_time = try interop.getTimeOfDay(), .allocator = allocator, .terminal_buffer = terminal_buffer, @@ -427,8 +429,9 @@ pub fn init( }; } -pub fn widget(self: *DurFile) Widget { - return Widget.init( +pub fn widget(self: *DurFile) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "DurFile", null, self, @@ -439,6 +442,7 @@ pub fn widget(self: *DurFile) Widget { null, calculateTimeout, ); + return &self.instance.?; } fn deinit(self: *DurFile) void { diff --git a/src/animations/GameOfLife.zig b/src/animations/GameOfLife.zig index 3f6e5d5..c100c52 100644 --- a/src/animations/GameOfLife.zig +++ b/src/animations/GameOfLife.zig @@ -21,6 +21,7 @@ const NEIGHBOR_DIRS = [_][2]i8{ .{ 1, 0 }, .{ 1, 1 }, }; +instance: ?Widget = null, start_time: TimeOfDay, allocator: Allocator, terminal_buffer: *TerminalBuffer, @@ -58,6 +59,7 @@ pub fn init( const next_grid = try allocator.alloc(bool, grid_size); var game = GameOfLife{ + .instance = null, .start_time = try interop.getTimeOfDay(), .allocator = allocator, .terminal_buffer = terminal_buffer, @@ -83,8 +85,9 @@ pub fn init( return game; } -pub fn widget(self: *GameOfLife) Widget { - return Widget.init( +pub fn widget(self: *GameOfLife) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "GameOfLife", null, self, @@ -95,6 +98,7 @@ pub fn widget(self: *GameOfLife) Widget { null, calculateTimeout, ); + return &self.instance.?; } fn deinit(self: *GameOfLife) void { diff --git a/src/animations/Matrix.zig b/src/animations/Matrix.zig index 9becbd6..e7af489 100644 --- a/src/animations/Matrix.zig +++ b/src/animations/Matrix.zig @@ -29,6 +29,7 @@ pub const Line = struct { update: usize, }; +instance: ?Widget = null, start_time: TimeOfDay, allocator: Allocator, terminal_buffer: *TerminalBuffer, @@ -62,6 +63,7 @@ pub fn init( initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random); return .{ + .instance = null, .start_time = try interop.getTimeOfDay(), .allocator = allocator, .terminal_buffer = terminal_buffer, @@ -80,8 +82,9 @@ pub fn init( }; } -pub fn widget(self: *Matrix) Widget { - return Widget.init( +pub fn widget(self: *Matrix) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "Matrix", null, self, @@ -92,6 +95,7 @@ pub fn widget(self: *Matrix) Widget { null, calculateTimeout, ); + return &self.instance.?; } fn deinit(self: *Matrix) void { diff --git a/src/components/InfoLine.zig b/src/components/InfoLine.zig index 7e81cf3..7a1a06b 100644 --- a/src/components/InfoLine.zig +++ b/src/components/InfoLine.zig @@ -18,6 +18,7 @@ const Message = struct { fg: u32, }; +instance: ?Widget = null, label: *MessageLabel, pub fn init( @@ -28,6 +29,7 @@ pub fn init( arrow_bg: u32, ) !InfoLine { return .{ + .instance = null, .label = try MessageLabel.init( allocator, buffer, @@ -46,8 +48,9 @@ pub fn deinit(self: *InfoLine) void { self.label.deinit(); } -pub fn widget(self: *InfoLine) Widget { - return Widget.init( +pub fn widget(self: *InfoLine) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "InfoLine", self.label.keybinds, self, @@ -58,6 +61,7 @@ pub fn widget(self: *InfoLine) Widget { handle, null, ); + return &self.instance.?; } pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void { diff --git a/src/components/Session.zig b/src/components/Session.zig index 373145e..1abf9b9 100644 --- a/src/components/Session.zig +++ b/src/components/Session.zig @@ -18,6 +18,7 @@ const EnvironmentLabel = CyclableLabel(Env, *UserList); const Session = @This(); +instance: ?Widget = null, label: *EnvironmentLabel, user_list: *UserList, @@ -31,6 +32,7 @@ pub fn init( bg: u32, ) !Session { return .{ + .instance = null, .label = try EnvironmentLabel.init( allocator, buffer, @@ -55,8 +57,9 @@ pub fn deinit(self: *Session) void { self.label.deinit(); } -pub fn widget(self: *Session) Widget { - return Widget.init( +pub fn widget(self: *Session) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "Session", self.label.keybinds, self, @@ -67,6 +70,7 @@ pub fn widget(self: *Session) Widget { handle, null, ); + return &self.instance.?; } pub fn addEnvironment(self: *Session, environment: Environment) !void { diff --git a/src/components/UserList.zig b/src/components/UserList.zig index 5747ed7..2131cae 100644 --- a/src/components/UserList.zig +++ b/src/components/UserList.zig @@ -21,6 +21,7 @@ const UserLabel = CyclableLabel(User, *Session); const UserList = @This(); +instance: ?Widget = null, label: *UserLabel, pub fn init( @@ -35,6 +36,7 @@ pub fn init( bg: u32, ) !UserList { var user_list = UserList{ + .instance = null, .label = try UserLabel.init( allocator, buffer, @@ -89,8 +91,9 @@ pub fn deinit(self: *UserList) void { self.label.deinit(); } -pub fn widget(self: *UserList) Widget { - return Widget.init( +pub fn widget(self: *UserList) *Widget { + if (self.instance) |*instance| return instance; + self.instance = Widget.init( "UserList", self.label.keybinds, self, @@ -101,6 +104,7 @@ pub fn widget(self: *UserList) Widget { handle, null, ); + return &self.instance.?; } pub fn getCurrentUsername(self: UserList) []const u8 { diff --git a/src/main.zig b/src/main.zig index 0e261db..fe3c748 100644 --- a/src/main.zig +++ b/src/main.zig @@ -94,7 +94,7 @@ const UiState = struct { saved_users: SavedUsers, login: UserList, password: *Text, - password_widget: Widget, + password_widget: *Widget, insert_mode: bool, edge_margin: Position, config: Config, @@ -910,7 +910,7 @@ pub fn main() !void { } // Initialize the animation, if any - var animation: ?Widget = null; + var animation: ?*Widget = null; switch (state.config.animation) { .none => {}, .doom => { @@ -985,7 +985,7 @@ pub fn main() !void { animation = dur.widget(); }, } - defer if (animation) |*a| a.deinit(); + defer if (animation) |a| a.deinit(); var cascade = Cascade.init( &state.buffer, @@ -1030,17 +1030,17 @@ pub fn main() !void { const session_widget = state.session.widget(); const login_widget = state.login.widget(); - var widgets: std.ArrayList([]Widget) = .empty; + var widgets: std.ArrayList([]*Widget) = .empty; defer widgets.deinit(state.allocator); // Layer 1 if (animation) |a| { - var layer1 = [_]Widget{a}; + var layer1 = [_]*Widget{a}; try widgets.append(state.allocator, &layer1); } // Layer 2 - var layer2: std.ArrayList(Widget) = .empty; + var layer2: std.ArrayList(*Widget) = .empty; defer layer2.deinit(state.allocator); if (!state.config.hide_key_hints) { @@ -1089,7 +1089,7 @@ pub fn main() !void { // Layer 3 if (state.config.auth_fails > 0) { - var layer3 = [_]Widget{cascade.widget()}; + var layer3 = [_]*Widget{cascade.widget()}; try widgets.append(state.allocator, &layer3); }