diff --git a/src/main.zig b/src/main.zig index 832b3af..71a410b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -66,6 +66,8 @@ fn ttyControlTransferSignalHandler(_: c_int) callconv(.c) void { const UiState = struct { allocator: Allocator, + active_widget_index: usize, + handlable_widgets: std.ArrayList(*Widget), auth_fails: u64, run: bool, update: bool, @@ -97,7 +99,7 @@ const UiState = struct { saved_users: SavedUsers, login: UserList, password: Text, - active_input: enums.Input, + password_widget: Widget, insert_mode: bool, edge_margin: Position, config: Config, @@ -780,6 +782,8 @@ pub fn main() !void { ); defer state.password.deinit(); + state.password_widget = state.password.widget(); + state.version_label = Label.init( ly_version_str, null, @@ -941,12 +945,12 @@ pub fn main() !void { state.config.auth_fails, ); + state.active_widget_index = 0; state.auth_fails = 0; state.run = true; state.update = true; state.animation_timed_out = false; state.animate = state.config.animation != .none; - state.active_input = state.config.default_input; state.insert_mode = !state.config.vi_mode or state.config.vi_default_mode == .insert; state.edge_margin = Position.init( state.config.edge_margin, @@ -955,6 +959,8 @@ pub fn main() !void { // Load last saved username and desktop selection, if any // Skip if autologin is active to prevent overriding autologin session + var default_input = state.config.default_input; + if (state.config.save and !state.is_autologin) { if (state.saved_users.last_username_index) |index| load_last_user: { // If the saved index isn't valid, bail out @@ -971,7 +977,7 @@ pub fn main() !void { } } - state.active_input = .password; + default_input = .password; state.session.label.current = @min(user.session_index, state.session.label.list.items.len - 1); } @@ -979,6 +985,10 @@ 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) + const info_line_widget = state.info_line.widget(); + const session_widget = state.session.widget(); + const login_widget = state.login.widget(); + var widgets: std.ArrayList(Widget) = .empty; defer widgets.deinit(state.allocator); @@ -1012,13 +1022,13 @@ pub fn main() !void { try widgets.append(state.allocator, state.capslock_label.widget()); } try widgets.append(state.allocator, state.box.widget()); - try widgets.append(state.allocator, state.info_line.widget()); + try widgets.append(state.allocator, info_line_widget); try widgets.append(state.allocator, state.session_specifier_label.widget()); - try widgets.append(state.allocator, state.session.widget()); + try widgets.append(state.allocator, session_widget); try widgets.append(state.allocator, state.login_label.widget()); - try widgets.append(state.allocator, state.login.widget()); + try widgets.append(state.allocator, login_widget); try widgets.append(state.allocator, state.password_label.widget()); - try widgets.append(state.allocator, state.password.widget()); + try widgets.append(state.allocator, state.password_widget); if (!state.config.hide_version_string) { try widgets.append(state.allocator, state.version_label.widget()); } @@ -1026,28 +1036,6 @@ pub fn main() !void { try widgets.append(state.allocator, cascade.widget()); } - // Position components and place cursor accordingly - for (widgets.items) |*widget| try widget.update(&state); - positionComponents(&state); - - switch (state.active_input) { - .info_line => state.info_line.label.handle(null, state.insert_mode), - .session => state.session.label.handle(null, state.insert_mode), - .login => state.login.label.handle(null, state.insert_mode), - .password => state.password.handle(null, state.insert_mode) 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 handle password input: {s}", - .{@errorName(err)}, - ); - }, - } - try state.buffer.registerKeybind("Esc", &disableInsertMode); try state.buffer.registerKeybind("I", &enableInsertMode); @@ -1057,11 +1045,11 @@ pub fn main() !void { try state.buffer.registerKeybind("Ctrl+K", &moveCursorUp); try state.buffer.registerKeybind("Up", &moveCursorUp); - try state.buffer.registerKeybind("J", &viMoseCursorUp); + try state.buffer.registerKeybind("K", &viMoveCursorUp); try state.buffer.registerKeybind("Ctrl+J", &moveCursorDown); try state.buffer.registerKeybind("Down", &moveCursorDown); - try state.buffer.registerKeybind("K", &viMoveCursorDown); + try state.buffer.registerKeybind("J", &viMoseCursorDown); try state.buffer.registerKeybind("Tab", &wrapCursor); try state.buffer.registerKeybind("Shift+Tab", &wrapCursorReverse); @@ -1104,21 +1092,51 @@ pub fn main() !void { ); } + // Position components and place cursor accordingly if (state.is_autologin) _ = try authenticate(&state); + const active_widget = switch (default_input) { + .info_line => info_line_widget, + .session => session_widget, + .login => login_widget, + .password => state.password_widget, + }; + + // Run the event loop + state.handlable_widgets = .empty; + defer state.handlable_widgets.deinit(state.allocator); + + var i: usize = 0; + for (widgets.items) |*widget| { + if (widget.vtable.handle_fn != null) { + try state.handlable_widgets.append(state.allocator, widget); + + if (widget.id == active_widget.id) state.active_widget_index = i; + i += 1; + } + } + + for (widgets.items) |*widget| try widget.update(&state); + positionComponents(&state); + while (state.run) { if (state.update) { for (widgets.items) |*widget| try widget.update(&state); - switch (state.active_input) { - .info_line => state.info_line.label.handle(null, state.insert_mode), - .session => state.session.label.handle(null, state.insert_mode), - .login => state.login.label.handle(null, state.insert_mode), - .password => state.password.handle(null, state.insert_mode) 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 handle password input: {s}", .{@errorName(err)}); - }, - } + // Reset cursor + const current_widget = getActiveWidget(&state); + current_widget.handle(null, state.insert_mode) 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 set cursor in active widget: {s}", + .{@errorName(err)}, + ); + }; try TerminalBuffer.clearScreen(false); @@ -1237,19 +1255,20 @@ pub fn main() !void { if (maybe_keys) |*keys| { defer keys.deinit(state.allocator); + const current_widget = getActiveWidget(&state); for (keys.items) |key| { - switch (state.active_input) { - .info_line => state.info_line.label.handle(key, state.insert_mode), - .session => state.session.label.handle(key, state.insert_mode), - .login => state.login.label.handle(key, state.insert_mode), - .password => state.password.handle(key, state.insert_mode) catch { - try state.info_line.addMessage( - state.lang.err_alloc, - state.config.error_bg, - state.config.error_fg, - ); - }, - } + current_widget.handle(key, state.insert_mode) 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 handle active widget: {s}", + .{@errorName(err)}, + ); + }; } state.update = true; @@ -1257,6 +1276,16 @@ pub fn main() !void { } } +fn getActiveWidget(state: *UiState) *Widget { + return state.handlable_widgets.items[state.active_widget_index]; +} + +fn setActiveWidget(state: *UiState, widget: *Widget) void { + for (state.handlable_widgets.items, 0..) |widg, i| { + if (widg.id == widget.id) state.active_widget_index = i; + } +} + fn disableInsertMode(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); @@ -1279,7 +1308,7 @@ fn enableInsertMode(ptr: *anyopaque) !bool { fn clearPassword(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); - if (state.active_input == .password) { + if (getActiveWidget(state) == &state.password_widget) { state.password.clear(); state.update = true; } @@ -1288,34 +1317,38 @@ fn clearPassword(ptr: *anyopaque) !bool { fn moveCursorUp(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); + if (state.active_widget_index == 0) return false; - state.active_input.move(true, false); + state.active_widget_index -= 1; state.update = true; return false; } -fn viMoseCursorUp(ptr: *anyopaque) !bool { +fn viMoveCursorUp(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); if (state.insert_mode) return true; + if (state.active_widget_index == 0) return false; - state.active_input.move(false, false); + state.active_widget_index -= 1; state.update = true; return false; } fn moveCursorDown(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); + if (state.active_widget_index == state.handlable_widgets.items.len - 1) return false; - state.active_input.move(false, false); + state.active_widget_index += 1; state.update = true; return false; } -fn viMoveCursorDown(ptr: *anyopaque) !bool { +fn viMoseCursorDown(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); if (state.insert_mode) return true; + if (state.active_widget_index == state.handlable_widgets.items.len - 1) return false; - state.active_input.move(true, false); + state.active_widget_index += 1; state.update = true; return false; } @@ -1323,7 +1356,7 @@ fn viMoveCursorDown(ptr: *anyopaque) !bool { fn wrapCursor(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); - state.active_input.move(false, true); + state.active_widget_index = (state.active_widget_index + 1) % state.handlable_widgets.items.len; state.update = true; return false; } @@ -1331,7 +1364,7 @@ fn wrapCursor(ptr: *anyopaque) !bool { fn wrapCursorReverse(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); - state.active_input.move(true, true); + state.active_widget_index = (state.active_widget_index - 1) % state.handlable_widgets.items.len; state.update = true; return false; } @@ -1500,7 +1533,7 @@ fn authenticate(ptr: *anyopaque) !bool { const auth_err = shared_err.readError(); if (auth_err) |err| { state.auth_fails += 1; - state.active_input = .password; + setActiveWidget(state, &state.password_widget); try state.info_line.addMessage( getAuthErrorMsg(err, state.lang), diff --git a/src/tui/Widget.zig b/src/tui/Widget.zig index 6ffb0b3..80413a7 100644 --- a/src/tui/Widget.zig +++ b/src/tui/Widget.zig @@ -4,13 +4,14 @@ const keyboard = @import("keyboard.zig"); const TerminalBuffer = @import("TerminalBuffer.zig"); const VTable = struct { - deinit_fn: *const fn (ptr: *anyopaque) void, - realloc_fn: *const fn (ptr: *anyopaque) anyerror!void, + deinit_fn: ?*const fn (ptr: *anyopaque) void, + realloc_fn: ?*const fn (ptr: *anyopaque) anyerror!void, 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, + 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, }; +id: u64, pointer: *anyopaque, vtable: VTable, @@ -18,7 +19,7 @@ pub fn init( pointer: anytype, comptime deinit_fn: ?fn (ptr: @TypeOf(pointer)) void, comptime realloc_fn: ?fn (ptr: @TypeOf(pointer)) anyerror!void, - comptime draw_fn: ?fn (ptr: @TypeOf(pointer)) void, + 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, ) Widget { @@ -27,73 +28,64 @@ pub fn init( pub fn deinitImpl(ptr: *anyopaque) void { const impl: Pointer = @ptrCast(@alignCast(ptr)); - if (deinit_fn) |func| { - return @call( - .always_inline, - func, - .{impl}, - ); - } + return @call( + .always_inline, + deinit_fn.?, + .{impl}, + ); } pub fn reallocImpl(ptr: *anyopaque) !void { const impl: Pointer = @ptrCast(@alignCast(ptr)); - if (realloc_fn) |func| { - return @call( - .always_inline, - func, - .{impl}, - ); - } + return @call( + .always_inline, + realloc_fn.?, + .{impl}, + ); } pub fn drawImpl(ptr: *anyopaque) void { const impl: Pointer = @ptrCast(@alignCast(ptr)); - if (draw_fn) |func| { - return @call( - .always_inline, - func, - .{impl}, - ); - } + return @call( + .always_inline, + draw_fn, + .{impl}, + ); } pub fn updateImpl(ptr: *anyopaque, ctx: *anyopaque) !void { const impl: Pointer = @ptrCast(@alignCast(ptr)); - if (update_fn) |func| { - return @call( - .always_inline, - func, - .{ impl, ctx }, - ); - } + return @call( + .always_inline, + update_fn.?, + .{ impl, ctx }, + ); } pub fn handleImpl(ptr: *anyopaque, maybe_key: ?keyboard.Key, insert_mode: bool) !void { const impl: Pointer = @ptrCast(@alignCast(ptr)); - if (handle_fn) |func| { - return @call( - .always_inline, - func, - .{ impl, maybe_key, insert_mode }, - ); - } + return @call( + .always_inline, + handle_fn.?, + .{ impl, maybe_key, insert_mode }, + ); } const vtable = VTable{ - .deinit_fn = deinitImpl, - .realloc_fn = reallocImpl, + .deinit_fn = if (deinit_fn != null) deinitImpl else null, + .realloc_fn = if (realloc_fn != null) reallocImpl else null, .draw_fn = drawImpl, - .update_fn = updateImpl, - .handle_fn = handleImpl, + .update_fn = if (update_fn != null) updateImpl else null, + .handle_fn = if (handle_fn != null) handleImpl else null, }; }; return .{ + .id = @intFromPtr(Impl.vtable.draw_fn), .pointer = pointer, .vtable = Impl.vtable, }; @@ -102,27 +94,31 @@ pub fn init( pub fn deinit(self: *Widget) void { const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer)); - return @call( - .auto, - self.vtable.deinit_fn, - .{impl}, - ); + if (self.vtable.deinit_fn) |deinit_fn| { + return @call( + .auto, + deinit_fn, + .{impl}, + ); + } } pub fn realloc(self: *Widget) !void { const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer)); - return @call( - .auto, - self.vtable.realloc_fn, - .{impl}, - ); + if (self.vtable.realloc_fn) |realloc_fn| { + return @call( + .auto, + realloc_fn, + .{impl}, + ); + } } pub fn draw(self: *Widget) void { const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer)); - return @call( + @call( .auto, self.vtable.draw_fn, .{impl}, @@ -132,19 +128,23 @@ pub fn draw(self: *Widget) void { pub fn update(self: *Widget, ctx: *anyopaque) !void { const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer)); - return @call( - .auto, - self.vtable.update_fn, - .{ impl, ctx }, - ); + if (self.vtable.update_fn) |update_fn| { + return @call( + .auto, + update_fn, + .{ impl, ctx }, + ); + } } pub fn handle(self: *Widget, maybe_key: ?keyboard.Key, insert_mode: bool) !void { const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer)); - return @call( - .auto, - self.vtable.handle_fn, - .{ impl, maybe_key, insert_mode }, - ); + if (self.vtable.handle_fn) |handle_fn| { + return @call( + .auto, + handle_fn, + .{ impl, maybe_key, insert_mode }, + ); + } }