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,16 @@ pub fn Options(context: type) type {
return struct {
label: []const u8 = "button",
pos: Widget.Box = .{ .y = 0, .x = 0, .w = 8, .h = 1 },
ctx: context = {},
ctx: Context,
on_click: *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_click: *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,
pub const Context = context;
pub fn do_nothing(_: *context, _: *State(Context)) void {}
pub fn do_nothing(_: context, _: *State(Context)) void {}
pub fn on_render_default(_: *context, self: *State(Context), theme: *const Widget.Theme) bool {
pub fn on_render_default(_: context, self: *State(Context), theme: *const Widget.Theme) bool {
tui.set_base_style(&self.plane, " ", if (self.active) theme.scrollbar_active else if (self.hover) theme.scrollbar_hover else theme.scrollbar);
self.plane.erase();
self.plane.home();
@ -28,14 +28,14 @@ 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: *State(Context)) Widget.Layout {
return .{ .static = self.opts.label.len + 2 };
}
};
}
pub fn create(ctx: anytype, a: std.mem.Allocator, parent: nc.Plane, opts: Options(@TypeOf(ctx))) !Widget {
const Self = State(@TypeOf(ctx));
pub fn create(ctx_type: type, a: std.mem.Allocator, parent: nc.Plane, opts: Options(ctx_type)) !Widget {
const Self = State(ctx_type);
const self = try a.create(Self);
var n = try nc.Plane.init(&opts.pos.opts(@typeName(Self)), parent);
errdefer n.deinit();
@ -64,27 +64,31 @@ pub fn State(ctx_type: type) type {
}
pub fn layout(self: *Self) Widget.Layout {
return self.opts.on_layout(&self.opts.ctx, self);
return self.opts.on_layout(self.opts.ctx, self);
}
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
return self.opts.on_render(&self.opts.ctx, self, theme);
return self.opts.on_render(self.opts.ctx, self, theme);
}
pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "B", nc.event_type.PRESS, nc.key.BUTTON1, tp.any, tp.any, tp.any, tp.any, tp.any })) {
self.active = true;
tui.need_render();
return true;
} else if (try m.match(.{ "B", nc.event_type.RELEASE, nc.key.BUTTON1, tp.any, tp.any, tp.any, tp.any, tp.any })) {
self.opts.on_click(&self.opts.ctx, self);
self.opts.on_click(self.opts.ctx, self);
self.active = false;
tui.need_render();
return true;
} else if (try m.match(.{ "D", nc.event_type.RELEASE, nc.key.BUTTON1, tp.any, tp.any, tp.any, tp.any, tp.any })) {
self.opts.on_click(&self.opts.ctx, self);
self.opts.on_click(self.opts.ctx, self);
self.active = false;
tui.need_render();
return true;
} else if (try m.match(.{ "H", tp.extract(&self.hover) })) {
tui.current().request_mouse_cursor_pointer(self.hover);
tui.need_render();
return true;
}
return false;

View file

