refactor: pass relative click position to button click handlers

This a big refactor just to clean-up type definitions used by Button and Menu.
The goals is to pass the click position as a cursor object.
This commit is contained in:
CJ van den Berg 2025-10-09 19:11:25 +02:00
parent 35ccf7f1df
commit ce87dcfa2b
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
21 changed files with 148 additions and 118 deletions

View file

@ -4,6 +4,7 @@ const tp = @import("thespian");
const EventHandler = @import("EventHandler");
const Plane = @import("renderer").Plane;
const input = @import("input");
pub const Cursor = @import("Buffer").Cursor;
const Widget = @import("Widget.zig");
const tui = @import("tui.zig");
@ -14,20 +15,23 @@ pub fn Options(context: type) type {
pos: Widget.Box = .{ .y = 0, .x = 0, .w = 8, .h = 1 },
ctx: Context,
on_click: *const fn (ctx: *context, button: *State(Context)) void = do_nothing,
on_click2: *const fn (ctx: *context, button: *State(Context)) void = do_nothing,
on_click3: *const fn (ctx: *context, button: *State(Context)) void = do_nothing,
on_click4: *const fn (ctx: *context, button: *State(Context)) void = do_nothing,
on_click5: *const fn (ctx: *context, button: *State(Context)) void = do_nothing,
on_render: *const fn (ctx: *context, button: *State(Context), theme: *const Widget.Theme) bool = on_render_default,
on_layout: *const fn (ctx: *context, button: *State(Context)) Widget.Layout = on_layout_default,
on_receive: *const fn (ctx: *context, button: *State(Context), from: tp.pid_ref, m: tp.message) error{Exit}!bool = on_receive_default,
on_click: ClickHandler = do_nothing,
on_click2: ClickHandler = do_nothing,
on_click3: ClickHandler = do_nothing,
on_click4: ClickHandler = do_nothing,
on_click5: ClickHandler = do_nothing,
on_render: *const fn (ctx: *context, button: *ButtonType, theme: *const Widget.Theme) bool = on_render_default,
on_layout: *const fn (ctx: *context, button: *ButtonType) Widget.Layout = on_layout_default,
on_receive: *const fn (ctx: *context, button: *ButtonType, from: tp.pid_ref, m: tp.message) error{Exit}!bool = on_receive_default,
on_event: ?EventHandler = null,
pub const ButtonType = State(Context);
pub const Context = context;
pub fn do_nothing(_: *context, _: *State(Context)) void {}
pub const ClickHandler = *const fn (ctx: *context, button: *ButtonType, pos: Cursor) void;
pub fn on_render_default(_: *context, self: *State(Context), theme: *const Widget.Theme) bool {
pub fn do_nothing(_: *context, _: *ButtonType, _: Cursor) void {}
pub fn on_render_default(_: *context, self: *ButtonType, theme: *const Widget.Theme) bool {
self.plane.set_base_style(if (self.active) theme.scrollbar_active else if (self.hover) theme.scrollbar_hover else theme.scrollbar);
self.plane.erase();
self.plane.home();
@ -35,18 +39,18 @@ pub fn Options(context: type) type {
return false;
}
pub fn on_layout_default(_: *context, self: *State(Context)) Widget.Layout {
pub fn on_layout_default(_: *context, self: *ButtonType) Widget.Layout {
return .{ .static = self.opts.label.len + 2 };
}
pub fn on_receive_default(_: *context, _: *State(Context), _: tp.pid_ref, _: tp.message) error{Exit}!bool {
pub fn on_receive_default(_: *context, _: *ButtonType, _: tp.pid_ref, _: tp.message) error{Exit}!bool {
return false;
}
};
}
pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) error{OutOfMemory}!*State(ctx_type) {
const Self = State(ctx_type);
pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) error{OutOfMemory}!*Options(ctx_type).ButtonType {
const Self = Options(ctx_type).ButtonType;
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
var n = try Plane.init(&opts.pos.opts(@typeName(Self)), parent);
@ -67,7 +71,7 @@ pub fn create_widget(ctx_type: type, allocator: std.mem.Allocator, parent: Plane
return Widget.to(try create(ctx_type, allocator, parent, opts));
}
pub fn State(ctx_type: type) type {
fn State(ctx_type: type) type {
return struct {
allocator: std.mem.Allocator,
parent: Plane,
@ -110,7 +114,9 @@ pub fn State(ctx_type: type) type {
pub fn receive(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool {
var btn: input.MouseType = 0;
if (try m.match(.{ "B", input.event.press, tp.extract(&btn), tp.more })) {
var x: c_int = undefined;
var y: c_int = undefined;
if (try m.match(.{ "B", input.event.press, tp.extract(&btn), tp.any, tp.extract(&x), tp.extract(&y), tp.any, tp.any })) {
const btn_enum: input.Mouse = @enumFromInt(btn);
switch (btn_enum) {
input.mouse.BUTTON1 => {
@ -118,14 +124,14 @@ pub fn State(ctx_type: type) type {
tui.need_render();
},
input.mouse.BUTTON4, input.mouse.BUTTON5 => {
self.call_click_handler(btn_enum);
self.call_click_handler(btn_enum, self.to_rel_cursor(x, y));
return true;
},
else => {},
}
return true;
} else if (try m.match(.{ "B", input.event.release, tp.extract(&btn), tp.more })) {
self.call_click_handler(@enumFromInt(btn));
} else if (try m.match(.{ "B", input.event.release, tp.extract(&btn), tp.any, tp.extract(&x), tp.extract(&y), tp.any, tp.any })) {
self.call_click_handler(@enumFromInt(btn), self.to_rel_cursor(x, y));
tui.need_render();
return true;
} else if (try m.match(.{ "D", input.event.press, tp.extract(&btn), tp.more })) {
@ -134,12 +140,12 @@ pub fn State(ctx_type: type) type {
h.send(from, m) catch {};
}
return true;
} else if (try m.match(.{ "D", input.event.release, tp.extract(&btn), tp.more })) {
} else if (try m.match(.{ "D", input.event.release, tp.extract(&btn), tp.any, tp.extract(&x), tp.extract(&y), tp.any, tp.any })) {
if (self.opts.on_event) |h| {
self.active = false;
h.send(from, m) catch {};
}
self.call_click_handler(@enumFromInt(btn));
self.call_click_handler(@enumFromInt(btn), self.to_rel_cursor(x, y));
tui.need_render();
return true;
} else if (try m.match(.{ "H", tp.extract(&self.hover) })) {
@ -150,18 +156,23 @@ pub fn State(ctx_type: type) type {
return self.opts.on_receive(&self.opts.ctx, self, from, m);
}
fn call_click_handler(self: *Self, btn: input.Mouse) void {
fn to_rel_cursor(self: *const Self, abs_x: c_int, abs_y: c_int) Cursor {
const rel_y, const rel_x = self.plane.abs_yx_to_rel(abs_y, abs_x);
return .{ .row = @intCast(rel_y), .col = @intCast(rel_x) };
}
fn call_click_handler(self: *Self, btn: input.Mouse, pos: Cursor) void {
if (btn == input.mouse.BUTTON1) {
if (!self.active) return;
self.active = false;
}
if (!self.hover) return;
switch (btn) {
input.mouse.BUTTON1 => self.opts.on_click(&self.opts.ctx, self),
input.mouse.BUTTON2 => self.opts.on_click2(&self.opts.ctx, self),
input.mouse.BUTTON3 => self.opts.on_click3(&self.opts.ctx, self),
input.mouse.BUTTON4 => self.opts.on_click4(&self.opts.ctx, self),
input.mouse.BUTTON5 => self.opts.on_click5(&self.opts.ctx, self),
input.mouse.BUTTON1 => self.opts.on_click(&self.opts.ctx, self, pos),
input.mouse.BUTTON2 => self.opts.on_click2(&self.opts.ctx, self, pos),
input.mouse.BUTTON3 => self.opts.on_click3(&self.opts.ctx, self, pos),
input.mouse.BUTTON4 => self.opts.on_click4(&self.opts.ctx, self, pos),
input.mouse.BUTTON5 => self.opts.on_click5(&self.opts.ctx, self, pos),
else => {},
}
}

View file

@ -16,20 +16,24 @@ pub fn Options(context: type) type {
ctx: Context,
style: Widget.Type,
on_click: *const fn (ctx: context, button: *Button.State(*State(Context))) void = do_nothing,
on_click4: *const fn (menu: **State(Context), button: *Button.State(*State(Context))) void = do_nothing_click,
on_click5: *const fn (menu: **State(Context), button: *Button.State(*State(Context))) void = do_nothing_click,
on_render: *const fn (ctx: context, button: *Button.State(*State(Context)), theme: *const Widget.Theme, selected: bool) bool = on_render_default,
on_layout: *const fn (ctx: context, button: *Button.State(*State(Context))) Widget.Layout = on_layout_default,
prepare_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) Widget.Box = prepare_resize_default,
after_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) void = after_resize_default,
on_click: ClickHandler = do_nothing,
on_click4: ButtonClickHandler = do_nothing_click,
on_click5: ButtonClickHandler = do_nothing_click,
on_render: *const fn (ctx: context, button: *ButtonType, theme: *const Widget.Theme, selected: bool) bool = on_render_default,
on_layout: *const fn (ctx: context, button: *ButtonType) Widget.Layout = on_layout_default,
prepare_resize: *const fn (ctx: context, menu: *MenuType, box: Widget.Box) Widget.Box = prepare_resize_default,
after_resize: *const fn (ctx: context, menu: *MenuType, box: Widget.Box) void = after_resize_default,
on_scroll: ?EventHandler = null,
pub const Context = context;
pub fn do_nothing(_: context, _: *Button.State(*State(Context))) void {}
pub fn do_nothing_click(_: **State(Context), _: *Button.State(*State(Context))) void {}
pub const MenuType = State(Context);
pub const ButtonType = Button.Options(*MenuType).ButtonType;
pub const ButtonClickHandler = Button.Options(*MenuType).ClickHandler;
pub const ClickHandler = *const fn (ctx: context, button: *ButtonType) void;
pub fn do_nothing(_: context, _: *ButtonType) void {}
pub fn do_nothing_click(_: **MenuType, _: *ButtonType, _: Button.Cursor) void {}
pub fn on_render_default(_: context, button: *Button.State(*State(Context)), theme: *const Widget.Theme, selected: bool) bool {
pub fn on_render_default(_: context, button: *ButtonType, theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor;
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else style_base;
button.plane.set_base_style(style_base);
@ -44,17 +48,17 @@ pub fn Options(context: type) type {
return false;
}
pub fn on_layout_default(_: context, _: *Button.State(*State(Context))) Widget.Layout {
pub fn on_layout_default(_: context, _: *ButtonType) Widget.Layout {
return .{ .static = 1 };
}
pub fn prepare_resize_default(_: context, state: *State(Context), box_: Widget.Box) Widget.Box {
pub fn prepare_resize_default(_: context, state: *MenuType, box_: Widget.Box) Widget.Box {
var box = box_;
box.h = if (box_.h == 0) state.menu.widgets.items.len else box_.h;
return box;
}
pub fn after_resize_default(_: context, _: *State(Context), _: Widget.Box) void {}
pub fn after_resize_default(_: context, _: *MenuType, _: Widget.Box) void {}
};
}
@ -92,15 +96,15 @@ pub fn State(ctx_type: type) type {
container_widget: Widget,
frame_widget: ?Widget,
scrollbar: ?*scrollbar_v,
opts: options_type,
opts: OptionsType,
selected: ?usize = null,
render_idx: usize = 0,
selected_active: bool = false,
header_count: usize = 0,
const Self = @This();
const options_type = Options(ctx_type);
const button_type = Button.State(*Self);
pub const OptionsType = Options(ctx_type);
pub const ButtonType = Button.Options(*Self).ButtonType;
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
self.menu.deinit(allocator);
@ -125,7 +129,7 @@ pub fn State(ctx_type: type) type {
}));
}
pub fn add_item_with_handler(self: *Self, label: []const u8, on_click: *const fn (_: **Self, _: *Button.State(*Self)) void) !void {
pub fn add_item_with_handler(self: *Self, label: []const u8, on_click: OptionsType.ButtonClickHandler) !void {
try self.menu.add(try Button.create_widget(*Self, self.allocator, self.menu.parent, .{
.ctx = self,
.on_layout = on_layout,
@ -163,11 +167,11 @@ pub fn State(ctx_type: type) type {
self.opts.after_resize(self.*.opts.ctx, self, box);
}
pub fn on_layout(self: **Self, button: *Button.State(*Self)) Widget.Layout {
pub fn on_layout(self: **Self, button: *ButtonType) Widget.Layout {
return self.*.opts.on_layout(self.*.opts.ctx, button);
}
pub fn on_render(self: **Self, button: *Button.State(*Self), theme: *const Widget.Theme) bool {
pub fn on_render(self: **Self, button: *ButtonType, theme: *const Widget.Theme) bool {
defer self.*.render_idx += 1;
std.debug.assert(self.*.render_idx < self.*.menu.widgets.items.len);
return self.*.opts.on_render(self.*.opts.ctx, button, theme, self.*.render_idx == self.*.selected);
@ -217,15 +221,15 @@ pub fn State(ctx_type: type) type {
pub fn activate_selected(self: *Self) void {
const button = self.get_selected() orelse return;
button.opts.on_click(&button.opts.ctx, button);
button.opts.on_click(&button.opts.ctx, button, .{});
}
pub fn get_selected(self: *Self) ?*button_type {
pub fn get_selected(self: *Self) ?*ButtonType {
const selected = self.selected orelse return null;
self.selected_active = true;
const pos = selected + self.header_count;
return if (pos < self.menu.widgets.items.len)
self.menu.widgets.items[pos].widget.dynamic_cast(button_type)
self.menu.widgets.items[pos].widget.dynamic_cast(ButtonType)
else
null;
}

View file

@ -23,7 +23,7 @@ const Commands = command.Collection(cmds);
allocator: std.mem.Allocator,
plane: Plane,
menu: *Menu.State(*Self),
menu: *MenuType,
logger: log.Logger,
commands: Commands = undefined,
@ -35,6 +35,8 @@ entries: std.ArrayList(Entry) = undefined,
selected: ?usize = null,
box: Widget.Box = .{},
const MenuType = Menu.Options(*Self).MenuType;
const ButtonType = MenuType.ButtonType;
const path_column_ratio = 4;
const widget_type: Widget.Type = .panel;
@ -136,7 +138,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
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: *ButtonType, theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.panel;
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.panel;
const style_hint: Widget.Theme.Style = .{ .fg = theme.editor_hint.fg, .fs = theme.editor_hint.fs, .bg = style_label.bg };
@ -195,7 +197,7 @@ fn update_scrollbar(self: *Self) void {
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 {
fn mouse_click_button4(menu: **MenuType, _: *ButtonType, _: Button.Cursor) 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) {
@ -207,7 +209,7 @@ fn mouse_click_button4(menu: **Menu.State(*Self), _: *Button.State(*Menu.State(*
self.update_scrollbar();
}
fn mouse_click_button5(menu: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
fn mouse_click_button5(menu: **MenuType, _: *ButtonType, _: Button.Cursor) 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)
@ -226,7 +228,7 @@ fn update_selected(self: *Self) void {
}
}
fn handle_menu_action(menu: **Menu.State(*Self), button: *Button.State(*Menu.State(*Self))) void {
fn handle_menu_action(menu: **MenuType, button: *ButtonType, _: Button.Cursor) void {
const self = menu.*.opts.ctx;
var idx: usize = undefined;
var iter = button.opts.label;

View file

@ -82,6 +82,8 @@ home_style_bufs: [][]const u8,
const Self = @This();
const widget_type: Widget.Type = .home;
const MenuType = Menu.Options(*Self).MenuType;
const ButtonType = MenuType.ButtonType;
pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
const logger = log.logger("home");
@ -188,7 +190,7 @@ pub fn receive(_: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
return false;
}
fn menu_on_render(self: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
fn menu_on_render(self: *Self, button: *ButtonType, theme: *const Widget.Theme, selected: bool) bool {
var description: []const u8 = undefined;
var hint: []const u8 = undefined;
var command_name: []const u8 = undefined;
@ -257,7 +259,7 @@ fn menu_on_render(self: *Self, button: *Button.State(*Menu.State(*Self)), theme:
return false;
}
fn menu_action(_: **Menu.State(*Self), button: *Button.State(*Menu.State(*Self))) void {
fn menu_action(_: **Menu.State(*Self), button: *ButtonType, _: Button.Cursor) void {
var description: []const u8 = undefined;
var hint: []const u8 = undefined;
var command_name: []const u8 = undefined;

View file

@ -54,11 +54,11 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
palette.items += 1;
}
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.Theme, selected: bool) bool {
return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Cursor) void {
var file_path: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchString(&iter, &file_path) catch false)) return;
@ -66,7 +66,7 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
}
pub fn delete_item(menu: *Type.MenuState, button: *Type.ButtonState) bool {
pub fn delete_item(menu: *Type.MenuType, button: *Type.ButtonType) bool {
var file_path: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchString(&iter, &file_path) catch false)) return false;

View file

@ -52,7 +52,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
palette.items += 1;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Cursor) void {
var unused: []const u8 = undefined;
var command_id: command.ID = undefined;
var iter = button.opts.label;

View file

@ -83,7 +83,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
palette.items += 1;
}
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.Theme, selected: bool) bool {
var item_cbor: []const u8 = undefined;
var matches_cbor: []const u8 = undefined;
@ -146,13 +146,13 @@ fn get_replace_selection(replace: Buffer.Selection) ?Buffer.Selection {
replace;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Cursor) void {
const label_, _, _, _, _ = get_values(button.opts.label);
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
tp.self_pid().send(.{ "cmd", "insert_chars", .{label_} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
}
pub fn updated(palette: *Type, button_: ?*Type.ButtonState) !void {
pub fn updated(palette: *Type, button_: ?*Type.ButtonType) !void {
const button = button_ orelse return cancel(palette);
_, _, _, const replace, _ = get_values(button.opts.label);
const editor = tui.get_active_editor() orelse return error.NotFound;

View file

@ -71,7 +71,7 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
palette.items += 1;
}
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
pub fn on_render_menu(_: *Type, button: *Type.ButtonType, theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor_widget;
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget;
const style_hint = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_label;
@ -116,7 +116,7 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
return false;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Cursor) void {
var description_: []const u8 = undefined;
var icon_: []const u8 = undefined;
var color: u24 = undefined;

View file

@ -56,7 +56,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
palette.items += 1;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Cursor) void {
var label_: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchString(&iter, &label_) catch false)) return;
@ -64,7 +64,7 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
tp.self_pid().send(.{ "cmd", "set_fontface", .{label_} }) catch |e| menu.*.opts.ctx.logger.err("fontface_palette", e);
}
pub fn updated(palette: *Type, button_: ?*Type.ButtonState) !void {
pub fn updated(palette: *Type, button_: ?*Type.ButtonType) !void {
const button = button_ orelse return cancel(palette);
var label_: []const u8 = undefined;
var iter = button.opts.label;

View file

@ -69,7 +69,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
palette.items += 1;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Cursor) void {
var unused: []const u8 = undefined;
var command_id: command.ID = undefined;
var iter = button.opts.label;

View file

@ -28,7 +28,7 @@ const widget_type: Widget.Type = .palette;
allocator: std.mem.Allocator,
f: usize = 0,
modal: *ModalBackground.State(*Self),
menu: *Menu.State(*Self),
menu: *MenuType,
inputbox: *InputBox.State(*Self),
logger: log.Logger,
query_pending: bool = false,
@ -39,6 +39,8 @@ commands: Commands = undefined,
buffer_manager: ?*BufferManager,
const inputbox_label = "Search files by name";
const MenuType = Menu.Options(*Self).MenuType;
const ButtonType = MenuType.ButtonType;
pub fn create(allocator: std.mem.Allocator) !tui.Mode {
const mv = tui.mainview() orelse return error.NotFound;
@ -104,11 +106,11 @@ inline fn max_menu_width() usize {
return @max(15, width - (width / 5));
}
fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
fn on_render_menu(_: *Self, button: *ButtonType, theme: *const Widget.Theme, selected: bool) bool {
return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
}
fn prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box {
fn prepare_resize_menu(self: *Self, _: *MenuType, _: Widget.Box) Widget.Box {
return self.prepare_resize();
}
@ -123,7 +125,7 @@ fn do_resize(self: *Self) void {
self.menu.resize(self.prepare_resize());
}
fn menu_action_open_file(menu: **Menu.State(*Self), button: *Button.State(*Menu.State(*Self))) void {
fn menu_action_open_file(menu: **MenuType, button: *ButtonType, _: Button.Cursor) void {
var file_path: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchString(&iter, &file_path) catch false)) return;

View file

@ -63,7 +63,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
palette.items += 1;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Cursor) void {
var name_: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchString(&iter, &name_) catch false)) return;
@ -71,7 +71,7 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
tp.self_pid().send(.{ "cmd", "change_project", .{name_} }) catch |e| menu.*.opts.ctx.logger.err("open_recent_project", e);
}
pub fn delete_item(menu: *Type.MenuState, button: *Type.ButtonState) bool {
pub fn delete_item(menu: *Type.MenuType, button: *Type.ButtonType) bool {
var name_: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchString(&iter, &name_) catch false)) return false;

View file

@ -46,8 +46,9 @@ pub fn Create(options: type) type {
const Self = @This();
const ValueType = if (@hasDecl(options, "ValueType")) options.ValueType else void;
pub const MenuState = Menu.State(*Self);
pub const ButtonState = Button.State(*Menu.State(*Self));
pub const MenuType = Menu.Options(*Self).MenuType;
pub const ButtonType = MenuType.ButtonType;
pub const Cursor = Button.Cursor;
pub fn create(allocator: std.mem.Allocator) !tui.Mode {
return create_with_args(allocator, .{});
@ -131,7 +132,7 @@ pub fn Create(options: type) type {
.{ .fg = theme.scrollbar.fg, .bg = theme.editor_widget.bg };
}
fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
fn on_render_menu(_: *Self, button: *ButtonType, theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor_widget;
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget;
const style_hint = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_label;
@ -211,7 +212,7 @@ pub fn Create(options: type) type {
scrollbar.set(@intCast(@max(self.total_items, 1) - 1), @intCast(self.view_rows), @intCast(self.view_pos));
}
fn mouse_click_button4(menu: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
fn mouse_click_button4(menu: **Menu.State(*Self), _: *ButtonType, _: Button.Cursor) void {
const self = &menu.*.opts.ctx.*;
if (self.view_pos < Menu.scroll_lines) {
self.view_pos = 0;
@ -222,7 +223,7 @@ pub fn Create(options: type) type {
self.start_query(0) catch {};
}
fn mouse_click_button5(menu: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
fn mouse_click_button5(menu: **Menu.State(*Self), _: *ButtonType, _: Button.Cursor) void {
const self = &menu.*.opts.ctx.*;
if (self.view_pos < @max(self.total_items, self.view_rows) - self.view_rows)
self.view_pos += Menu.scroll_lines;

View file

@ -58,7 +58,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
palette.items += 1;
}
pub fn on_render_menu(palette: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
pub fn on_render_menu(palette: *Type, button: *Type.ButtonType, theme: *const Widget.Theme, selected: bool) bool {
var entry: Entry = undefined;
var iter = button.opts.label; // label contains cbor entry object and matches
if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false))
@ -116,7 +116,7 @@ pub fn on_render_menu(palette: *Type, button: *Type.ButtonState, theme: *const W
return false;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Cursor) void {
var entry: Entry = undefined;
var iter = button.opts.label;
if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false)) return;
@ -130,7 +130,7 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
}
}
pub fn delete_item(menu: *Type.MenuState, button: *Type.ButtonState) bool {
pub fn delete_item(menu: *Type.MenuType, button: *Type.ButtonType) bool {
var entry: Entry = undefined;
var iter = button.opts.label;
if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false)) return false;

View file

@ -53,7 +53,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
palette.items += 1;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
fn select(menu: **Type.MenuType, button: *Type.ButtonType, _: Type.Cursor) void {
var description_: []const u8 = undefined;
var name_: []const u8 = undefined;
var iter = button.opts.label;
@ -65,7 +65,7 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
tp.self_pid().send(.{ "cmd", "set_theme", .{name_} }) catch |e| menu.*.opts.ctx.logger.err("theme_palette", e);
}
pub fn updated(palette: *Type, button_: ?*Type.ButtonState) !void {
pub fn updated(palette: *Type, button_: ?*Type.ButtonType) !void {
const button = button_ orelse return cancel(palette);
var description_: []const u8 = undefined;
var name_: []const u8 = undefined;

View file

@ -29,6 +29,7 @@ untracked: usize = 0,
done: bool = true,
const Self = @This();
const ButtonType = Button.Options(Self).ButtonType;
pub fn create(
allocator: std.mem.Allocator,
@ -61,7 +62,7 @@ pub fn ctx_deinit(self: *Self) void {
if (self.behind) |p| self.allocator.free(p);
}
fn on_click(self: *Self, _: *Button.State(Self)) void {
fn on_click(self: *Self, _: *ButtonType, _: Button.Cursor) void {
self.refresh_git_status();
command.executeName("show_git_status", .{}) catch {};
}
@ -70,7 +71,7 @@ fn refresh_git_status(self: *Self) void {
if (self.workspace_path) |_| git.status(0) catch {};
}
pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool {
pub fn receive(self: *Self, _: *ButtonType, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "E", tp.more }))
return self.process_event(m);
if (try m.match(.{ "PRJ", "open" }))
@ -195,14 +196,14 @@ fn format(self: *Self, buf: []u8) []const u8 {
return fbs.getWritten();
}
pub fn layout(self: *Self, btn: *Button.State(Self)) Widget.Layout {
pub fn layout(self: *Self, btn: *ButtonType) Widget.Layout {
var buf: [256]u8 = undefined;
const text = self.format(&buf);
const len = btn.plane.egc_chunk_width(text, 0, 1);
return .{ .static = len };
}
pub fn render(self: *Self, btn: *Button.State(Self), theme: *const Widget.Theme) bool {
pub fn render(self: *Self, btn: *ButtonType, theme: *const Widget.Theme) bool {
var buf: [256]u8 = undefined;
const text = self.format(&buf);
if (text.len == 0) return false;

View file

@ -17,6 +17,7 @@ buf: [256]u8 = undefined,
rendered: [:0]const u8 = "",
const Self = @This();
const ButtonType = Button.Options(Self).ButtonType;
pub fn create(allocator: Allocator, parent: Plane, event_handler: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget {
return Button.create_widget(Self, allocator, parent, .{
@ -30,15 +31,15 @@ pub fn create(allocator: Allocator, parent: Plane, event_handler: ?EventHandler,
});
}
fn on_click(_: *Self, _: *Button.State(Self)) void {
fn on_click(_: *Self, _: *ButtonType, _: Button.Cursor) void {
command.executeName("show_diagnostics", .{}) catch {};
}
pub fn layout(self: *Self, _: *Button.State(Self)) Widget.Layout {
pub fn layout(self: *Self, _: *ButtonType) Widget.Layout {
return .{ .static = self.rendered.len };
}
pub fn render(self: *Self, btn: *Button.State(Self), theme: *const Widget.Theme) bool {
pub fn render(self: *Self, btn: *ButtonType, theme: *const Widget.Theme) bool {
const bg_style = if (btn.active) theme.editor_cursor else if (btn.hover) theme.statusbar_hover else theme.statusbar;
btn.plane.set_base_style(theme.editor);
btn.plane.erase();
@ -61,7 +62,7 @@ fn format(self: *Self) void {
self.buf[self.rendered.len] = 0;
}
pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool {
pub fn receive(self: *Self, _: *ButtonType, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "E", "diag", tp.extract(&self.errors), tp.extract(&self.warnings), tp.extract(&self.info), tp.extract(&self.hints) }))
self.format();
return false;

View file

@ -40,6 +40,7 @@ indent_mode: config.IndentMode = .spaces,
const project_icon = "";
const Self = @This();
const ButtonType = Button.Options(Self).ButtonType;
pub fn create(allocator: Allocator, parent: Plane, event_handler: ?EventHandler, _: ?[]const u8) @import("widget.zig").CreateError!Widget {
const btn = try Button.create(Self, allocator, parent, .{
@ -64,23 +65,23 @@ pub fn create(allocator: Allocator, parent: Plane, event_handler: ?EventHandler,
return Widget.to(btn);
}
fn on_click(_: *Self, _: *Button.State(Self)) void {
fn on_click(_: *Self, _: *ButtonType, _: Button.Cursor) void {
command.executeName("open_recent", .{}) catch {};
}
fn on_click2(_: *Self, _: *Button.State(Self)) void {
fn on_click2(_: *Self, _: *ButtonType, _: Button.Cursor) void {
command.executeName("close_file", .{}) catch {};
}
fn on_click3(self: *Self, _: *Button.State(Self)) void {
fn on_click3(self: *Self, _: *ButtonType, _: Button.Cursor) void {
self.detailed = !self.detailed;
}
pub fn layout(_: *Self, _: *Button.State(Self)) Widget.Layout {
pub fn layout(_: *Self, _: *ButtonType) Widget.Layout {
return .dynamic;
}
pub fn render(self: *Self, btn: *Button.State(Self), theme: *const Widget.Theme) bool {
pub fn render(self: *Self, btn: *ButtonType, theme: *const Widget.Theme) bool {
const style_base = theme.statusbar;
const style_label = if (btn.active) theme.editor_cursor else style_base;
btn.plane.set_base_style(theme.editor);
@ -205,7 +206,7 @@ fn render_terminal_title(self: *Self) void {
tui.rdr().set_terminal_title(new_title);
}
pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool {
pub fn receive(self: *Self, _: *ButtonType, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "E", tp.more }))
return self.process_event(m);
if (try m.match(.{ "PRJ", "open" })) {

View file

@ -33,6 +33,7 @@ const Leader = enum {
zero,
};
const Self = @This();
const ButtonType = Button.Options(Self).ButtonType;
pub fn create(allocator: Allocator, parent: Plane, event_handler: ?EventHandler, arg: ?[]const u8) @import("widget.zig").CreateError!Widget {
const padding: ?usize, const leader: ?Leader, const style: ?DigitStyle = if (arg) |fmt| blk: {
@ -59,17 +60,17 @@ pub fn create(allocator: Allocator, parent: Plane, event_handler: ?EventHandler,
});
}
fn on_click(_: *Self, _: *Button.State(Self)) void {
fn on_click(_: *Self, _: *ButtonType, _: Button.Cursor) void {
command.executeName("goto", .{}) catch {};
}
pub fn layout(self: *Self, btn: *Button.State(Self)) Widget.Layout {
pub fn layout(self: *Self, btn: *ButtonType) Widget.Layout {
const warn_len = if (self.utf8_sanitized) btn.plane.egc_chunk_width(utf8_sanitized_warning, 0, 1) else 0;
const len = btn.plane.egc_chunk_width(self.rendered, 0, 1) + warn_len;
return .{ .static = len };
}
pub fn render(self: *Self, btn: *Button.State(Self), theme: *const Widget.Theme) bool {
pub fn render(self: *Self, btn: *ButtonType, theme: *const Widget.Theme) bool {
btn.plane.set_base_style(theme.editor);
btn.plane.erase();
btn.plane.home();
@ -120,7 +121,7 @@ fn format_count(self: *Self, writer: anytype, value: usize, width: usize) !void
for (value_str, 0..) |_, i| try writer.writeAll(fonts.get_digit_ascii(value_str[i .. i + 1], self.style orelse .ascii));
}
pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool {
pub fn receive(self: *Self, _: *ButtonType, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "E", "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) })) {
self.format();
} else if (try m.match(.{ "E", "eol_mode", tp.extract(&self.eol_mode), tp.extract(&self.utf8_sanitized), tp.extract(&self.indent_mode) })) {

View file

@ -18,6 +18,8 @@ const Style = enum {
};
const default_style = .fancy;
const ButtonType = Button.Options(Style).ButtonType;
pub fn create(allocator: Allocator, parent: Plane, event_handler: ?EventHandler, arg: ?[]const u8) CreateError!Widget {
const style_ = if (arg) |str_style| std.meta.stringToEnum(Style, str_style) orelse default_style else default_style;
return Button.create_widget(Style, allocator, parent, .{
@ -32,7 +34,7 @@ pub fn create(allocator: Allocator, parent: Plane, event_handler: ?EventHandler,
});
}
pub fn layout(_: *Style, btn: *Button.State(Style)) Widget.Layout {
pub fn layout(_: *Style, btn: *ButtonType) Widget.Layout {
const name = btn.plane.egc_chunk_width(tui.get_mode(), 0, 1);
const logo = if (is_mini_mode() or is_overlay_mode()) 1 else btn.plane.egc_chunk_width(left ++ symbol ++ right, 0, 1);
const padding: usize = 2;
@ -48,7 +50,7 @@ fn is_overlay_mode() bool {
return tui.input_mode_outer() != null;
}
pub fn render(ctx: *Style, self: *Button.State(Style), theme: *const Widget.Theme) bool {
pub fn render(ctx: *Style, self: *ButtonType, theme: *const Widget.Theme) bool {
const style_base = theme.statusbar;
const style_label = switch (ctx.*) {
.fancy => if (self.active) theme.editor_cursor else if (self.hover) theme.editor_selection else theme.statusbar_hover,
@ -78,7 +80,7 @@ pub fn render(ctx: *Style, self: *Button.State(Style), theme: *const Widget.Them
return false;
}
fn render_separator(self: *Button.State(Style), theme: *const Widget.Theme) void {
fn render_separator(self: *ButtonType, theme: *const Widget.Theme) void {
self.plane.reverse_style();
self.plane.set_base_style(.{ .bg = theme.editor.bg });
if (theme.statusbar.bg) |bg| self.plane.set_style(.{ .bg = bg });
@ -89,7 +91,7 @@ const left = " ";
const symbol = "󱞏";
const right = " ";
fn render_logo(self: *Button.State(Style), theme: *const Widget.Theme, style_label: Widget.Theme.Style) void {
fn render_logo(self: *ButtonType, theme: *const Widget.Theme, style_label: Widget.Theme.Style) void {
const style_braces: Widget.Theme.Style = if (tui.find_scope_style(theme, "punctuation")) |sty| .{ .fg = sty.style.fg, .bg = style_label.bg, .fs = style_label.fs } else style_label;
if (left.len > 0) {
self.plane.set_style(style_braces);
@ -103,7 +105,7 @@ fn render_logo(self: *Button.State(Style), theme: *const Widget.Theme, style_lab
}
}
fn on_click(_: *Style, _: *Button.State(Style)) void {
fn on_click(_: *Style, _: *ButtonType, _: Button.Cursor) void {
if (is_mini_mode()) {
command.executeName("exit_mini_mode", .{}) catch {};
} else if (is_overlay_mode()) {
@ -113,6 +115,6 @@ fn on_click(_: *Style, _: *Button.State(Style)) void {
}
}
fn toggle_panel(_: *Style, _: *Button.State(Style)) void {
fn toggle_panel(_: *Style, _: *ButtonType, _: Button.Cursor) void {
command.executeName("toggle_panel", .{}) catch {};
}

View file

@ -184,7 +184,7 @@ pub const TabBar = struct {
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
try self.update_tab_buffers();
const prev_widget_count = self.widget_list.widgets.items.len;
while (self.widget_list.pop()) |widget| if (widget.dynamic_cast(Button.State(Tab)) == null)
while (self.widget_list.pop()) |widget| if (widget.dynamic_cast(Tab.ButtonType) == null)
widget.deinit(self.widget_list.allocator);
var first = true;
for (self.tabs) |tab| {
@ -194,7 +194,7 @@ pub const TabBar = struct {
try self.widget_list.add(try self.make_spacer());
}
try self.widget_list.add(tab.widget);
if (tab.widget.dynamic_cast(Button.State(Tab))) |btn| {
if (tab.widget.dynamic_cast(Tab.ButtonType)) |btn| {
if (buffer_manager.buffer_from_ref(tab.buffer_ref)) |buffer|
try btn.update_label(Tab.name_from_buffer(buffer));
}
@ -327,6 +327,8 @@ const Tab = struct {
const Mode = enum { active, inactive, selected };
const ButtonType = Button.Options(@This()).ButtonType;
fn create(
tabbar: *TabBar,
buffer_ref: usize,
@ -346,19 +348,19 @@ const Tab = struct {
});
}
fn on_click(self: *@This(), _: *Button.State(@This())) void {
fn on_click(self: *@This(), _: *ButtonType, _: Button.Cursor) void {
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
if (buffer_manager.buffer_from_ref(self.buffer_ref)) |buffer|
tp.self_pid().send(.{ "cmd", "navigate", .{ .file = buffer.get_file_path() } }) catch {};
}
fn on_click2(self: *@This(), _: *Button.State(@This())) void {
fn on_click2(self: *@This(), _: *ButtonType, _: Button.Cursor) void {
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
if (buffer_manager.buffer_from_ref(self.buffer_ref)) |buffer|
tp.self_pid().send(.{ "cmd", "close_buffer", .{buffer.get_file_path()} }) catch {};
}
fn render(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme) bool {
fn render(self: *@This(), btn: *ButtonType, theme: *const Widget.Theme) bool {
const active = self.tabbar.active_buffer_ref == self.buffer_ref;
const mode: Mode = if (btn.hover) .selected else if (active) .active else .inactive;
switch (mode) {
@ -369,7 +371,7 @@ const Tab = struct {
return false;
}
fn render_selected(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme, active: bool) void {
fn render_selected(self: *@This(), btn: *ButtonType, theme: *const Widget.Theme, active: bool) void {
btn.plane.set_base_style(theme.editor);
btn.plane.erase();
btn.plane.home();
@ -407,7 +409,7 @@ const Tab = struct {
_ = btn.plane.putstr(self.tab_style.selected_right) catch {};
}
fn render_active(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme) void {
fn render_active(self: *@This(), btn: *ButtonType, theme: *const Widget.Theme) void {
btn.plane.set_base_style(theme.editor);
btn.plane.erase();
btn.plane.home();
@ -443,7 +445,7 @@ const Tab = struct {
_ = btn.plane.putstr(self.tab_style.active_right) catch {};
}
fn render_inactive(self: *@This(), btn: *Button.State(@This()), theme: *const Widget.Theme) void {
fn render_inactive(self: *@This(), btn: *ButtonType, theme: *const Widget.Theme) void {
btn.plane.set_base_style(theme.editor);
btn.plane.erase();
btn.plane.home();
@ -473,7 +475,7 @@ const Tab = struct {
_ = btn.plane.putstr(self.tab_style.inactive_right) catch {};
}
fn render_content(self: *@This(), btn: *Button.State(@This()), fg: ?Widget.Theme.Color, theme: *const Widget.Theme) void {
fn render_content(self: *@This(), btn: *ButtonType, fg: ?Widget.Theme.Color, theme: *const Widget.Theme) void {
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
const buffer_ = buffer_manager.buffer_from_ref(self.buffer_ref);
const is_dirty = if (buffer_) |buffer| buffer.is_dirty() else false;
@ -513,7 +515,7 @@ const Tab = struct {
while (padding > 0) : (padding -= 1) _ = plane.putstr(self.tab_style.padding) catch {};
}
fn layout(self: *@This(), btn: *Button.State(@This())) Widget.Layout {
fn layout(self: *@This(), btn: *ButtonType) Widget.Layout {
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
const is_dirty = if (buffer_manager.buffer_from_ref(self.buffer_ref)) |buffer| buffer.is_dirty() else false;
const active = self.tabbar.active_buffer_ref == self.buffer_ref;