refactor: button, menu and widget stack apis

This commit is contained in:
CJ van den Berg 2024-03-17 22:32:31 +01:00
parent dcd9e119da
commit 2f9a0e2eb0
9 changed files with 348 additions and 130 deletions

View file

@ -11,16 +11,19 @@ const command = @import("command.zig");
const fonts = @import("fonts.zig");
a: std.mem.Allocator,
background: nc.Plane,
plane: nc.Plane,
parent: nc.Plane,
fire: ?Fire = null,
commands: Commands = undefined,
menu: *Menu,
menu: *Menu.State(*Self),
const Self = @This();
pub fn create(a: std.mem.Allocator, parent: Widget) !Widget {
const self: *Self = try a.create(Self);
var background = try nc.Plane.init(&(Widget.Box{}).opts("background"), parent.plane.*);
errdefer background.deinit();
var n = try nc.Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*);
errdefer n.deinit();
@ -28,35 +31,28 @@ pub fn create(a: std.mem.Allocator, parent: Widget) !Widget {
self.* = .{
.a = a,
.parent = parent.plane.*,
.background = background,
.plane = n,
.menu = try Menu.create(a, w),
.menu = try Menu.create(*Self, a, w, .{ .ctx = self, .on_render = menu_on_render }),
};
try self.commands.init(self);
try self.menu.add_item("Help ······················· :h", menu_action_help);
try self.menu.add_item("Open file ·················· :o", menu_action_open_file);
try self.menu.add_item("Open recent file ····(wip)·· :e", menu_action_open_recent_file);
try self.menu.add_item("Open recent project ·(wip)·· :r", menu_action_open_recent_project);
try self.menu.add_item("Show/Run commands ···(wip)·· :p", menu_action_show_commands);
try self.menu.add_item("Open config file ··········· :c", menu_action_open_config);
try self.menu.add_item("Quit/Close ················· :q", menu_action_quit);
try self.menu.add_item_with_handler("Help ······················· :h", menu_action_help);
try self.menu.add_item_with_handler("Open file ·················· :o", menu_action_open_file);
try self.menu.add_item_with_handler("Open recent file ····(wip)·· :e", menu_action_open_recent_file);
try self.menu.add_item_with_handler("Open recent project ·(wip)·· :r", menu_action_open_recent_project);
try self.menu.add_item_with_handler("Show/Run commands ···(wip)·· :p", menu_action_show_commands);
try self.menu.add_item_with_handler("Open config file ··········· :c", menu_action_open_config);
try self.menu.add_item_with_handler("Quit/Close ················· :q", menu_action_quit);
self.menu.resize(.{ .y = 15, .x = 9, .w = 32 });
command.executeName("enter_mode", command.Context.fmt(.{"home"})) catch {};
return w;
}
fn menu_item(self: *Self, label: []const u8, on_click: *const fn (_: *void, _: *Button.State(void)) void) !void {
try self.menu.add(try Button.create({}, self.a, self.parent, .{
.on_layout = menu_layout,
.label = label,
.on_click = on_click,
.on_render = render_menu_item,
}));
}
pub fn deinit(self: *Self, a: std.mem.Allocator) void {
self.menu.deinit(a);
self.commands.deinit();
self.plane.deinit();
self.background.deinit();
if (self.fire) |*fire| fire.deinit();
a.destroy(self);
}
@ -73,47 +69,85 @@ pub fn receive(_: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
var hover: bool = false;
if (try m.match(.{ "H", tp.extract(&hover) })) {
tui.current().request_mouse_cursor_default(hover);
tui.need_render();
return true;
}
return false;
}
fn menu_layout(_: *void, _: *Button.State(void)) Widget.Layout {
return .{ .static = 1 };
fn set_style(plane: nc.Plane, style: Widget.Theme.Style) void {
var channels: u64 = 0;
if (style.fg) |fg| {
nc.channels_set_fg_rgb(&channels, fg) catch {};
nc.channels_set_fg_alpha(&channels, nc.ALPHA_OPAQUE) catch {};
}
if (style.bg) |bg| {
nc.channels_set_bg_rgb(&channels, bg) catch {};
nc.channels_set_bg_alpha(&channels, nc.ALPHA_TRANSPARENT) catch {};
}
plane.set_channels(channels);
if (style.fs) |fs| switch (fs) {
.normal => plane.set_styles(nc.style.none),
.bold => plane.set_styles(nc.style.bold),
.italic => plane.set_styles(nc.style.italic),
.underline => plane.set_styles(nc.style.underline),
.strikethrough => plane.set_styles(nc.style.struck),
};
}
fn menu_action_help(_: *void, _: *Button.State(void)) void {
fn menu_on_render(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme) bool {
const style_base = if (button.active) theme.editor_cursor else if (button.hover) theme.editor_selection else theme.editor;
const bg_alpha: c_uint = if (button.active or button.hover) nc.ALPHA_OPAQUE else nc.ALPHA_TRANSPARENT;
try tui.set_base_style_alpha(button.plane, " ", style_base, nc.ALPHA_OPAQUE, bg_alpha);
button.plane.erase();
button.plane.home();
const style_subtext = if (tui.find_scope_style(theme, "comment")) |sty| sty.style else theme.editor;
const style_text = if (tui.find_scope_style(theme, "keyword")) |sty| sty.style else theme.editor;
const style_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else theme.editor;
const sep = std.mem.indexOfScalar(u8, button.opts.label, ':') orelse button.opts.label.len;
set_style(button.plane, style_subtext);
set_style(button.plane, style_text);
_ = button.plane.print(" {s}", .{button.opts.label[0..sep]}) catch {};
set_style(button.plane, style_keybind);
_ = button.plane.print("{s}", .{button.opts.label[sep + 1 ..]}) catch {};
return false;
}
fn menu_action_help(_: *Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
command.executeName("open_help", .{}) catch {};
}
fn menu_action_open_file(_: *void, _: *Button.State(void)) void {
fn menu_action_open_file(_: *Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
command.executeName("enter_open_file_mode", .{}) catch {};
}
fn menu_action_open_recent_file(_: *void, _: *Button.State(void)) void {
fn menu_action_open_recent_file(_: *Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
tp.self_pid().send(.{ "log", "home", "open recent file not implemented" }) catch {};
}
fn menu_action_open_recent_project(_: *void, _: *Button.State(void)) void {
fn menu_action_open_recent_project(_: *Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
tp.self_pid().send(.{ "log", "home", "open recent project not implemented" }) catch {};
}
fn menu_action_show_commands(_: *void, _: *Button.State(void)) void {
fn menu_action_show_commands(_: *Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
tp.self_pid().send(.{ "log", "home", "open command palette not implemented" }) catch {};
}
fn menu_action_open_config(_: *void, _: *Button.State(void)) void {
fn menu_action_open_config(_: *Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
command.executeName("open_config", .{}) catch {};
}
fn menu_action_quit(_: *void, _: *Button.State(void)) void {
fn menu_action_quit(_: *Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
command.executeName("quit", .{}) catch {};
}
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
const more = self.menu.render(theme);
tui.set_base_style(&self.plane, " ", theme.editor);
try tui.set_base_style_alpha(self.background, " ", theme.editor, nc.ALPHA_OPAQUE, nc.ALPHA_TRANSPARENT);
self.background.erase();
self.background.home();
try tui.set_base_style_alpha(self.plane, "", theme.editor, nc.ALPHA_TRANSPARENT, nc.ALPHA_TRANSPARENT);
self.plane.erase();
self.plane.home();
if (self.fire) |*fire| fire.render() catch unreachable;
@ -125,56 +159,42 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
const subtext = "a programmer's text editor";
if (self.plane.dim_x() > 120 and self.plane.dim_y() > 22) {
tui.set_style(&self.plane, style_title);
set_style(self.plane, style_title);
self.plane.cursor_move_yx(2, 4) catch return more;
fonts.print_string_large(self.plane, title) catch return more;
tui.set_style(&self.plane, style_subtext);
set_style(self.plane, style_subtext);
self.plane.cursor_move_yx(10, 8) catch return more;
fonts.print_string_medium(self.plane, subtext) catch return more;
self.menu.resize(.{ .y = 15, .x = 10, .w = 32 });
} else if (self.plane.dim_x() > 55 and self.plane.dim_y() > 16) {
tui.set_style(&self.plane, style_title);
set_style(self.plane, style_title);
self.plane.cursor_move_yx(2, 4) catch return more;
fonts.print_string_medium(self.plane, title) catch return more;
tui.set_style(&self.plane, style_subtext);
set_style(self.plane, style_subtext);
self.plane.cursor_move_yx(7, 6) catch return more;
_ = self.plane.print(subtext, .{}) catch {};
self.menu.resize(.{ .y = 9, .x = 8, .w = 32 });
} else {
tui.set_style(&self.plane, style_title);
set_style(self.plane, style_title);
self.plane.cursor_move_yx(1, 4) catch return more;
_ = self.plane.print(title, .{}) catch return more;
tui.set_style(&self.plane, style_subtext);
set_style(self.plane, style_subtext);
self.plane.cursor_move_yx(3, 6) catch return more;
_ = self.plane.print(subtext, .{}) catch {};
self.menu.resize(.{ .y = 5, .x = 8, .w = 32 });
}
return true;
}
fn render_menu_item(_: *void, button: *Button.State(void), theme: *const Widget.Theme) bool {
tui.set_base_style(&button.plane, " ", if (button.active) theme.editor_cursor else if (button.hover) theme.editor_selection else theme.editor);
button.plane.erase();
button.plane.home();
const style_subtext = if (tui.find_scope_style(theme, "comment")) |sty| sty.style else theme.editor;
const style_text = if (tui.find_scope_style(theme, "keyword")) |sty| sty.style else theme.editor;
const style_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else theme.editor;
const sep = std.mem.indexOfScalar(u8, button.opts.label, ':') orelse button.opts.label.len;
tui.set_style(&button.plane, style_subtext);
tui.set_style(&button.plane, style_text);
_ = button.plane.print(" {s}", .{button.opts.label[0..sep]}) catch {};
tui.set_style(&button.plane, style_keybind);
_ = button.plane.print("{s}", .{button.opts.label[sep + 1 ..]}) catch {};
return false;
return more or self.fire != null;
}
pub fn handle_resize(self: *Self, pos: Widget.Box) void {
self.background.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return;
self.background.resize_simple(@intCast(pos.h), @intCast(pos.w)) 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;
if (self.fire) |*fire| {
@ -193,7 +213,7 @@ const cmds = struct {
self.fire = if (self.fire) |*fire| ret: {
fire.deinit();
break :ret null;
} else Fire.init(self.a, self.plane, Widget.Box.from(self.plane)) catch |e| return tp.exit_error(e);
} else Fire.init(self.a, self.background, Widget.Box.from(self.background)) catch |e| return tp.exit_error(e);
}
};