@ -7,70 +7,102 @@ const WidgetList = @import("WidgetList.zig");
const Button = @import("Button.zig");
const tui = @import("tui.zig");
a: std.mem.Allocator,
menu: *WidgetList,
menu_widget: Widget,
pub fn Options(context: type) type {
return struct {
ctx: Context,
const Self = @This();
on_click: *const fn (ctx: context, button: *Button.State(*State(Context))) void = do_nothing,
on_render: *const fn (ctx: context, button: *Button.State(*State(Context)), theme: *const Widget.Theme) bool = on_render_default,
on_layout: *const fn (ctx: context, button: *Button.State(*State(Context))) Widget.Layout = on_layout_default,
pub fn create(a: std.mem.Allocator, parent: Widget) !*Self {
const self: *Self = try a.create(Self);
pub const Context = context;
pub fn do_nothing(_: context, _: *Button.State(*State(Context))) void {}
pub fn on_render_default(_: context, button: *Button.State(*State(Context)), 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_TRANSPARENT, bg_alpha);
button.plane.erase();
button.plane.home();
_ = button.plane.print(" {s} ", .{button.opts.label}) catch {};
return false;
}
pub fn on_layout_default(_: context, _: *Button.State(*State(Context))) Widget.Layout {
return .{ .static = 1 };
}
};
}
pub fn create(ctx_type: type, a: std.mem.Allocator, parent: Widget, opts: Options(ctx_type)) !*State(ctx_type) {
const self = try a.create(State(ctx_type));
self.* = .{
.a = a,
.menu = try WidgetList.createV(a, parent, @typeName(Self), .dynamic),
.menu = try WidgetList.createV(a, parent, @typeName(@This()), .dynamic),
.menu_widget = self.menu.widget(),
.opts = opts,
};
return self;
}
pub fn add_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.menu.parent, .{
.on_layout = menu_layout,
.label = label,
.on_click = on_click,
.on_render = render_menu_item,
}));
}
pub fn State(ctx_type: type) type {
return struct {
a: std.mem.Allocator,
menu: *WidgetList,
menu_widget: Widget,
opts: Options(ctx_type),
pub fn deinit(self: *Self, a: std.mem.Allocator) void {
const Self = @This();
pub fn deinit(self: *Self, a: std.mem.Allocator) void {
self.menu.deinit(a);
a.destroy(self);
}
}
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
pub fn add_item(self: *Self, label: []const u8) !void {
try self.menu.add(try Button.create(*Self, self.a, self.menu.parent, .{
.ctx = self,
.on_layout = self.opts.on_layout,
.label = label,
.on_click = self.opts.on_click,
.on_render = self.opts.on_render,
}));
}
pub fn add_item_with_handler(self: *Self, label: []const u8, on_click: *const fn (_: *Self, _: *Button.State(*Self)) void) !void {
try self.menu.add(try Button.create(*Self, self.a, self.menu.parent, .{
.ctx = self,
.on_layout = on_layout,
.label = label,
.on_click = on_click,
.on_render = on_render,
}));
}
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
return self.menu.render(theme);
}
}
pub fn resize(self: *Self, box_: Widget.Box) void {
pub fn on_layout(self: *Self, button: *Button.State(*Self)) 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 {
return self.opts.on_render(self.opts.ctx, button, theme);
}
pub fn resize(self: *Self, box_: Widget.Box) void {
var box = box_;
box.h = self.menu.widgets.items.len;
self.menu.resize(box);
}
}
pub fn update(self: *Self) void {
pub fn update(self: *Self) void {
self.menu.update();
}
}
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool {
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool {
return self.menu.walk(walk_ctx, f, &self.menu_widget);
}
fn menu_layout(_: *void, _: *Button.State(void)) Widget.Layout {
return .{ .static = 1 };
}
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;
}
};
}

View file

