feat: make filelist_view fully mouse and keyboard scrollable

This commit is contained in:
CJ van den Berg 2024-08-11 20:28:38 +02:00
parent ef95114039
commit 3a7e124255

View file

@ -10,6 +10,8 @@ const ArrayList = @import("std").ArrayList;
const Plane = @import("renderer").Plane; const Plane = @import("renderer").Plane;
const tp = @import("thespian"); const tp = @import("thespian");
const log = @import("log"); const log = @import("log");
const key = @import("renderer").input.key;
const event_type = @import("renderer").input.event_type;
const command = @import("command.zig"); const command = @import("command.zig");
const tui = @import("tui.zig"); const tui = @import("tui.zig");
@ -36,6 +38,7 @@ items: usize = 0,
view_pos: usize = 0, view_pos: usize = 0,
view_rows: usize = 0, view_rows: usize = 0,
entries: std.ArrayList(Entry) = undefined, entries: std.ArrayList(Entry) = undefined,
selected: ?usize = null,
const Entry = struct { const Entry = struct {
path: []const u8, path: []const u8,
@ -57,6 +60,8 @@ pub fn create(allocator: Allocator, parent: Plane) !Widget {
.ctx = self, .ctx = self,
.on_render = handle_render_menu, .on_render = handle_render_menu,
.on_scroll = EventHandler.bind(self, Self.handle_scroll), .on_scroll = EventHandler.bind(self, Self.handle_scroll),
.on_click4 = mouse_click_button4,
.on_click5 = mouse_click_button5,
}), }),
}; };
try self.commands.init(self); try self.commands.init(self);
@ -72,13 +77,13 @@ pub fn deinit(self: *Self, a: Allocator) void {
pub fn handle_resize(self: *Self, pos: Widget.Box) void { pub fn handle_resize(self: *Self, pos: Widget.Box) void {
self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return; self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return;
self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return; self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return;
self.menu.resize(pos); self.menu.container_widget.resize(pos);
self.view_rows = pos.h; self.view_rows = pos.h;
self.update_scrollbar(); self.update_scrollbar();
} }
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool { pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool {
return self.menu.walk(walk_ctx, f) or f(walk_ctx, w); return self.menu.container_widget.walk(walk_ctx, f) or f(walk_ctx, w);
} }
pub fn add_item(self: *Self, entry_: Entry) !void { pub fn add_item(self: *Self, entry_: Entry) !void {
@ -92,7 +97,7 @@ pub fn add_item(self: *Self, entry_: Entry) !void {
const writer = label.writer(); const writer = label.writer();
cbor.writeValue(writer, idx) catch return; cbor.writeValue(writer, idx) catch return;
self.menu.add_item_with_handler(label.items, handle_menu_action) catch return; self.menu.add_item_with_handler(label.items, handle_menu_action) catch return;
self.menu.resize(Widget.Box.from(self.plane)); self.menu.container_widget.resize(Widget.Box.from(self.plane));
self.update_scrollbar(); self.update_scrollbar();
} }
@ -103,13 +108,15 @@ pub fn reset(self: *Self) void {
} }
self.entries.clearRetainingCapacity(); self.entries.clearRetainingCapacity();
self.menu.reset_items(); self.menu.reset_items();
self.selected = null;
self.menu.selected = null;
} }
pub fn render(self: *Self, theme: *const Widget.Theme) bool { pub fn render(self: *Self, theme: *const Widget.Theme) bool {
self.plane.set_base_style(" ", theme.panel); self.plane.set_base_style(" ", theme.panel);
self.plane.erase(); self.plane.erase();
self.plane.home(); self.plane.home();
return self.menu.render(theme); return self.menu.container_widget.render(theme);
} }
fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool { fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
@ -126,6 +133,7 @@ fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), th
self.logger.print_err(name, "invalid table entry: {s}", .{json}); self.logger.print_err(name, "invalid table entry: {s}", .{json});
return false; return false;
} }
idx += self.view_pos;
if (idx >= self.entries.items.len) { if (idx >= self.entries.items.len) {
return false; return false;
} }
@ -147,12 +155,43 @@ fn render_cell(plane: *Plane, y: usize, x: usize, style: Widget.Theme.Style) !vo
fn handle_scroll(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!void { fn handle_scroll(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!void {
_ = try m.match(.{ "scroll_to", tp.extract(&self.view_pos) }); _ = try m.match(.{ "scroll_to", tp.extract(&self.view_pos) });
self.update_selected();
} }
fn update_scrollbar(self: *Self) void { fn update_scrollbar(self: *Self) void {
self.menu.scrollbar.?.set(@intCast(self.entries.items.len), @intCast(self.view_rows), @intCast(self.view_pos)); self.menu.scrollbar.?.set(@intCast(self.entries.items.len), @intCast(self.view_rows), @intCast(self.view_pos));
} }
fn mouse_click_button4(menu: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
const self = &menu.*.opts.ctx.*;
self.selected = if (self.menu.selected) |sel_| sel_ + self.view_pos else self.selected;
if (self.view_pos < Menu.scroll_lines) {
self.view_pos = 0;
} else {
self.view_pos -= Menu.scroll_lines;
}
self.update_selected();
self.update_scrollbar();
}
fn mouse_click_button5(menu: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
const self = &menu.*.opts.ctx.*;
self.selected = if (self.menu.selected) |sel_| sel_ + self.view_pos else self.selected;
if (self.view_pos < @max(self.entries.items.len, self.view_rows) - self.view_rows)
self.view_pos += Menu.scroll_lines;
self.update_selected();
}
fn update_selected(self: *Self) void {
if (self.selected) |sel| {
if (sel >= self.view_pos and sel < self.view_pos + self.view_rows) {
self.menu.selected = sel - self.view_pos;
} else {
self.menu.selected = null;
}
}
}
fn handle_menu_action(menu: **Menu.State(*Self), button: *Button.State(*Menu.State(*Self))) void { fn handle_menu_action(menu: **Menu.State(*Self), button: *Button.State(*Menu.State(*Self))) void {
const self = menu.*.opts.ctx; const self = menu.*.opts.ctx;
var idx: usize = undefined; var idx: usize = undefined;
@ -162,10 +201,13 @@ fn handle_menu_action(menu: **Menu.State(*Self), button: *Button.State(*Menu.Sta
self.logger.print_err(name, "invalid table entry: {s}", .{json}); self.logger.print_err(name, "invalid table entry: {s}", .{json});
return; return;
} }
idx += self.view_pos;
if (idx >= self.entries.items.len) { if (idx >= self.entries.items.len) {
self.logger.print_err(name, "table entry index out of range: {d}/{d}", .{ idx, self.entries.items.len }); self.logger.print_err(name, "table entry index out of range: {d}/{d}", .{ idx, self.entries.items.len });
return; return;
} }
self.selected = idx;
self.update_selected();
const entry = &self.entries.items[idx]; const entry = &self.entries.items[idx];
tp.self_pid().send(.{ "cmd", "navigate", .{ tp.self_pid().send(.{ "cmd", "navigate", .{
@ -181,18 +223,29 @@ fn handle_menu_action(menu: **Menu.State(*Self), button: *Button.State(*Menu.Sta
} }) catch |e| self.logger.err("navigate", e); } }) catch |e| self.logger.err("navigate", e);
} }
fn move_next(self: *Self, dir: enum { up, down }) void {
self.selected = if (self.menu.selected) |sel_| sel_ + self.view_pos else self.selected;
const sel = switch (dir) {
.up => if (self.selected) |sel_| if (sel_ > 0) sel_ - 1 else self.entries.items.len - 1 else self.entries.items.len - 1,
.down => if (self.selected) |sel_| if (sel_ < self.entries.items.len - 1) sel_ + 1 else 0 else 0,
};
self.selected = sel;
if (sel < self.view_pos) self.view_pos = sel;
if (sel > self.view_pos + self.view_rows - 1) self.view_pos = sel - @min(sel, self.view_rows - 1);
self.update_selected();
self.menu.activate_selected();
}
const cmds = struct { const cmds = struct {
pub const Target = Self; pub const Target = Self;
const Ctx = command.Context; const Ctx = command.Context;
const Result = command.Result; const Result = command.Result;
pub fn goto_prev_file(self: *Self, _: Ctx) Result { pub fn goto_prev_file(self: *Self, _: Ctx) Result {
self.menu.select_up(); self.move_next(.up);
self.menu.activate_selected();
} }
pub fn goto_next_file(self: *Self, _: Ctx) Result { pub fn goto_next_file(self: *Self, _: Ctx) Result {
self.menu.select_down(); self.move_next(.down);
self.menu.activate_selected();
} }
}; };