Switch to single-instance Widget model

And make widget() functions return pointers to widgets instead of just
widgets

Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
AnErrupTion
2026-03-21 16:19:33 +01:00
parent aa392837bc
commit 60e3380375
15 changed files with 93 additions and 41 deletions

View File

@@ -169,8 +169,8 @@ pub fn runEventLoop(
self: *TerminalBuffer, self: *TerminalBuffer,
allocator: Allocator, allocator: Allocator,
shared_error: SharedError, shared_error: SharedError,
layers: [][]Widget, layers: [][]*Widget,
active_widget: Widget, active_widget: *Widget,
inactivity_delay: u16, inactivity_delay: u16,
position_widgets_fn: *const fn (*anyopaque) anyerror!void, position_widgets_fn: *const fn (*anyopaque) anyerror!void,
inactivity_event_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; var i: usize = 0;
for (layers) |layer| { for (layers) |layer| {
for (layer) |*widget| { for (layer) |widget| {
try widget.update(context); try widget.update(context);
if (widget.vtable.handle_fn != null) { if (widget.vtable.handle_fn != null) {
@@ -210,7 +210,7 @@ pub fn runEventLoop(
while (self.run) { while (self.run) {
if (self.update) { if (self.update) {
for (layers) |layer| { for (layers) |layer| {
for (layer) |*widget| { for (layer) |widget| {
try widget.update(context); try widget.update(context);
} }
} }
@@ -229,7 +229,7 @@ pub fn runEventLoop(
try TerminalBuffer.clearScreen(false); try TerminalBuffer.clearScreen(false);
for (layers) |layer| { for (layers) |layer| {
for (layer) |*widget| { for (layer) |widget| {
widget.draw(); widget.draw();
} }
} }
@@ -239,7 +239,7 @@ pub fn runEventLoop(
var maybe_timeout: ?usize = null; var maybe_timeout: ?usize = null;
for (layers) |layer| { for (layers) |layer| {
for (layer) |*widget| { for (layer) |widget| {
if (try widget.calculateTimeout(context)) |widget_timeout| { if (try widget.calculateTimeout(context)) |widget_timeout| {
if (maybe_timeout == null or widget_timeout < maybe_timeout.?) maybe_timeout = 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 (layers) |layer| {
for (layer) |*widget| { for (layer) |widget| {
widget.realloc() catch |err| { widget.realloc() catch |err| {
shared_error.writeError(error.WidgetReallocationFailed); shared_error.writeError(error.WidgetReallocationFailed);
try self.log_file.err( try self.log_file.err(
@@ -326,7 +326,7 @@ pub fn getActiveWidget(self: *TerminalBuffer) *Widget {
return self.handlable_widgets.items[self.active_widget_index]; 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| { for (self.handlable_widgets.items, 0..) |widg, i| {
if (widg.id == widget.id) self.active_widget_index = i; if (widg.id == widget.id) self.active_widget_index = i;
} }

View File

@@ -44,6 +44,7 @@ pub const BigLabelLocale = enum {
fa, fa,
}; };
instance: ?Widget = null,
allocator: ?Allocator = null, allocator: ?Allocator = null,
buffer: *TerminalBuffer, buffer: *TerminalBuffer,
text: []const u8, text: []const u8,
@@ -67,6 +68,7 @@ pub fn init(
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize, calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
) BigLabel { ) BigLabel {
return .{ return .{
.instance = null,
.allocator = null, .allocator = null,
.buffer = buffer, .buffer = buffer,
.text = text, .text = text,
@@ -85,8 +87,9 @@ pub fn deinit(self: *BigLabel) void {
if (self.allocator) |allocator| allocator.free(self.text); if (self.allocator) |allocator| allocator.free(self.text);
} }
pub fn widget(self: *BigLabel) Widget { pub fn widget(self: *BigLabel) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"BigLabel", "BigLabel",
null, null,
self, self,
@@ -97,6 +100,7 @@ pub fn widget(self: *BigLabel) Widget {
null, null,
calculateTimeout, calculateTimeout,
); );
return &self.instance.?;
} }
pub fn setTextAlloc( pub fn setTextAlloc(

View File

@@ -7,6 +7,7 @@ const Widget = @import("../Widget.zig");
const CenteredBox = @This(); const CenteredBox = @This();
instance: ?Widget = null,
buffer: *TerminalBuffer, buffer: *TerminalBuffer,
horizontal_margin: usize, horizontal_margin: usize,
vertical_margin: usize, vertical_margin: usize,
@@ -40,6 +41,7 @@ pub fn init(
update_fn: ?*const fn (*CenteredBox, *anyopaque) anyerror!void, update_fn: ?*const fn (*CenteredBox, *anyopaque) anyerror!void,
) CenteredBox { ) CenteredBox {
return .{ return .{
.instance = null,
.buffer = buffer, .buffer = buffer,
.horizontal_margin = horizontal_margin, .horizontal_margin = horizontal_margin,
.vertical_margin = vertical_margin, .vertical_margin = vertical_margin,
@@ -59,8 +61,9 @@ pub fn init(
}; };
} }
pub fn widget(self: *CenteredBox) Widget { pub fn widget(self: *CenteredBox) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"CenteredBox", "CenteredBox",
null, null,
self, self,
@@ -71,6 +74,7 @@ pub fn widget(self: *CenteredBox) Widget {
null, null,
null, null,
); );
return &self.instance.?;
} }
pub fn positionXY(self: *CenteredBox, original_pos: Position) void { pub fn positionXY(self: *CenteredBox, original_pos: Position) void {

View File

@@ -8,6 +8,7 @@ const Position = @import("../Position.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig"); const TerminalBuffer = @import("../TerminalBuffer.zig");
const Widget = @import("../Widget.zig"); const Widget = @import("../Widget.zig");
instance: ?Widget,
allocator: ?Allocator, allocator: ?Allocator,
text: []const u8, text: []const u8,
max_width: ?usize, max_width: ?usize,
@@ -27,6 +28,7 @@ pub fn init(
calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize, calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize,
) Label { ) Label {
return .{ return .{
.instance = null,
.allocator = null, .allocator = null,
.text = text, .text = text,
.max_width = max_width, .max_width = max_width,
@@ -43,8 +45,9 @@ pub fn deinit(self: *Label) void {
if (self.allocator) |allocator| allocator.free(self.text); if (self.allocator) |allocator| allocator.free(self.text);
} }
pub fn widget(self: *Label) Widget { pub fn widget(self: *Label) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Label", "Label",
null, null,
self, self,
@@ -55,6 +58,7 @@ pub fn widget(self: *Label) Widget {
null, null,
calculateTimeout, calculateTimeout,
); );
return &self.instance.?;
} }
pub fn setTextAlloc( pub fn setTextAlloc(

View File

@@ -10,6 +10,7 @@ const DynamicString = std.ArrayListUnmanaged(u8);
const Text = @This(); const Text = @This();
instance: ?Widget,
allocator: Allocator, allocator: Allocator,
buffer: *TerminalBuffer, buffer: *TerminalBuffer,
text: DynamicString, text: DynamicString,
@@ -38,6 +39,7 @@ pub fn init(
) !*Text { ) !*Text {
var self = try allocator.create(Text); var self = try allocator.create(Text);
self.* = Text{ self.* = Text{
.instance = null,
.allocator = allocator, .allocator = allocator,
.buffer = buffer, .buffer = buffer,
.text = .empty, .text = .empty,
@@ -70,8 +72,9 @@ pub fn deinit(self: *Text) void {
self.allocator.destroy(self); self.allocator.destroy(self);
} }
pub fn widget(self: *Text) Widget { pub fn widget(self: *Text) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Text", "Text",
self.keybinds, self.keybinds,
self, self,
@@ -82,6 +85,7 @@ pub fn widget(self: *Text) Widget {
handle, handle,
null, null,
); );
return &self.instance.?;
} }
pub fn positionX(self: *Text, original_pos: Position) void { pub fn positionX(self: *Text, original_pos: Position) void {

View File

@@ -8,6 +8,7 @@ const Widget = ly_ui.Widget;
const Cascade = @This(); const Cascade = @This();
instance: ?Widget = null,
buffer: *TerminalBuffer, buffer: *TerminalBuffer,
current_auth_fails: *usize, current_auth_fails: *usize,
max_auth_fails: usize, max_auth_fails: usize,
@@ -18,14 +19,16 @@ pub fn init(
max_auth_fails: usize, max_auth_fails: usize,
) Cascade { ) Cascade {
return .{ return .{
.instance = null,
.buffer = buffer, .buffer = buffer,
.current_auth_fails = current_auth_fails, .current_auth_fails = current_auth_fails,
.max_auth_fails = max_auth_fails, .max_auth_fails = max_auth_fails,
}; };
} }
pub fn widget(self: *Cascade) Widget { pub fn widget(self: *Cascade) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Cascade", "Cascade",
null, null,
self, self,
@@ -36,6 +39,7 @@ pub fn widget(self: *Cascade) Widget {
null, null,
null, null,
); );
return &self.instance.?;
} }
fn draw(self: *Cascade) void { fn draw(self: *Cascade) void {

View File

@@ -21,6 +21,7 @@ fn length(vec: Vec2) f32 {
return math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]); return math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]);
} }
instance: ?Widget = null,
start_time: TimeOfDay, start_time: TimeOfDay,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
animate: *bool, animate: *bool,
@@ -41,6 +42,7 @@ pub fn init(
frame_delay: u16, frame_delay: u16,
) !ColorMix { ) !ColorMix {
return .{ return .{
.instance = null,
.start_time = try interop.getTimeOfDay(), .start_time = try interop.getTimeOfDay(),
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.animate = animate, .animate = animate,
@@ -66,8 +68,9 @@ pub fn init(
}; };
} }
pub fn widget(self: *ColorMix) Widget { pub fn widget(self: *ColorMix) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"ColorMix", "ColorMix",
null, null,
self, self,
@@ -78,6 +81,7 @@ pub fn widget(self: *ColorMix) Widget {
null, null,
calculateTimeout, calculateTimeout,
); );
return &self.instance.?;
} }
fn draw(self: *ColorMix) void { fn draw(self: *ColorMix) void {

View File

@@ -16,6 +16,7 @@ pub const STEPS = 12;
pub const HEIGHT_MAX = 9; pub const HEIGHT_MAX = 9;
pub const SPREAD_MAX = 4; pub const SPREAD_MAX = 4;
instance: ?Widget = null,
start_time: TimeOfDay, start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
@@ -60,6 +61,7 @@ pub fn init(
}; };
return .{ return .{
.instance = null,
.start_time = try interop.getTimeOfDay(), .start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
@@ -73,8 +75,9 @@ pub fn init(
}; };
} }
pub fn widget(self: *Doom) Widget { pub fn widget(self: *Doom) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Doom", "Doom",
null, null,
self, self,
@@ -85,6 +88,7 @@ pub fn widget(self: *Doom) Widget {
null, null,
calculateTimeout, calculateTimeout,
); );
return &self.instance.?;
} }
fn deinit(self: *Doom) void { fn deinit(self: *Doom) void {

View File

@@ -304,6 +304,7 @@ const VEC_Y = 1;
const DurFile = @This(); const DurFile = @This();
instance: ?Widget = null,
start_time: TimeOfDay, start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
@@ -408,6 +409,7 @@ pub fn init(
const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?); const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?);
return .{ return .{
.instance = null,
.start_time = try interop.getTimeOfDay(), .start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
@@ -427,8 +429,9 @@ pub fn init(
}; };
} }
pub fn widget(self: *DurFile) Widget { pub fn widget(self: *DurFile) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"DurFile", "DurFile",
null, null,
self, self,
@@ -439,6 +442,7 @@ pub fn widget(self: *DurFile) Widget {
null, null,
calculateTimeout, calculateTimeout,
); );
return &self.instance.?;
} }
fn deinit(self: *DurFile) void { fn deinit(self: *DurFile) void {

View File

@@ -21,6 +21,7 @@ const NEIGHBOR_DIRS = [_][2]i8{
.{ 1, 0 }, .{ 1, 1 }, .{ 1, 0 }, .{ 1, 1 },
}; };
instance: ?Widget = null,
start_time: TimeOfDay, start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
@@ -58,6 +59,7 @@ pub fn init(
const next_grid = try allocator.alloc(bool, grid_size); const next_grid = try allocator.alloc(bool, grid_size);
var game = GameOfLife{ var game = GameOfLife{
.instance = null,
.start_time = try interop.getTimeOfDay(), .start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
@@ -83,8 +85,9 @@ pub fn init(
return game; return game;
} }
pub fn widget(self: *GameOfLife) Widget { pub fn widget(self: *GameOfLife) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"GameOfLife", "GameOfLife",
null, null,
self, self,
@@ -95,6 +98,7 @@ pub fn widget(self: *GameOfLife) Widget {
null, null,
calculateTimeout, calculateTimeout,
); );
return &self.instance.?;
} }
fn deinit(self: *GameOfLife) void { fn deinit(self: *GameOfLife) void {

View File

@@ -29,6 +29,7 @@ pub const Line = struct {
update: usize, update: usize,
}; };
instance: ?Widget = null,
start_time: TimeOfDay, start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
@@ -62,6 +63,7 @@ pub fn init(
initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random); initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random);
return .{ return .{
.instance = null,
.start_time = try interop.getTimeOfDay(), .start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
@@ -80,8 +82,9 @@ pub fn init(
}; };
} }
pub fn widget(self: *Matrix) Widget { pub fn widget(self: *Matrix) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Matrix", "Matrix",
null, null,
self, self,
@@ -92,6 +95,7 @@ pub fn widget(self: *Matrix) Widget {
null, null,
calculateTimeout, calculateTimeout,
); );
return &self.instance.?;
} }
fn deinit(self: *Matrix) void { fn deinit(self: *Matrix) void {

View File

@@ -18,6 +18,7 @@ const Message = struct {
fg: u32, fg: u32,
}; };
instance: ?Widget = null,
label: *MessageLabel, label: *MessageLabel,
pub fn init( pub fn init(
@@ -28,6 +29,7 @@ pub fn init(
arrow_bg: u32, arrow_bg: u32,
) !InfoLine { ) !InfoLine {
return .{ return .{
.instance = null,
.label = try MessageLabel.init( .label = try MessageLabel.init(
allocator, allocator,
buffer, buffer,
@@ -46,8 +48,9 @@ pub fn deinit(self: *InfoLine) void {
self.label.deinit(); self.label.deinit();
} }
pub fn widget(self: *InfoLine) Widget { pub fn widget(self: *InfoLine) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"InfoLine", "InfoLine",
self.label.keybinds, self.label.keybinds,
self, self,
@@ -58,6 +61,7 @@ pub fn widget(self: *InfoLine) Widget {
handle, handle,
null, null,
); );
return &self.instance.?;
} }
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void { pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void {

View File

@@ -18,6 +18,7 @@ const EnvironmentLabel = CyclableLabel(Env, *UserList);
const Session = @This(); const Session = @This();
instance: ?Widget = null,
label: *EnvironmentLabel, label: *EnvironmentLabel,
user_list: *UserList, user_list: *UserList,
@@ -31,6 +32,7 @@ pub fn init(
bg: u32, bg: u32,
) !Session { ) !Session {
return .{ return .{
.instance = null,
.label = try EnvironmentLabel.init( .label = try EnvironmentLabel.init(
allocator, allocator,
buffer, buffer,
@@ -55,8 +57,9 @@ pub fn deinit(self: *Session) void {
self.label.deinit(); self.label.deinit();
} }
pub fn widget(self: *Session) Widget { pub fn widget(self: *Session) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Session", "Session",
self.label.keybinds, self.label.keybinds,
self, self,
@@ -67,6 +70,7 @@ pub fn widget(self: *Session) Widget {
handle, handle,
null, null,
); );
return &self.instance.?;
} }
pub fn addEnvironment(self: *Session, environment: Environment) !void { pub fn addEnvironment(self: *Session, environment: Environment) !void {

View File

@@ -21,6 +21,7 @@ const UserLabel = CyclableLabel(User, *Session);
const UserList = @This(); const UserList = @This();
instance: ?Widget = null,
label: *UserLabel, label: *UserLabel,
pub fn init( pub fn init(
@@ -35,6 +36,7 @@ pub fn init(
bg: u32, bg: u32,
) !UserList { ) !UserList {
var user_list = UserList{ var user_list = UserList{
.instance = null,
.label = try UserLabel.init( .label = try UserLabel.init(
allocator, allocator,
buffer, buffer,
@@ -89,8 +91,9 @@ pub fn deinit(self: *UserList) void {
self.label.deinit(); self.label.deinit();
} }
pub fn widget(self: *UserList) Widget { pub fn widget(self: *UserList) *Widget {
return Widget.init( if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"UserList", "UserList",
self.label.keybinds, self.label.keybinds,
self, self,
@@ -101,6 +104,7 @@ pub fn widget(self: *UserList) Widget {
handle, handle,
null, null,
); );
return &self.instance.?;
} }
pub fn getCurrentUsername(self: UserList) []const u8 { pub fn getCurrentUsername(self: UserList) []const u8 {

View File

@@ -94,7 +94,7 @@ const UiState = struct {
saved_users: SavedUsers, saved_users: SavedUsers,
login: UserList, login: UserList,
password: *Text, password: *Text,
password_widget: Widget, password_widget: *Widget,
insert_mode: bool, insert_mode: bool,
edge_margin: Position, edge_margin: Position,
config: Config, config: Config,
@@ -910,7 +910,7 @@ pub fn main() !void {
} }
// Initialize the animation, if any // Initialize the animation, if any
var animation: ?Widget = null; var animation: ?*Widget = null;
switch (state.config.animation) { switch (state.config.animation) {
.none => {}, .none => {},
.doom => { .doom => {
@@ -985,7 +985,7 @@ pub fn main() !void {
animation = dur.widget(); animation = dur.widget();
}, },
} }
defer if (animation) |*a| a.deinit(); defer if (animation) |a| a.deinit();
var cascade = Cascade.init( var cascade = Cascade.init(
&state.buffer, &state.buffer,
@@ -1030,17 +1030,17 @@ pub fn main() !void {
const session_widget = state.session.widget(); const session_widget = state.session.widget();
const login_widget = state.login.widget(); const login_widget = state.login.widget();
var widgets: std.ArrayList([]Widget) = .empty; var widgets: std.ArrayList([]*Widget) = .empty;
defer widgets.deinit(state.allocator); defer widgets.deinit(state.allocator);
// Layer 1 // Layer 1
if (animation) |a| { if (animation) |a| {
var layer1 = [_]Widget{a}; var layer1 = [_]*Widget{a};
try widgets.append(state.allocator, &layer1); try widgets.append(state.allocator, &layer1);
} }
// Layer 2 // Layer 2
var layer2: std.ArrayList(Widget) = .empty; var layer2: std.ArrayList(*Widget) = .empty;
defer layer2.deinit(state.allocator); defer layer2.deinit(state.allocator);
if (!state.config.hide_key_hints) { if (!state.config.hide_key_hints) {
@@ -1089,7 +1089,7 @@ pub fn main() !void {
// Layer 3 // Layer 3
if (state.config.auth_fails > 0) { if (state.config.auth_fails > 0) {
var layer3 = [_]Widget{cascade.widget()}; var layer3 = [_]*Widget{cascade.widget()};
try widgets.append(state.allocator, &layer3); try widgets.append(state.allocator, &layer3);
} }