@ -25,22 +25,27 @@ pub fn deinit(self: *Self) void {
self.widgets.deinit();
}
pub fn addWidget(self: *Self, widget: Widget) !void {
pub fn add(self: *Self, widget: Widget) !void {
(try self.widgets.addOne()).* = widget;
}
pub fn swapWidget(self: *Self, n: usize, widget: Widget) Widget {
pub fn swap(self: *Self, n: usize, widget: Widget) Widget {
const old = self.widgets.items[n];
self.widgets.items[n] = widget;
return old;
}
pub fn replaceWidget(self: *Self, n: usize, widget: Widget) void {
pub fn replace(self: *Self, n: usize, widget: Widget) void {
const old = self.swapWidget(n, widget);
old.deinit(self.a);
}
pub fn deleteWidget(self: *Self, name: []const u8) bool {
pub fn remove(self: *Self, w: Widget) void {
for (self.widgets.items, 0..) |p, i| if (p.ptr == w.ptr)
self.widgets.orderedRemove(i).deinit(self.a);
}
pub fn delete(self: *Self, name: []const u8) bool {
for (self.widgets.items, 0..) |*widget, i| {
var buf: [64]u8 = undefined;
const wname = widget.name(&buf);
@ -52,7 +57,7 @@ pub fn deleteWidget(self: *Self, name: []const u8) bool {
return false;
}
pub fn findWidget(self: *Self, name: []const u8) ?*Widget {
pub fn find(self: *Self, name: []const u8) ?*Widget {
for (self.widgets.items) |*widget| {
var buf: [64]u8 = undefined;
const wname = widget.name(&buf);

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);
}
};

View file

@ -87,10 +87,9 @@ pub fn update(self: *Self) void {
}
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
var more = self.widgets.render(theme);
if (self.floating_views.render(theme))
more = true;
return more;
const widgets_more = self.widgets.render(theme);
const views_more = self.floating_views.render(theme);
return widgets_more or views_more;
}
pub fn resize(self: *Self) void {

View file

@ -72,6 +72,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result {
if (self.leader) |_| return self.mapFollower(keynormal, egc, modifiers);
return switch (modifiers) {
mod.CTRL => switch (keynormal) {
'E' => self.cmd("enter_overlay_mode", command.fmt(.{"open_recent"})),
'J' => self.cmd("toggle_logview", .{}),
'Z' => self.cmd("undo", .{}),
'Y' => self.cmd("redo", .{}),

View file

@ -0,0 +1,108 @@
const std = @import("std");
const nc = @import("notcurses");
const tp = @import("thespian");
const root = @import("root");
const tui = @import("../../tui.zig");
const command = @import("../../command.zig");
const EventHandler = @import("../../EventHandler.zig");
const Button = @import("../../Button.zig");
const Menu = @import("../../Menu.zig");
const mainview = @import("../../mainview.zig");
const Self = @This();
a: std.mem.Allocator,
f: usize = 0,
menu: *Menu.State(*Self),
pub fn create(a: std.mem.Allocator) !tui.Mode {
const mv = if (tui.current().mainview.dynamic_cast(mainview)) |mv_| mv_ else return error.NotFound;
const self: *Self = try a.create(Self);
self.* = .{
.a = a,
.menu = try Menu.create(*Self, a, tui.current().mainview, .{ .ctx = self }),
};
try self.menu.add_item_with_handler("open help", menu_action_help);
self.menu.resize(.{ .y = 0, .x = 25, .w = 32 });
try mv.floating_views.add(self.menu.menu_widget);
return .{
.handler = EventHandler.to_owned(self),
.name = "󰈞 open recent",
.description = "open recent",
};
}
pub fn deinit(self: *Self) void {
if (tui.current().mainview.dynamic_cast(mainview)) |mv|
mv.floating_views.remove(self.menu.menu_widget);
self.a.destroy(self);
}
fn menu_action_help(_: *Menu.State(*Self), _: *Button.State(*Menu.State(*Self))) void {
command.executeName("open_help", .{}) catch {};
}
pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
var evtype: u32 = undefined;
var keypress: u32 = undefined;
var modifiers: u32 = undefined;
if (try m.match(.{ "I", tp.extract(&evtype), tp.extract(&keypress), tp.any, tp.string, tp.extract(&modifiers) })) {
try self.mapEvent(evtype, keypress, modifiers);
}
return false;
}
fn mapEvent(self: *Self, evtype: u32, keypress: u32, modifiers: u32) tp.result {
return switch (evtype) {
nc.event_type.PRESS => self.mapPress(keypress, modifiers),
else => {},
};
}
fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result {
const keynormal = if ('a' <= keypress and keypress <= 'z') keypress - ('a' - 'A') else keypress;
return switch (modifiers) {
nc.mod.CTRL => switch (keynormal) {
'J' => self.cmd("toggle_logview", .{}),
'Q' => self.cmd("quit", .{}),
'W' => self.cmd("close_file", .{}),
else => {},
},
nc.mod.CTRL | nc.mod.SHIFT => switch (keynormal) {
'Q' => self.cmd("quit_without_saving", .{}),
'R' => self.cmd("restart", .{}),
'L' => self.cmd_async("toggle_logview"),
'I' => self.cmd_async("toggle_inputview"),
else => {},
},
nc.mod.ALT => switch (keynormal) {
'L' => self.cmd("toggle_logview", .{}),
'I' => self.cmd("toggle_inputview", .{}),
else => {},
},
0 => switch (keypress) {
nc.key.F09 => self.cmd("theme_prev", .{}),
nc.key.F10 => self.cmd("theme_next", .{}),
nc.key.F11 => self.cmd("toggle_logview", .{}),
nc.key.F12 => self.cmd("toggle_inputview", .{}),
nc.key.ESC => self.cmd("exit_overlay_mode", .{}),
nc.key.ENTER => self.cmd("exit_overlay_mode", .{}),
else => {},
},
else => {},
};
}
fn cmd(_: *Self, name_: []const u8, ctx: command.Context) tp.result {
try command.executeName(name_, ctx);
}
fn msg(_: *Self, text: []const u8) tp.result {
return tp.self_pid().send(.{ "log", "home", text });
}
fn cmd_async(_: *Self, name_: []const u8) tp.result {
return tp.self_pid().send(.{ "cmd", name_ });
}

View file

@ -14,7 +14,8 @@ const ed = @import("../editor.zig");
const tui = @import("../tui.zig");
pub fn create(a: Allocator, parent: nc.Plane) !Widget {
return Button.create({}, a, parent, .{
return Button.create(void, a, parent, .{
.ctx = {},
.label = tui.get_mode(),
.on_click = on_click,
.on_layout = layout,
@ -22,7 +23,7 @@ pub fn create(a: Allocator, parent: nc.Plane) !Widget {
});
}
pub fn layout(_: *void, _: *Button.State(void)) Widget.Layout {
pub fn layout(_: void, _: *Button.State(void)) Widget.Layout {
const name = tui.get_mode();
const width = Buffer.egc_chunk_width(name, 0);
const padding: usize = if (is_mini_mode()) 3 else 2;
@ -33,7 +34,7 @@ fn is_mini_mode() bool {
return if (tui.current().mini_mode) |_| true else false;
}
pub fn render(state: *void, self: *Button.State(void), theme: *const Widget.Theme) bool {
pub fn render(_: void, self: *Button.State(void), theme: *const Widget.Theme) bool {
tui.set_base_style(&self.plane, " ", if (self.active) theme.editor_cursor else if (self.hover) theme.editor_selection else theme.statusbar_hover);
self.plane.on_styles(nc.style.bold);
self.plane.erase();
@ -41,16 +42,16 @@ pub fn render(state: *void, self: *Button.State(void), theme: *const Widget.Them
var buf: [31:0]u8 = undefined;
_ = self.plane.putstr(std.fmt.bufPrintZ(&buf, " {s} ", .{tui.get_mode()}) catch return false) catch {};
if (is_mini_mode())
render_separator(state, self, theme);
render_separator(self, theme);
return false;
}
fn render_separator(_: *void, self: *Button.State(void), theme: *const Widget.Theme) void {
fn render_separator(self: *Button.State(void), theme: *const Widget.Theme) void {
if (theme.statusbar_hover.bg) |bg| self.plane.set_fg_rgb(bg) catch {};
if (theme.statusbar.bg) |bg| self.plane.set_bg_rgb(bg) catch {};
_ = self.plane.putstr("") catch {};
}
fn on_click(_: *void, _: *Button.State(void)) void {
fn on_click(_: void, _: *Button.State(void)) void {
command.executeName("toggle_input_mode", .{}) catch {};
}

View file

@ -692,6 +692,10 @@ const cmds = struct {
return tp.exit_error(error.InvalidArgument);
if (self.mini_mode) |_| try cmds.exit_mini_mode(self, .{});
if (self.input_mode) |*m| m.deinit();
if (self.input_mode_outer) |*m| {
m.deinit();
self.input_mode_outer = null;
}
self.input_mode = if (std.mem.eql(u8, mode, "vim/normal"))
@import("mode/input/vim/normal.zig").create(self.a) catch |e| return tp.exit_error(e)
else if (std.mem.eql(u8, mode, "vim/insert"))
@ -713,6 +717,34 @@ const cmds = struct {
return enter_mode(self, Ctx.fmt(.{self.config.input_mode}));
}
pub fn enter_overlay_mode(self: *Self, ctx: Ctx) tp.result {
var mode: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&mode)}))
return tp.exit_error(error.InvalidArgument);
if (self.mini_mode) |_| try cmds.exit_mini_mode(self, .{});
if (self.input_mode_outer) |*m| {
m.deinit();
self.input_mode_outer = null;
}
self.input_mode = if (std.mem.eql(u8, mode, "open_recent")) ret: {
self.input_mode_outer = self.input_mode;
break :ret @import("mode/overlay/open_recent.zig").create(self.a) catch |e| return tp.exit_error(e);
} else {
self.logger.print("unknown mode {s}", .{mode});
return;
};
self.logger.print("input mode: {s}", .{(self.input_mode orelse return).description});
}
pub fn exit_overlay_mode(self: *Self, _: Ctx) tp.result {
if (self.input_mode_outer) |_| {} else return;
defer {
self.input_mode = self.input_mode_outer;
self.input_mode_outer = null;
}
if (self.input_mode) |*mode| mode.deinit();
}
pub fn enter_find_mode(self: *Self, ctx: Ctx) tp.result {
return enter_mini_mode(self, @import("mode/mini/find.zig"), ctx);
}
@ -755,6 +787,7 @@ const cmds = struct {
if (self.mini_mode) |_| {} else return;
defer {
self.input_mode = self.input_mode_outer;
self.input_mode_outer = null;
self.mini_mode = null;
}
if (self.input_mode) |*mode| mode.deinit();
@ -919,6 +952,21 @@ pub inline fn set_base_style(plane: *const nc.Plane, egc: [*c]const u8, style: W
_ = plane.set_base(egc, 0, channels) catch {};
}
pub fn set_base_style_alpha(plane: nc.Plane, egc: [*:0]const u8, style: Widget.Theme.Style, fg_alpha: c_uint, bg_alpha: c_uint) !void {
var channels: u64 = 0;
if (style.fg) |fg| {
nc.channels_set_fg_rgb(&channels, fg) catch {};
nc.channels_set_fg_alpha(&channels, fg_alpha) catch {};
}
if (style.bg) |bg| {
nc.channels_set_bg_rgb(&channels, bg) catch {};
nc.channels_set_bg_alpha(&channels, bg_alpha) catch {};
}
if (style.fg) |fg| plane.set_fg_rgb(fg) catch {};
if (style.bg) |bg| plane.set_bg_rgb(bg) catch {};
_ = plane.set_base(egc, 0, channels) catch {};
}
pub inline fn set_style(plane: *const nc.Plane, style: Widget.Theme.Style) void {
var channels: u64 = 0;
channels_from_style(&channels, style);