feat: make filelist_view fully mouse and keyboard scrollable
This commit is contained in:
		
							parent
							
								
									ef95114039
								
							
						
					
					
						commit
						3a7e124255
					
				
					 1 changed files with 61 additions and 8 deletions
				
			
		| 
						 | 
				
			
			@ -10,6 +10,8 @@ const ArrayList = @import("std").ArrayList;
 | 
			
		|||
const Plane = @import("renderer").Plane;
 | 
			
		||||
const tp = @import("thespian");
 | 
			
		||||
const log = @import("log");
 | 
			
		||||
const key = @import("renderer").input.key;
 | 
			
		||||
const event_type = @import("renderer").input.event_type;
 | 
			
		||||
 | 
			
		||||
const command = @import("command.zig");
 | 
			
		||||
const tui = @import("tui.zig");
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +38,7 @@ items: usize = 0,
 | 
			
		|||
view_pos: usize = 0,
 | 
			
		||||
view_rows: usize = 0,
 | 
			
		||||
entries: std.ArrayList(Entry) = undefined,
 | 
			
		||||
selected: ?usize = null,
 | 
			
		||||
 | 
			
		||||
const Entry = struct {
 | 
			
		||||
    path: []const u8,
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +60,8 @@ pub fn create(allocator: Allocator, parent: Plane) !Widget {
 | 
			
		|||
            .ctx = self,
 | 
			
		||||
            .on_render = handle_render_menu,
 | 
			
		||||
            .on_scroll = EventHandler.bind(self, Self.handle_scroll),
 | 
			
		||||
            .on_click4 = mouse_click_button4,
 | 
			
		||||
            .on_click5 = mouse_click_button5,
 | 
			
		||||
        }),
 | 
			
		||||
    };
 | 
			
		||||
    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 {
 | 
			
		||||
    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.menu.resize(pos);
 | 
			
		||||
    self.menu.container_widget.resize(pos);
 | 
			
		||||
    self.view_rows = pos.h;
 | 
			
		||||
    self.update_scrollbar();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
| 
						 | 
				
			
			@ -92,7 +97,7 @@ pub fn add_item(self: *Self, entry_: Entry) !void {
 | 
			
		|||
    const writer = label.writer();
 | 
			
		||||
    cbor.writeValue(writer, idx) 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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -103,13 +108,15 @@ pub fn reset(self: *Self) void {
 | 
			
		|||
    }
 | 
			
		||||
    self.entries.clearRetainingCapacity();
 | 
			
		||||
    self.menu.reset_items();
 | 
			
		||||
    self.selected = null;
 | 
			
		||||
    self.menu.selected = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
 | 
			
		||||
    self.plane.set_base_style(" ", theme.panel);
 | 
			
		||||
    self.plane.erase();
 | 
			
		||||
    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 {
 | 
			
		||||
| 
						 | 
				
			
			@ -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});
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    idx += self.view_pos;
 | 
			
		||||
    if (idx >= self.entries.items.len) {
 | 
			
		||||
        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 {
 | 
			
		||||
    _ = try m.match(.{ "scroll_to", tp.extract(&self.view_pos) });
 | 
			
		||||
    self.update_selected();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn update_scrollbar(self: *Self) void {
 | 
			
		||||
    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 {
 | 
			
		||||
    const self = menu.*.opts.ctx;
 | 
			
		||||
    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});
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    idx += self.view_pos;
 | 
			
		||||
    if (idx >= self.entries.items.len) {
 | 
			
		||||
        self.logger.print_err(name, "table entry index out of range: {d}/{d}", .{ idx, self.entries.items.len });
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    self.selected = idx;
 | 
			
		||||
    self.update_selected();
 | 
			
		||||
    const entry = &self.entries.items[idx];
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
    pub const Target = Self;
 | 
			
		||||
    const Ctx = command.Context;
 | 
			
		||||
    const Result = command.Result;
 | 
			
		||||
 | 
			
		||||
    pub fn goto_prev_file(self: *Self, _: Ctx) Result {
 | 
			
		||||
        self.menu.select_up();
 | 
			
		||||
        self.menu.activate_selected();
 | 
			
		||||
        self.move_next(.up);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn goto_next_file(self: *Self, _: Ctx) Result {
 | 
			
		||||
        self.menu.select_down();
 | 
			
		||||
        self.menu.activate_selected();
 | 
			
		||||
        self.move_next(.down);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue