diff --git a/src/tui/Button.zig b/src/tui/Button.zig index 18f9730..de07f9f 100644 --- a/src/tui/Button.zig +++ b/src/tui/Button.zig @@ -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; diff --git a/src/tui/Menu.zig b/src/tui/Menu.zig index 5d88439..4a55920 100644 --- a/src/tui/Menu.zig +++ b/src/tui/Menu.zig @@ -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 { - self.menu.deinit(a); - a.destroy(self); -} + const Self = @This(); -pub fn render(self: *Self, theme: *const Widget.Theme) bool { - return self.menu.render(theme); -} + pub fn deinit(self: *Self, a: std.mem.Allocator) void { + self.menu.deinit(a); + a.destroy(self); + } -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 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 update(self: *Self) void { - self.menu.update(); -} + 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 walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool { - return self.menu.walk(walk_ctx, f, &self.menu_widget); -} + pub fn render(self: *Self, theme: *const Widget.Theme) bool { + return self.menu.render(theme); + } -fn menu_layout(_: *void, _: *Button.State(void)) Widget.Layout { - return .{ .static = 1 }; -} + pub fn on_layout(self: *Self, button: *Button.State(*Self)) Widget.Layout { + return self.opts.on_layout(self.opts.ctx, button); + } -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; + 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 { + self.menu.update(); + } + + pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool { + return self.menu.walk(walk_ctx, f, &self.menu_widget); + } + }; } diff --git a/src/tui/WidgetStack.zig b/src/tui/WidgetStack.zig index 6db8361..f2417e7 100644 --- a/src/tui/WidgetStack.zig +++ b/src/tui/WidgetStack.zig @@ -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); diff --git a/src/tui/home.zig b/src/tui/home.zig index 780c024..c576a3e 100644 --- a/src/tui/home.zig +++ b/src/tui/home.zig @@ -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); } }; diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 1a86ca6..b54bea4 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -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 { diff --git a/src/tui/mode/input/flow.zig b/src/tui/mode/input/flow.zig index 641defa..184b2ce 100644 --- a/src/tui/mode/input/flow.zig +++ b/src/tui/mode/input/flow.zig @@ -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", .{}), diff --git a/src/tui/mode/overlay/open_recent.zig b/src/tui/mode/overlay/open_recent.zig new file mode 100644 index 0000000..bc16069 --- /dev/null +++ b/src/tui/mode/overlay/open_recent.zig @@ -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_ }); +} diff --git a/src/tui/status/modestate.zig b/src/tui/status/modestate.zig index f6eafde..87d7347 100644 --- a/src/tui/status/modestate.zig +++ b/src/tui/status/modestate.zig @@ -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 {}; } diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 236584f..6418fae 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -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);