diff --git a/ly-ui/src/TerminalBuffer.zig b/ly-ui/src/TerminalBuffer.zig index 28e03f0..66b915f 100644 --- a/ly-ui/src/TerminalBuffer.zig +++ b/ly-ui/src/TerminalBuffer.zig @@ -172,7 +172,6 @@ pub fn runEventLoop( layers: [][]Widget, active_widget: Widget, inactivity_delay: u16, - insert_mode: *bool, position_widgets_fn: *const fn (*anyopaque) anyerror!void, inactivity_event_fn: ?*const fn (*anyopaque) anyerror!void, context: *anyopaque, @@ -221,7 +220,7 @@ pub fn runEventLoop( // Reset cursor const current_widget = self.getActiveWidget(); - current_widget.handle(null, insert_mode.*) catch |err| { + current_widget.handle(null) catch |err| { shared_error.writeError(error.SetCursorFailed); try self.log_file.err( "tui", @@ -303,7 +302,7 @@ pub fn runEventLoop( const current_widget = self.getActiveWidget(); for (keys.items) |key| { - current_widget.handle(key, insert_mode.*) catch |err| { + current_widget.handle(key) catch |err| { shared_error.writeError(error.CurrentWidgetHandlingFailed); try self.log_file.err( "tui", @@ -441,6 +440,17 @@ pub fn simulateKeybind(self: *TerminalBuffer, keybind: []const u8) !bool { ); } + const current_widget = self.getActiveWidget(); + if (current_widget.keybinds) |keybinds| { + if (keybinds.get(key)) |binding| { + return try @call( + .auto, + binding.callback, + .{binding.context}, + ); + } + } + return true; } diff --git a/ly-ui/src/Widget.zig b/ly-ui/src/Widget.zig index 5c613d8..d66fce8 100644 --- a/ly-ui/src/Widget.zig +++ b/ly-ui/src/Widget.zig @@ -8,7 +8,7 @@ const VTable = struct { 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, + handle_fn: ?*const fn (ptr: *anyopaque, maybe_key: ?keyboard.Key) anyerror!void, calculate_timeout_fn: ?*const fn (ptr: *anyopaque, ctx: *anyopaque) anyerror!?usize, }; @@ -26,7 +26,7 @@ pub fn init( comptime realloc_fn: ?fn (ptr: @TypeOf(pointer)) anyerror!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, + comptime handle_fn: ?fn (ptr: @TypeOf(pointer), maybe_key: ?keyboard.Key) anyerror!void, comptime calculate_timeout_fn: ?fn (ptr: @TypeOf(pointer), ctx: *anyopaque) anyerror!?usize, ) Widget { const Pointer = @TypeOf(pointer); @@ -71,13 +71,13 @@ pub fn init( ); } - pub fn handleImpl(ptr: *anyopaque, maybe_key: ?keyboard.Key, insert_mode: bool) !void { + pub fn handleImpl(ptr: *anyopaque, maybe_key: ?keyboard.Key) !void { const impl: Pointer = @ptrCast(@alignCast(ptr)); return @call( .always_inline, handle_fn.?, - .{ impl, maybe_key, insert_mode }, + .{ impl, maybe_key }, ); } @@ -156,14 +156,14 @@ pub fn update(self: *Widget, ctx: *anyopaque) !void { } } -pub fn handle(self: *Widget, maybe_key: ?keyboard.Key, insert_mode: bool) !void { +pub fn handle(self: *Widget, maybe_key: ?keyboard.Key) !void { const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer)); if (self.vtable.handle_fn) |handle_fn| { return @call( .auto, handle_fn, - .{ impl, maybe_key, insert_mode }, + .{ impl, maybe_key }, ); } } diff --git a/ly-ui/src/components/CenteredBox.zig b/ly-ui/src/components/CenteredBox.zig index fe10880..934a83c 100644 --- a/ly-ui/src/components/CenteredBox.zig +++ b/ly-ui/src/components/CenteredBox.zig @@ -67,7 +67,7 @@ pub fn widget(self: *CenteredBox) Widget { null, null, draw, - null, + update, null, null, ); diff --git a/ly-ui/src/components/Text.zig b/ly-ui/src/components/Text.zig index ab139c8..0ad2128 100644 --- a/ly-ui/src/components/Text.zig +++ b/ly-ui/src/components/Text.zig @@ -19,6 +19,7 @@ visible_start: usize, width: usize, component_pos: Position, children_pos: Position, +should_insert: bool, masked: bool, maybe_mask: ?u32, fg: u32, @@ -28,6 +29,7 @@ keybinds: TerminalBuffer.KeybindMap, pub fn init( allocator: Allocator, buffer: *TerminalBuffer, + should_insert: bool, masked: bool, maybe_mask: ?u32, width: usize, @@ -45,6 +47,7 @@ pub fn init( .width = width, .component_pos = TerminalBuffer.START_POSITION, .children_pos = TerminalBuffer.START_POSITION, + .should_insert = should_insert, .masked = masked, .maybe_mask = maybe_mask, .fg = fg, @@ -52,6 +55,10 @@ pub fn init( .keybinds = .init(allocator), }; + try buffer.registerKeybind(&self.keybinds, "Left", &goLeft, self); + try buffer.registerKeybind(&self.keybinds, "Right", &goRight, self); + try buffer.registerKeybind(&self.keybinds, "Delete", &delete, self); + try buffer.registerKeybind(&self.keybinds, "Backspace", &backspace, self); try buffer.registerKeybind(&self.keybinds, "Ctrl+U", &clearTextEntry, self); return self; @@ -110,17 +117,9 @@ pub fn toggleMask(self: *Text) void { self.masked = !self.masked; } -pub fn handle(self: *Text, maybe_key: ?keyboard.Key, insert_mode: bool) !void { +pub fn handle(self: *Text, maybe_key: ?keyboard.Key) !void { if (maybe_key) |key| { - if (key.left or (!insert_mode and (key.h or key.backspace))) { - self.goLeft(); - } else if (key.right or (!insert_mode and key.l)) { - self.goRight(); - } else if (key.delete) { - self.delete(); - } else if (key.backspace) { - self.backspace(); - } else if (insert_mode) { + if (self.should_insert) { const maybe_character = key.getEnabledPrintableAscii(); if (maybe_character) |character| try self.write(character); } @@ -181,33 +180,45 @@ fn draw(self: *Text) void { ); } -fn goLeft(self: *Text) void { - if (self.cursor == 0) return; +fn goLeft(ptr: *anyopaque) !bool { + var self: *Text = @ptrCast(@alignCast(ptr)); + + if (self.cursor == 0) return false; if (self.visible_start > 0) self.visible_start -= 1; self.cursor -= 1; + return false; } -fn goRight(self: *Text) void { - if (self.cursor >= self.end) return; +fn goRight(ptr: *anyopaque) !bool { + var self: *Text = @ptrCast(@alignCast(ptr)); + + if (self.cursor >= self.end) return false; if (self.cursor - self.visible_start == self.width - 1) self.visible_start += 1; self.cursor += 1; + return false; } -fn delete(self: *Text) void { - if (self.cursor >= self.end) return; +fn delete(ptr: *anyopaque) !bool { + var self: *Text = @ptrCast(@alignCast(ptr)); + + if (self.cursor >= self.end or !self.should_insert) return false; _ = self.text.orderedRemove(self.cursor); self.end -= 1; + return false; } -fn backspace(self: *Text) void { - if (self.cursor == 0) return; +fn backspace(ptr: *anyopaque) !bool { + const self: *Text = @ptrCast(@alignCast(ptr)); - self.goLeft(); - self.delete(); + if (self.cursor == 0 or !self.should_insert) return false; + + _ = try goLeft(ptr); + _ = try delete(ptr); + return false; } fn write(self: *Text, char: u8) !void { @@ -216,12 +227,14 @@ fn write(self: *Text, char: u8) !void { try self.text.insert(self.allocator, self.cursor, char); self.end += 1; - self.goRight(); + _ = try goRight(self); } fn clearTextEntry(ptr: *anyopaque) !bool { var self: *Text = @ptrCast(@alignCast(ptr)); + if (!self.should_insert) return false; + self.clear(); self.buffer.drawNextFrame(true); return false; diff --git a/ly-ui/src/components/generic.zig b/ly-ui/src/components/generic.zig index 4f9c9e7..d7a609d 100644 --- a/ly-ui/src/components/generic.zig +++ b/ly-ui/src/components/generic.zig @@ -28,6 +28,7 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ draw_item_fn: DrawItemFn, change_item_fn: ?ChangeItemFn, change_item_arg: ?ChangeItemType, + keybinds: TerminalBuffer.KeybindMap, pub fn init( allocator: Allocator, @@ -39,8 +40,9 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ text_in_center: bool, fg: u32, bg: u32, - ) Self { - return .{ + ) !*Self { + var self = try allocator.create(Self); + self.* = .{ .allocator = allocator, .buffer = buffer, .list = .empty, @@ -55,11 +57,21 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ .draw_item_fn = draw_item_fn, .change_item_fn = change_item_fn, .change_item_arg = change_item_arg, + .keybinds = .init(allocator), }; + + try buffer.registerKeybind(&self.keybinds, "Left", &goLeft, self); + try buffer.registerKeybind(&self.keybinds, "Ctrl+H", &goLeft, self); + try buffer.registerKeybind(&self.keybinds, "Right", &goRight, self); + try buffer.registerKeybind(&self.keybinds, "Ctrl+L", &goRight, self); + + return self; } pub fn deinit(self: *Self) void { self.list.deinit(self.allocator); + self.keybinds.deinit(); + self.allocator.destroy(self); } pub fn positionX(self: *Self, original_pos: Position) void { @@ -92,15 +104,7 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ self.current = self.list.items.len - 1; } - pub fn handle(self: *Self, maybe_key: ?keyboard.Key, insert_mode: bool) void { - if (maybe_key) |key| { - if (key.left or (key.ctrl and key.h) or (!insert_mode and key.h)) { - self.goLeft(); - } else if (key.right or (key.ctrl and key.l) or (!insert_mode and key.l)) { - self.goRight(); - } - } - + pub fn handle(self: *Self, _: ?keyboard.Key) void { TerminalBuffer.setCursor( self.component_pos.x + self.cursor + 2, self.component_pos.y, @@ -132,7 +136,9 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ ); } - fn goLeft(self: *Self) void { + fn goLeft(ptr: *anyopaque) !bool { + var self: *Self = @ptrCast(@alignCast(ptr)); + self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1; if (self.change_item_fn) |change_item_fn| { @@ -142,9 +148,13 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ .{ self.list.items[self.current], self.change_item_arg }, ); } + + return false; } - fn goRight(self: *Self) void { + fn goRight(ptr: *anyopaque) !bool { + var self: *Self = @ptrCast(@alignCast(ptr)); + self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1; if (self.change_item_fn) |change_item_fn| { @@ -154,6 +164,8 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ .{ self.list.items[self.current], self.change_item_arg }, ); } + + return false; } }; } diff --git a/src/components/InfoLine.zig b/src/components/InfoLine.zig index 161639b..7e81cf3 100644 --- a/src/components/InfoLine.zig +++ b/src/components/InfoLine.zig @@ -18,7 +18,7 @@ const Message = struct { fg: u32, }; -label: MessageLabel, +label: *MessageLabel, pub fn init( allocator: Allocator, @@ -26,9 +26,9 @@ pub fn init( width: usize, arrow_fg: u32, arrow_bg: u32, -) InfoLine { +) !InfoLine { return .{ - .label = MessageLabel.init( + .label = try MessageLabel.init( allocator, buffer, drawItem, @@ -49,7 +49,7 @@ pub fn deinit(self: *InfoLine) void { pub fn widget(self: *InfoLine) Widget { return Widget.init( "InfoLine", - null, + self.label.keybinds, self, deinit, null, @@ -91,8 +91,8 @@ 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 handle(self: *InfoLine, maybe_key: ?keyboard.Key) !void { + self.label.handle(maybe_key); } fn drawItem(label: *MessageLabel, message: Message, x: usize, y: usize, width: usize) void { diff --git a/src/components/Session.zig b/src/components/Session.zig index 726de81..373145e 100644 --- a/src/components/Session.zig +++ b/src/components/Session.zig @@ -18,7 +18,7 @@ const EnvironmentLabel = CyclableLabel(Env, *UserList); const Session = @This(); -label: EnvironmentLabel, +label: *EnvironmentLabel, user_list: *UserList, pub fn init( @@ -29,9 +29,9 @@ pub fn init( text_in_center: bool, fg: u32, bg: u32, -) Session { +) !Session { return .{ - .label = EnvironmentLabel.init( + .label = try EnvironmentLabel.init( allocator, buffer, drawItem, @@ -58,7 +58,7 @@ pub fn deinit(self: *Session) void { pub fn widget(self: *Session) Widget { return Widget.init( "Session", - null, + self.label.keybinds, self, deinit, null, @@ -80,8 +80,8 @@ 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 handle(self: *Session, maybe_key: ?keyboard.Key) !void { + self.label.handle(maybe_key); } fn addedSession(env: Env, user_list: *UserList) void { diff --git a/src/components/UserList.zig b/src/components/UserList.zig index d872b67..5747ed7 100644 --- a/src/components/UserList.zig +++ b/src/components/UserList.zig @@ -21,7 +21,7 @@ const UserLabel = CyclableLabel(User, *Session); const UserList = @This(); -label: UserLabel, +label: *UserLabel, pub fn init( allocator: Allocator, @@ -35,7 +35,7 @@ pub fn init( bg: u32, ) !UserList { var user_list = UserList{ - .label = UserLabel.init( + .label = try UserLabel.init( allocator, buffer, drawItem, @@ -92,7 +92,7 @@ pub fn deinit(self: *UserList) void { pub fn widget(self: *UserList) Widget { return Widget.init( "UserList", - null, + self.label.keybinds, self, deinit, null, @@ -111,8 +111,8 @@ fn draw(self: *UserList) void { self.label.draw(); } -fn handle(self: *UserList, maybe_key: ?keyboard.Key, insert_mode: bool) !void { - self.label.handle(maybe_key, insert_mode); +fn handle(self: *UserList, maybe_key: ?keyboard.Key) !void { + self.label.handle(maybe_key); } fn usernameChanged(user: User, maybe_session: ?*Session) void { diff --git a/src/main.zig b/src/main.zig index b715ff4..9d29599 100644 --- a/src/main.zig +++ b/src/main.zig @@ -540,7 +540,7 @@ pub fn main() !void { &updateBox, ); - state.info_line = InfoLine.init( + state.info_line = try InfoLine.init( state.allocator, &state.buffer, state.box.width - 2 * state.box.horizontal_margin, @@ -549,6 +549,9 @@ pub fn main() !void { ); defer state.info_line.deinit(); + try state.buffer.registerKeybind(&state.info_line.label.keybinds, "H", &viGoLeft, &state); + try state.buffer.registerKeybind(&state.info_line.label.keybinds, "L", &viGoRight, &state); + if (maybe_res == null) { var longest = diag.name.longest(); if (longest.kind == .positional) @@ -650,7 +653,7 @@ pub fn main() !void { ); defer state.session_specifier_label.deinit(); - state.session = Session.init( + state.session = try Session.init( state.allocator, &state.buffer, &state.login, @@ -661,6 +664,9 @@ pub fn main() !void { ); defer state.session.deinit(); + try state.buffer.registerKeybind(&state.session.label.keybinds, "H", &viGoLeft, &state); + try state.buffer.registerKeybind(&state.session.label.keybinds, "L", &viGoRight, &state); + state.login_label = Label.init( state.lang.login, null, @@ -684,6 +690,9 @@ pub fn main() !void { ); defer state.login.deinit(); + try state.buffer.registerKeybind(&state.login.label.keybinds, "H", &viGoLeft, &state); + try state.buffer.registerKeybind(&state.login.label.keybinds, "L", &viGoRight, &state); + addOtherEnvironment(&state.session, state.lang, .shell, null) catch |err| { try state.info_line.addMessage( state.lang.err_alloc, @@ -792,9 +801,12 @@ pub fn main() !void { ); defer state.password_label.deinit(); + state.insert_mode = !state.config.vi_mode or state.config.vi_default_mode == .insert; + state.password = try Text.init( state.allocator, &state.buffer, + state.insert_mode, true, state.config.asterisk, state.box.width - 2 * state.box.horizontal_margin - state.labels_max_length - 1, @@ -803,6 +815,9 @@ pub fn main() !void { ); defer state.password.deinit(); + try state.buffer.registerKeybind(&state.password.keybinds, "H", &viGoLeft, &state); + try state.buffer.registerKeybind(&state.password.keybinds, "L", &viGoRight, &state); + state.password_widget = state.password.widget(); state.version_label = Label.init( @@ -979,7 +994,6 @@ pub fn main() !void { state.auth_fails = 0; state.animate = state.config.animation != .none; - state.insert_mode = !state.config.vi_mode or state.config.vi_default_mode == .insert; state.edge_margin = Position.init( state.config.edge_margin, state.config.edge_margin, @@ -1139,7 +1153,6 @@ pub fn main() !void { widgets.items, active_widget, state.config.inactivity_delay, - &state.insert_mode, // FIXME: Hack positionWidgets, handleInactivity, &state, @@ -1180,6 +1193,7 @@ fn disableInsertMode(ptr: *anyopaque) !bool { if (state.config.vi_mode and state.insert_mode) { state.insert_mode = false; + state.password.should_insert = false; state.buffer.drawNextFrame(true); } return false; @@ -1190,10 +1204,25 @@ fn enableInsertMode(ptr: *anyopaque) !bool { if (state.insert_mode) return true; state.insert_mode = true; + state.password.should_insert = true; state.buffer.drawNextFrame(true); return false; } +fn viGoLeft(ptr: *anyopaque) !bool { + var self: *UiState = @ptrCast(@alignCast(ptr)); + if (self.insert_mode) return true; + + return try self.buffer.simulateKeybind("Left"); +} + +fn viGoRight(ptr: *anyopaque) !bool { + var state: *UiState = @ptrCast(@alignCast(ptr)); + if (state.insert_mode) return true; + + return try state.buffer.simulateKeybind("Right"); +} + fn viMoveCursorUp(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); if (state.insert_mode) return true;