From b15fa47f3023970548083e56e8bfefd31cc55158 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Thu, 25 Apr 2024 22:45:02 +0200 Subject: [PATCH] refactor: add renderer abstraction layer --- build.zig | 18 +- src/buffer/Buffer.zig | 38 +- src/renderer/notcurses/Cell.zig | 38 ++ src/renderer/notcurses/Plane.zig | 247 +++++++++++ src/renderer/notcurses/channels.zig | 40 ++ src/renderer/notcurses/egc.zig | 29 ++ src/renderer/notcurses/input.zig | 14 + src/renderer/notcurses/renderer.zig | 508 ++++++++++++++++++++++ src/renderer/notcurses/style.zig | 14 + src/tui/Box.zig | 6 +- src/tui/Button.zig | 31 +- src/tui/InputBox.zig | 25 +- src/tui/Menu.zig | 6 +- src/tui/Widget.zig | 11 +- src/tui/WidgetList.zig | 10 +- src/tui/WidgetStack.zig | 1 - src/tui/editor.zig | 117 ++--- src/tui/editor_gutter.zig | 44 +- src/tui/fonts.zig | 10 +- src/tui/home.zig | 72 ++-- src/tui/inputview.zig | 17 +- src/tui/inspector_view.zig | 28 +- src/tui/logview.zig | 14 +- src/tui/mainview.zig | 7 +- src/tui/message_box.zig | 44 -- src/tui/mode/input/flow.zig | 16 +- src/tui/mode/input/home.zig | 37 +- src/tui/mode/input/vim/insert.zig | 16 +- src/tui/mode/input/vim/normal.zig | 16 +- src/tui/mode/input/vim/visual.zig | 16 +- src/tui/mode/mini/find.zig | 16 +- src/tui/mode/mini/find_in_files.zig | 16 +- src/tui/mode/mini/goto.zig | 11 +- src/tui/mode/mini/move_to_char.zig | 12 +- src/tui/mode/mini/open_file.zig | 36 +- src/tui/mode/overlay/open_recent.zig | 64 +-- src/tui/scrollbar_v.zig | 23 +- src/tui/status/diagstate.zig | 7 +- src/tui/status/filestate.zig | 29 +- src/tui/status/keystate.zig | 40 +- src/tui/status/linenumstate.zig | 7 +- src/tui/status/minilog.zig | 15 +- src/tui/status/modestate.zig | 14 +- src/tui/status/modstate.zig | 26 +- src/tui/status/selectionstate.zig | 15 +- src/tui/status/statusbar.zig | 4 - src/tui/tui.zig | 617 +++------------------------ 47 files changed, 1419 insertions(+), 1023 deletions(-) create mode 100644 src/renderer/notcurses/Cell.zig create mode 100644 src/renderer/notcurses/Plane.zig create mode 100644 src/renderer/notcurses/channels.zig create mode 100644 src/renderer/notcurses/egc.zig create mode 100644 src/renderer/notcurses/input.zig create mode 100644 src/renderer/notcurses/renderer.zig create mode 100644 src/renderer/notcurses/style.zig delete mode 100644 src/tui/message_box.zig diff --git a/build.zig b/build.zig index ad3088b..f5c56e2 100644 --- a/build.zig +++ b/build.zig @@ -55,6 +55,7 @@ pub fn build(b: *std.Build) void { .optimize = dependency_optimize, .use_system_notcurses = use_system_notcurses, }); + const notcurses_mod = notcurses_dep.module("notcurses"); const clap_dep = b.dependency("clap", .{ .target = target, @@ -94,7 +95,6 @@ pub fn build(b: *std.Build) void { const thespian_mod = thespian_dep.module("thespian"); const cbor_mod = thespian_dep.module("cbor"); - const notcurses_mod = notcurses_dep.module("notcurses"); const help_mod = b.createModule(.{ .root_source_file = .{ .path = "help.md" }, @@ -118,10 +118,22 @@ pub fn build(b: *std.Build) void { .root_source_file = .{ .path = "src/color.zig" }, }); + const notcurses_renderer_mod = b.createModule(.{ + .root_source_file = .{ .path = "src/renderer/notcurses/renderer.zig" }, + .imports = &.{ + .{ .name = "notcurses", .module = notcurses_mod }, + .{ .name = "theme", .module = themes_dep.module("theme") }, + .{ .name = "cbor", .module = cbor_mod }, + .{ .name = "log", .module = log_mod }, + }, + }); + + const renderer_mod = notcurses_renderer_mod; + const Buffer_mod = b.createModule(.{ .root_source_file = .{ .path = "src/buffer/Buffer.zig" }, .imports = &.{ - .{ .name = "notcurses", .module = notcurses_mod }, + .{ .name = "renderer", .module = renderer_mod }, .{ .name = "cbor", .module = cbor_mod }, }, }); @@ -176,7 +188,7 @@ pub fn build(b: *std.Build) void { const tui_mod = b.createModule(.{ .root_source_file = .{ .path = "src/tui/tui.zig" }, .imports = &.{ - .{ .name = "notcurses", .module = notcurses_mod }, + .{ .name = "renderer", .module = renderer_mod }, .{ .name = "thespian", .module = thespian_mod }, .{ .name = "cbor", .module = cbor_mod }, .{ .name = "config", .module = config_mod }, diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index 3857eb3..07e68a5 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -1,10 +1,12 @@ const std = @import("std"); const builtin = @import("builtin"); -const nc = @import("notcurses"); const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const cwd = std.fs.cwd; +const egc_length = @import("renderer").egc.length; +const egc_chunk_width = @import("renderer").egc.chunk_width; + const Self = @This(); const default_leaf_capacity = 64; @@ -156,7 +158,7 @@ pub const Leaf = struct { buf = buf[1..]; pos.* -= 1; } else { - const bytes = egc_len(buf, &cols, abs_col); + const bytes = egc_length(buf, &cols, abs_col); buf = buf[bytes..]; pos.* -= bytes; } @@ -179,7 +181,7 @@ pub const Leaf = struct { return while (buf.len > 0) { if (col == 0) break @intFromPtr(buf.ptr) - @intFromPtr(self.buf.ptr); - const bytes = egc_len(buf, &cols, abs_col); + const bytes = egc_length(buf, &cols, abs_col); buf = buf[bytes..]; if (col < cols) break @intFromPtr(buf.ptr) - @intFromPtr(self.buf.ptr); @@ -212,7 +214,7 @@ pub const Leaf = struct { buf = buf[1..]; }, else => { - const bytes = egc_len(buf, &cols, 0); + const bytes = egc_length(buf, &cols, 0); var buf_: [4096]u8 = undefined; try l.appendSlice(try std.fmt.bufPrint(&buf_, "{s}", .{std.fmt.fmtSliceEscapeLower(buf[0..bytes])})); buf = buf[bytes..]; @@ -471,7 +473,7 @@ const Node = union(enum) { var buf: []const u8 = leaf.buf; while (buf.len > 0) { var cols: c_int = undefined; - const bytes = egc_len(buf, &cols, ctx.abs_col); + const bytes = egc_length(buf, &cols, ctx.abs_col); const ret = ctx.walker_f(ctx.walker_ctx, buf[0..bytes], @intCast(cols)); if (ret.err) |e| return .{ .err = e }; buf = buf[bytes..]; @@ -1142,29 +1144,3 @@ pub fn redo(self: *Self) error{Stop}![]const u8 { self.push_undo(u); return h.meta; } - -fn egc_len(egcs: []const u8, colcount: *c_int, abs_col: usize) usize { - if (egcs[0] == '\t') { - colcount.* = @intCast(8 - abs_col % 8); - return 1; - } - return nc.ncegc_len(egcs, colcount) catch ret: { - colcount.* = 1; - break :ret 1; - }; -} - -pub fn egc_chunk_width(chunk_: []const u8, abs_col_: usize) usize { - var abs_col = abs_col_; - var chunk = chunk_; - var colcount: usize = 0; - var cols: c_int = 0; - while (chunk.len > 0) { - const bytes = egc_len(chunk, &cols, abs_col); - colcount += @intCast(cols); - abs_col += @intCast(cols); - if (chunk.len < bytes) break; - chunk = chunk[bytes..]; - } - return colcount; -} diff --git a/src/renderer/notcurses/Cell.zig b/src/renderer/notcurses/Cell.zig new file mode 100644 index 0000000..f6f913c --- /dev/null +++ b/src/renderer/notcurses/Cell.zig @@ -0,0 +1,38 @@ +const nc = @import("notcurses"); +const Style = @import("theme").Style; +const channels = @import("channels.zig"); + +pub const Cell = struct { + cell: nc.Cell, + + pub inline fn set_style(cell: *Cell, style_: Style) void { + channels.from_style(&cell.cell.channels, style_); + if (style_.fs) |fs| switch (fs) { + .normal => nc.cell_set_styles(&cell.cell, nc.style.none), + .bold => nc.cell_set_styles(&cell.cell, nc.style.bold), + .italic => nc.cell_set_styles(&cell.cell, nc.style.italic), + .underline => nc.cell_set_styles(&cell.cell, nc.style.underline), + .undercurl => nc.cell_set_styles(&cell.cell, nc.style.undercurl), + .strikethrough => nc.cell_set_styles(&cell.cell, nc.style.struck), + }; + } + + pub inline fn set_style_fg(cell: *Cell, style_: Style) void { + channels.fg_from_style(&cell.cell.channels, style_); + } + + pub inline fn set_style_bg(cell: *Cell, style_: Style) void { + channels.bg_from_style(&cell.cell.channels, style_); + } + + pub inline fn set_fg_rgb(cell: *Cell, arg_rgb: c_uint) !void { + return channels.set_fg_rgb(&cell.cell.channels, arg_rgb); + } + pub inline fn set_bg_rgb(cell: *Cell, arg_rgb: c_uint) !void { + return channels.set_bg_rgb(&cell.cell.channels, arg_rgb); + } + + pub fn columns(cell: *const Cell) usize { + return nc.cell_cols(&cell.cell); + } +}; diff --git a/src/renderer/notcurses/Plane.zig b/src/renderer/notcurses/Plane.zig new file mode 100644 index 0000000..aaa6367 --- /dev/null +++ b/src/renderer/notcurses/Plane.zig @@ -0,0 +1,247 @@ +const nc = @import("notcurses"); +const Style = @import("theme").Style; +const channels = @import("channels.zig"); +const StyleBits = @import("style.zig").StyleBits; +const Cell = @import("Cell.zig").Cell; + +pub const Plane = struct { + plane: nc.Plane, + + pub const Options = struct { + y: usize = 0, + x: usize = 0, + rows: usize = 0, + cols: usize = 0, + name: [*:0]const u8, + flags: option = .none, + }; + + pub const option = enum { + none, + VSCROLL, + }; + + pub fn init(nopts: *const Options, parent_: Plane) !Plane { + var nopts_: nc.Plane.Options = .{ + .y = @intCast(nopts.y), + .x = @intCast(nopts.x), + .rows = @intCast(nopts.rows), + .cols = @intCast(nopts.cols), + .name = nopts.name, + }; + switch (nopts.flags) { + .none => {}, + .VSCROLL => nopts_.flags = nc.Plane.option.VSCROLL, + } + + return .{ .plane = nc.Plane.init(&nopts_, parent_.plane) catch |e| return e }; + } + + pub fn deinit(self: *Plane) void { + self.plane.deinit(); + } + + pub fn name(self: Plane, buf: []u8) []u8 { + return self.plane.name(buf); + } + + pub fn parent(self: Plane) Plane { + return .{ .plane = self.plane.parent() }; + } + + pub fn above(self: Plane) ?Plane { + return .{ .plane = self.plane.above() orelse return null }; + } + + pub fn below(self: Plane) ?Plane { + return .{ .plane = self.plane.below() orelse return null }; + } + + pub fn erase(self: Plane) void { + return self.plane.erase(); + } + + pub fn abs_y(self: Plane) c_int { + return self.plane.abs_y(); + } + + pub fn abs_x(self: Plane) c_int { + return self.plane.abs_x(); + } + + pub fn dim_y(self: Plane) c_uint { + return self.plane.dim_y(); + } + + pub fn dim_x(self: Plane) c_uint { + return self.plane.dim_x(); + } + + pub fn abs_yx_to_rel(self: Plane, y: ?*c_int, x: ?*c_int) void { + self.plane.abs_yx_to_rel(y, x); + } + + pub fn rel_yx_to_abs(self: Plane, y: ?*c_int, x: ?*c_int) void { + self.plane.rel_yx_to_abs(y, x); + } + + pub fn move_bottom(self: Plane) void { + self.plane.move_bottom(); + } + + pub fn move_yx(self: Plane, y: c_int, x: c_int) !void { + return self.plane.move_yx(y, x); + } + + pub fn resize_simple(self: Plane, ylen: c_uint, xlen: c_uint) !void { + return self.plane.resize_simple(ylen, xlen); + } + + pub fn home(self: Plane) void { + return self.plane.home(); + } + + pub fn print(self: Plane, comptime fmt: anytype, args: anytype) !usize { + return self.plane.print(fmt, args); + } + + pub fn print_aligned_right(self: Plane, y: c_int, comptime fmt: anytype, args: anytype) !usize { + return self.plane.print_aligned(y, .right, fmt, args); + } + + pub fn print_aligned_center(self: Plane, y: c_int, comptime fmt: anytype, args: anytype) !usize { + return self.plane.print_aligned(y, .center, fmt, args); + } + + pub fn putstr(self: Plane, gclustarr: [*:0]const u8) !usize { + return self.plane.putstr(gclustarr); + } + + pub fn putc(self: Plane, cell: *const Cell) !usize { + return self.plane.putc(&cell.cell); + } + + pub fn putc_yx(self: Plane, y: c_int, x: c_int, cell: *const Cell) !usize { + return self.plane.put_yx(y, x, &cell.cell); + } + + pub fn cursor_yx(self: Plane, y: *c_uint, x: *c_uint) void { + self.plane.cursor_yx(y, x); + } + + pub fn cursor_y(self: Plane) c_uint { + return self.plane.cursor_y(); + } + + pub fn cursor_x(self: Plane) c_uint { + return self.plane.cursor_x(); + } + + pub fn cursor_move_yx(self: Plane, y: c_int, x: c_int) !void { + return self.plane.cursor_move_yx(y, x); + } + + pub fn cursor_move_rel(self: Plane, y: c_int, x: c_int) !void { + return self.plane.cursor_move_rel(y, x); + } + + pub fn cell_init(self: Plane) Cell { + return .{ .cell = self.plane.cell_init() }; + } + + pub fn cell_load(self: Plane, cell: *Cell, gcluster: [:0]const u8) !usize { + return self.plane.cell_load(&cell.cell, gcluster); + } + + pub fn at_cursor_cell(self: Plane, cell: *Cell) !usize { + return self.plane.at_cursor_cell(&cell.cell); + } + + pub fn set_styles(self: Plane, stylebits: StyleBits) void { + return self.plane.set_styles(@intCast(@as(u5, @bitCast(stylebits)))); + } + + pub fn on_styles(self: Plane, stylebits: StyleBits) void { + return self.plane.on_styles(@intCast(@as(u5, @bitCast(stylebits)))); + } + + pub fn off_styles(self: Plane, stylebits: StyleBits) void { + return self.plane.off_styles(@intCast(@as(u5, @bitCast(stylebits)))); + } + + pub fn set_fg_rgb(self: Plane, channel: u32) !void { + return self.plane.set_fg_rgb(channel); + } + + pub fn set_bg_rgb(self: Plane, channel: u32) !void { + return self.plane.set_bg_rgb(channel); + } + + pub fn set_fg_palindex(self: Plane, idx: c_uint) !void { + return self.plane.set_fg_palindex(idx); + } + + pub fn set_bg_palindex(self: Plane, idx: c_uint) !void { + return self.plane.set_bg_palindex(idx); + } + + pub fn set_channels(self: Plane, channels_: u64) void { + return self.plane.set_channels(channels_); + } + + pub inline fn set_base_style(plane: *const Plane, egc_: [*c]const u8, style_: Style) void { + var channels_: u64 = 0; + channels.from_style(&channels_, style_); + if (style_.fg) |fg| plane.plane.set_fg_rgb(fg) catch {}; + if (style_.bg) |bg| plane.plane.set_bg_rgb(bg) catch {}; + _ = plane.plane.set_base(egc_, 0, channels_) catch {}; + } + + pub fn set_base_style_transparent(plane: Plane, egc_: [*:0]const u8, style_: Style) void { + var channels_: u64 = 0; + channels.from_style(&channels_, style_); + if (style_.fg) |fg| plane.plane.set_fg_rgb(fg) catch {}; + if (style_.bg) |bg| plane.plane.set_bg_rgb(bg) catch {}; + channels.set_fg_transparent(&channels_); + channels.set_bg_transparent(&channels_); + _ = plane.plane.set_base(egc_, 0, channels_) catch {}; + } + + pub fn set_base_style_bg_transparent(plane: Plane, egc_: [*:0]const u8, style_: Style) void { + var channels_: u64 = 0; + channels.from_style(&channels_, style_); + if (style_.fg) |fg| plane.plane.set_fg_rgb(fg) catch {}; + if (style_.bg) |bg| plane.plane.set_bg_rgb(bg) catch {}; + channels.set_bg_transparent(&channels_); + _ = plane.plane.set_base(egc_, 0, channels_) catch {}; + } + + pub inline fn set_style(plane: *const Plane, style_: Style) void { + var channels_: u64 = 0; + channels.from_style(&channels_, style_); + plane.plane.set_channels(channels_); + if (style_.fs) |fs| switch (fs) { + .normal => plane.plane.set_styles(nc.style.none), + .bold => plane.plane.set_styles(nc.style.bold), + .italic => plane.plane.set_styles(nc.style.italic), + .underline => plane.plane.set_styles(nc.style.underline), + .undercurl => plane.plane.set_styles(nc.style.undercurl), + .strikethrough => plane.plane.set_styles(nc.style.struck), + }; + } + + pub inline fn set_style_bg_transparent(plane: *const Plane, style_: Style) void { + var channels_: u64 = 0; + channels.from_style(&channels_, style_); + channels.set_bg_transparent(&channels_); + plane.plane.set_channels(channels_); + if (style_.fs) |fs| switch (fs) { + .normal => plane.plane.set_styles(nc.style.none), + .bold => plane.plane.set_styles(nc.style.bold), + .italic => plane.plane.set_styles(nc.style.italic), + .underline => plane.plane.set_styles(nc.style.underline), + .undercurl => plane.plane.set_styles(nc.style.undercurl), + .strikethrough => plane.plane.set_styles(nc.style.struck), + }; + } +}; diff --git a/src/renderer/notcurses/channels.zig b/src/renderer/notcurses/channels.zig new file mode 100644 index 0000000..5115023 --- /dev/null +++ b/src/renderer/notcurses/channels.zig @@ -0,0 +1,40 @@ +const Style = @import("theme").Style; +const nc = @import("notcurses"); + +pub const set_fg_rgb = nc.channels_set_fg_rgb; +pub const set_bg_rgb = nc.channels_set_bg_rgb; + +pub fn set_fg_opaque(channels_: *u64) void { + nc.channels_set_fg_alpha(channels_, nc.ALPHA_OPAQUE) catch {}; +} + +pub fn set_bg_opaque(channels_: *u64) void { + nc.channels_set_bg_alpha(channels_, nc.ALPHA_OPAQUE) catch {}; +} + +pub fn set_fg_transparent(channels_: *u64) void { + nc.channels_set_fg_alpha(channels_, nc.ALPHA_TRANSPARENT) catch {}; +} + +pub fn set_bg_transparent(channels_: *u64) void { + nc.channels_set_bg_alpha(channels_, nc.ALPHA_TRANSPARENT) catch {}; +} + +pub inline fn fg_from_style(channels_: *u64, style_: Style) void { + if (style_.fg) |fg| { + set_fg_rgb(channels_, fg) catch {}; + set_fg_opaque(channels_); + } +} + +pub inline fn bg_from_style(channels_: *u64, style_: Style) void { + if (style_.bg) |bg| { + set_bg_rgb(channels_, bg) catch {}; + set_bg_opaque(channels_); + } +} + +pub inline fn from_style(channels_: *u64, style_: Style) void { + fg_from_style(channels_, style_); + bg_from_style(channels_, style_); +} diff --git a/src/renderer/notcurses/egc.zig b/src/renderer/notcurses/egc.zig new file mode 100644 index 0000000..746ea84 --- /dev/null +++ b/src/renderer/notcurses/egc.zig @@ -0,0 +1,29 @@ +const nc = @import("notcurses"); + +pub fn length(egcs: []const u8, colcount: *c_int, abs_col: usize) usize { + if (egcs[0] == '\t') { + colcount.* = @intCast(8 - abs_col % 8); + return 1; + } + return nc.ncegc_len(egcs, colcount) catch ret: { + colcount.* = 1; + break :ret 1; + }; +} + +pub fn chunk_width(chunk_: []const u8, abs_col_: usize) usize { + var abs_col = abs_col_; + var chunk = chunk_; + var colcount: usize = 0; + var cols: c_int = 0; + while (chunk.len > 0) { + const bytes = length(chunk, &cols, abs_col); + colcount += @intCast(cols); + abs_col += @intCast(cols); + if (chunk.len < bytes) break; + chunk = chunk[bytes..]; + } + return colcount; +} + +pub const ucs32_to_utf8 = nc.ucs32_to_utf8; diff --git a/src/renderer/notcurses/input.zig b/src/renderer/notcurses/input.zig new file mode 100644 index 0000000..4c4e7e2 --- /dev/null +++ b/src/renderer/notcurses/input.zig @@ -0,0 +1,14 @@ +const nc = @import("notcurses"); + +pub const key = nc.key; +pub const modifier = nc.mod; +pub const event_type = nc.event_type; + +pub const utils = struct { + pub const isSuper = nc.isSuper; + pub const isCtrl = nc.isCtrl; + pub const isShift = nc.isShift; + pub const isAlt = nc.isAlt; + pub const key_id_string = nc.key_id_string; + pub const key_string = nc.key_string; +}; diff --git a/src/renderer/notcurses/renderer.zig b/src/renderer/notcurses/renderer.zig new file mode 100644 index 0000000..9c2bc64 --- /dev/null +++ b/src/renderer/notcurses/renderer.zig @@ -0,0 +1,508 @@ +const std = @import("std"); +const cbor = @import("cbor"); +const log = @import("log"); +const nc = @import("notcurses"); +const Style = @import("theme").Style; + +pub const egc = @import("egc.zig"); +pub const input = @import("input.zig"); + +pub const Plane = @import("Plane.zig").Plane; +pub const Cell = @import("Cell.zig").Cell; +pub const channels = @import("channels.zig"); + +pub const style = @import("style.zig").StyleBits; + +const mod = input.modifier; +const key = input.key; +const event_type = input.event_type; + +const Self = @This(); +const log_name = "ncrender"; + +a: std.mem.Allocator, +ctx: nc.Context, + +escape_state: EscapeState = .none, +escape_initial: ?nc.Input = null, +escape_code: std.ArrayList(u8), + +event_buffer: std.ArrayList(u8), +mods: ModState = .{}, +drag: bool = false, +drag_event: nc.Input = nc.input(), +bracketed_paste: bool = false, +bracketed_paste_buffer: std.ArrayList(u8), + +handler_ctx: *anyopaque, +dispatch_input: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null, +dispatch_mouse: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void = null, +dispatch_mouse_drag: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, dragging: bool, cbor_msg: []const u8) void = null, +dispatch_event: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null, + +logger: log.Logger, + +const EscapeState = enum { none, init, OSC, st, CSI }; +const ModState = struct { ctrl: bool = false, shift: bool = false, alt: bool = false }; + +pub fn init(a: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool) !Self { + var opts = nc.Context.Options{ + .termtype = null, + .loglevel = @intFromEnum(nc.LogLevel.silent), + .margin_t = 0, + .margin_r = 0, + .margin_b = 0, + .margin_l = 0, + .flags = nc.Context.option.SUPPRESS_BANNERS | nc.Context.option.INHIBIT_SETLOCALE | nc.Context.option.NO_WINCH_SIGHANDLER, + }; + if (no_alternate) + opts.flags |= nc.Context.option.NO_ALTERNATE_SCREEN; + const nc_ = try nc.Context.core_init(&opts, null); + nc_.mice_enable(nc.mice.ALL_EVENTS) catch {}; + try nc_.linesigs_disable(); + bracketed_paste_enable(); + + return .{ + .a = a, + .ctx = nc_, + .escape_code = std.ArrayList(u8).init(a), + .event_buffer = std.ArrayList(u8).init(a), + .bracketed_paste_buffer = std.ArrayList(u8).init(a), + .handler_ctx = handler_ctx, + .logger = log.logger(log_name), + }; +} + +pub fn deinit(self: *Self) void { + self.escape_code.deinit(); + self.event_buffer.deinit(); + self.bracketed_paste_buffer.deinit(); +} + +pub fn render(self: Self) !void { + return self.ctx.render(); +} + +pub fn refresh(self: Self) !void { + return self.ctx.refresh(); +} + +pub fn stop(self: Self) void { + return self.ctx.stop(); +} + +pub fn stdplane(self: Self) Plane { + return .{ .plane = self.ctx.stdplane() }; +} + +pub fn input_fd(self: Self) i32 { + return self.ctx.inputready_fd(); +} + +pub fn leave_alternate_screen(self: Self) void { + return self.ctx.leave_alternate_screen(); +} + +pub fn process_input(self: *Self) !void { + var input_buffer: [256]nc.Input = undefined; + + while (true) { + const nivec = try self.ctx.getvec_nblock(&input_buffer); + if (nivec.len == 0) + break; + for (nivec) |*ni| { + if (ni.id == 27 or self.escape_state != .none) { + try self.handle_escape(ni); + continue; + } + self.dispatch_input_event(ni) catch |e| + self.logger.err("input dispatch", e); + } + } + if (self.escape_state == .init) + try self.handle_escape_short(); +} + +fn fmtmsg(self: *Self, value: anytype) ![]const u8 { + self.event_buffer.clearRetainingCapacity(); + try cbor.writeValue(self.event_buffer.writer(), value); + return self.event_buffer.items; +} + +fn dispatch_input_event(self: *Self, ni: *nc.Input) !void { + const keypress: u32 = ni.id; + ni.modifiers &= mod.CTRL | mod.SHIFT | mod.ALT | mod.SUPER | mod.META | mod.HYPER; + if (keypress == key.RESIZE) return; + try self.sync_mod_state(keypress, ni.modifiers); + if (keypress == key.MOTION) { + if (ni.y == 0 and ni.x == 0 and ni.ypx == -1 and ni.xpx == -1) return; + if (self.dispatch_mouse) |f| f( + self.handler_ctx, + ni.y, + ni.x, + try self.fmtmsg(.{ + "M", + ni.x, + ni.y, + ni.xpx, + ni.ypx, + }), + ); + } else if (keypress > key.MOTION and keypress <= key.BUTTON11) { + if (ni.y == 0 and ni.x == 0 and ni.ypx == -1 and ni.xpx == -1) return; + if (try self.detect_drag(ni)) return; + if (self.dispatch_mouse) |f| f( + self.handler_ctx, + ni.y, + ni.x, + try self.fmtmsg(.{ + "B", + ni.evtype, + keypress, + input.utils.key_string(ni), + ni.x, + ni.y, + ni.xpx, + ni.ypx, + }), + ); + } else { + const cbor_msg = try self.fmtmsg(.{ + "I", + normalized_evtype(ni.evtype), + keypress, + if (@hasField(nc.Input, "eff_text")) ni.eff_text[0] else keypress, + input.utils.key_string(ni), + ni.modifiers, + }); + if (self.bracketed_paste and self.handle_bracketed_paste_input(cbor_msg) catch |e| { + self.bracketed_paste_buffer.clearAndFree(); + self.bracketed_paste = false; + return e; + }) {} else if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg); + } +} + +fn handle_bracketed_paste_input(self: *Self, cbor_msg: []const u8) !bool { + var keypress: u32 = undefined; + var egc_: u32 = undefined; + if (try cbor.match(cbor_msg, .{ "I", cbor.number, cbor.extract(&keypress), cbor.extract(&egc_), cbor.string, 0 })) { + switch (keypress) { + key.ENTER => try self.bracketed_paste_buffer.appendSlice("\n"), + else => if (!key.synthesized_p(keypress)) { + var buf: [6]u8 = undefined; + const bytes = try egc.ucs32_to_utf8(&[_]u32{egc_}, &buf); + try self.bracketed_paste_buffer.appendSlice(buf[0..bytes]); + } else { + try self.handle_bracketed_paste_end(); + return false; + }, + } + return true; + } + return false; +} + +fn normalized_evtype(evtype: c_uint) c_uint { + return if (evtype == event_type.UNKNOWN) @as(c_uint, @intCast(event_type.PRESS)) else evtype; +} + +fn sync_mod_state(self: *Self, keypress: u32, modifiers: u32) !void { + if (input.utils.isCtrl(modifiers) and !self.mods.ctrl and !(keypress == key.LCTRL or keypress == key.RCTRL)) + try self.send_sync_key(event_type.PRESS, key.LCTRL, "lctrl", modifiers); + if (!input.utils.isCtrl(modifiers) and self.mods.ctrl and !(keypress == key.LCTRL or keypress == key.RCTRL)) + try self.send_sync_key(event_type.RELEASE, key.LCTRL, "lctrl", modifiers); + if (input.utils.isAlt(modifiers) and !self.mods.alt and !(keypress == key.LALT or keypress == key.RALT)) + try self.send_sync_key(event_type.PRESS, key.LALT, "lalt", modifiers); + if (!input.utils.isAlt(modifiers) and self.mods.alt and !(keypress == key.LALT or keypress == key.RALT)) + try self.send_sync_key(event_type.RELEASE, key.LALT, "lalt", modifiers); + if (input.utils.isShift(modifiers) and !self.mods.shift and !(keypress == key.LSHIFT or keypress == key.RSHIFT)) + try self.send_sync_key(event_type.PRESS, key.LSHIFT, "lshift", modifiers); + if (!input.utils.isShift(modifiers) and self.mods.shift and !(keypress == key.LSHIFT or keypress == key.RSHIFT)) + try self.send_sync_key(event_type.RELEASE, key.LSHIFT, "lshift", modifiers); + self.mods = .{ + .ctrl = input.utils.isCtrl(modifiers), + .alt = input.utils.isAlt(modifiers), + .shift = input.utils.isShift(modifiers), + }; +} + +fn send_sync_key(self: *Self, event_type_: c_int, keypress: u32, key_string: []const u8, modifiers: u32) !void { + if (self.dispatch_input) |f| f( + self.handler_ctx, + try self.fmtmsg(.{ + "I", + event_type_, + keypress, + keypress, + key_string, + modifiers, + }), + ); +} + +fn detect_drag(self: *Self, ni: *nc.Input) !bool { + return switch (ni.id) { + key.BUTTON1...key.BUTTON3, key.BUTTON6...key.BUTTON9 => if (self.drag) self.detect_drag_end(ni) else self.detect_drag_begin(ni), + else => false, + }; +} + +fn detect_drag_begin(self: *Self, ni: *nc.Input) !bool { + if (ni.evtype == event_type.PRESS and self.drag_event.id == ni.id) { + self.drag_event = ni.*; + self.drag = true; + if (self.dispatch_mouse_drag) |f| f( + self.handler_ctx, + ni.y, + ni.x, + true, + try self.fmtmsg(.{ + "D", + event_type.PRESS, + ni.id, + input.utils.key_string(ni), + ni.x, + ni.y, + ni.xpx, + ni.ypx, + }), + ); + return true; + } + if (ni.evtype == event_type.PRESS) + self.drag_event = ni.* + else + self.drag_event = nc.input(); + return false; +} + +fn detect_drag_end(self: *Self, ni: *nc.Input) !bool { + if (ni.id == self.drag_event.id and ni.evtype != event_type.PRESS) { + if (self.dispatch_mouse_drag) |f| f( + self.handler_ctx, + ni.y, + ni.x, + false, + try self.fmtmsg(.{ + "D", + event_type.RELEASE, + ni.id, + input.utils.key_string(ni), + ni.x, + ni.y, + ni.xpx, + ni.ypx, + }), + ); + self.drag = false; + self.drag_event = nc.input(); + } else if (self.dispatch_mouse_drag) |f| f( + self.handler_ctx, + ni.y, + ni.x, + true, + try self.fmtmsg(.{ + "D", + ni.evtype, + ni.id, + input.utils.key_string(ni), + ni.x, + ni.y, + ni.xpx, + ni.ypx, + }), + ); + return true; +} + +fn handle_escape(self: *Self, ni: *nc.Input) !void { + switch (self.escape_state) { + .none => switch (ni.id) { + '\x1B' => { + self.escape_state = .init; + self.escape_initial = ni.*; + }, + else => unreachable, + }, + .init => switch (ni.id) { + ']' => self.escape_state = .OSC, + '[' => self.escape_state = .CSI, + else => { + try self.handle_escape_short(); + _ = try self.dispatch_input_event(ni); + }, + }, + .OSC => switch (ni.id) { + '\x1B' => self.escape_state = .st, + '\\' => try self.handle_OSC_escape_code(), + ' '...'\\' - 1, '\\' + 1...127 => { + const p = try self.escape_code.addOne(); + p.* = @intCast(ni.id); + }, + else => try self.handle_OSC_escape_code(), + }, + .st => switch (ni.id) { + '\\' => try self.handle_OSC_escape_code(), + else => try self.handle_OSC_escape_code(), + }, + .CSI => switch (ni.id) { + '0'...'9', ';', ' ', '-', '?' => { + const p = try self.escape_code.addOne(); + p.* = @intCast(ni.id); + }, + else => { + const p = try self.escape_code.addOne(); + p.* = @intCast(ni.id); + try self.handle_CSI_escape_code(); + }, + }, + } +} + +fn handle_escape_short(self: *Self) !void { + self.escape_code.clearAndFree(); + self.escape_state = .none; + defer self.escape_initial = null; + if (self.escape_initial) |*ni| + _ = try self.dispatch_input_event(ni); +} + +fn match_code(self: Self, match: []const u8, skip: usize) bool { + const code = self.escape_code.items; + if (!(code.len >= match.len - skip)) return false; + const code_prefix = code[0 .. match.len - skip]; + return std.mem.eql(u8, match[skip..], code_prefix); +} + +const OSC = "\x1B]"; // Operating System Command +const ST = "\x1B\\"; // String Terminator +const BEL = "\x07"; +const OSC0_title = OSC ++ "0;"; +const OSC52_clipboard = OSC ++ "52;c;"; +const OSC52_clipboard_paste = OSC ++ "52;p;"; +const OSC22_cursor = OSC ++ "22;"; +const OSC22_cursor_reply = OSC ++ "22:"; + +fn handle_OSC_escape_code(self: *Self) !void { + self.escape_state = .none; + self.escape_initial = null; + defer self.escape_code.clearAndFree(); + const code = self.escape_code.items; + if (self.match_code(OSC52_clipboard, OSC.len)) + return self.handle_system_clipboard(code[OSC52_clipboard.len - OSC.len ..]); + if (self.match_code(OSC52_clipboard_paste, OSC.len)) + return self.handle_system_clipboard(code[OSC52_clipboard_paste.len - OSC.len ..]); + if (self.match_code(OSC22_cursor_reply, OSC.len)) + return self.handle_mouse_cursor(code[OSC22_cursor_reply.len - OSC.len ..]); + self.logger.print("ignored escape code: OSC {s}", .{std.fmt.fmtSliceEscapeLower(code)}); +} + +fn handle_system_clipboard(self: *Self, base64: []const u8) !void { + const decoder = std.base64.standard.Decoder; + const text = try self.a.alloc(u8, try decoder.calcSizeForSlice(base64)); + defer self.a.free(text); + try decoder.decode(text, base64); + if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{ "system_clipboard", text })); +} + +fn handle_mouse_cursor(self: Self, text: []const u8) !void { + self.logger.print("mouse cursor report: {s}", .{text}); +} + +const CSI = "\x1B["; // Control Sequence Introducer +const CSI_bracketed_paste_enable = CSI ++ "?2004h"; +const CSI_bracketed_paste_disable = CSI ++ "?2004h"; +const CIS_bracketed_paste_begin = CSI ++ "200~"; +const CIS_bracketed_paste_end = CSI ++ "201~"; + +fn bracketed_paste_enable() void { + write_stdout(CSI_bracketed_paste_enable); +} + +fn bracketed_paste_disable() void { + write_stdout(CSI_bracketed_paste_disable); +} + +fn handle_CSI_escape_code(self: *Self) !void { + self.escape_state = .none; + self.escape_initial = null; + defer self.escape_code.clearAndFree(); + const code = self.escape_code.items; + if (self.match_code(CIS_bracketed_paste_begin, CSI.len)) + return self.handle_bracketed_paste_begin(); + if (self.match_code(CIS_bracketed_paste_end, CSI.len)) + return self.handle_bracketed_paste_end(); + self.logger.print("ignored escape code: CSI {s}", .{std.fmt.fmtSliceEscapeLower(code)}); +} + +fn handle_bracketed_paste_begin(self: *Self) !void { + self.bracketed_paste_buffer.clearAndFree(); + self.bracketed_paste = true; +} + +fn handle_bracketed_paste_end(self: *Self) !void { + defer self.bracketed_paste_buffer.clearAndFree(); + if (!self.bracketed_paste) return; + self.bracketed_paste = false; + if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{ "system_clipboard", self.bracketed_paste_buffer.items })); +} + +pub fn set_terminal_title(text: []const u8) void { + var writer = std.io.getStdOut().writer(); + var buf: [std.posix.PATH_MAX]u8 = undefined; + const term_cmd = std.fmt.bufPrint(&buf, OSC0_title ++ "{s}" ++ BEL, .{text}) catch return; + _ = writer.write(term_cmd) catch return; +} + +pub fn copy_to_system_clipboard(tmp_a: std.mem.Allocator, text: []const u8) void { + copy_to_system_clipboard_with_errors(tmp_a, text) catch |e| log.logger(log_name).err("copy_to_system_clipboard", e); +} + +fn copy_to_system_clipboard_with_errors(tmp_a: std.mem.Allocator, text: []const u8) !void { + var writer = std.io.getStdOut().writer(); + const encoder = std.base64.standard.Encoder; + const size = OSC52_clipboard.len + encoder.calcSize(text.len) + ST.len; + const buf = try tmp_a.alloc(u8, size); + defer tmp_a.free(buf); + @memcpy(buf[0..OSC52_clipboard.len], OSC52_clipboard); + const b64 = encoder.encode(buf[OSC52_clipboard.len..], text); + @memcpy(buf[OSC52_clipboard.len + b64.len ..], ST); + _ = try writer.write(buf); +} + +pub fn request_system_clipboard() void { + write_stdout(OSC52_clipboard ++ "?" ++ ST); +} + +pub fn request_mouse_cursor_text(push_or_pop: bool) void { + if (push_or_pop) mouse_cursor_push("text") else mouse_cursor_pop(); +} + +pub fn request_mouse_cursor_pointer(push_or_pop: bool) void { + if (push_or_pop) mouse_cursor_push("pointer") else mouse_cursor_pop(); +} + +pub fn request_mouse_cursor_default(push_or_pop: bool) void { + if (push_or_pop) mouse_cursor_push("default") else mouse_cursor_pop(); +} + +fn mouse_cursor_push(comptime name: []const u8) void { + write_stdout(OSC22_cursor ++ name ++ ST); +} + +fn mouse_cursor_pop() void { + write_stdout(OSC22_cursor ++ "default" ++ ST); +} + +fn write_stdout(bytes: []const u8) void { + _ = std.io.getStdOut().writer().write(bytes) catch |e| log.logger(log_name).err("stdout", e); +} + +pub fn cursor_enable(self: Self, y: c_int, x: c_int) !void { + return self.ctx.cursor_enable(y, x); +} + +pub fn cursor_disable(self: Self) void { + self.ctx.cursor_disable() catch {}; +} diff --git a/src/renderer/notcurses/style.zig b/src/renderer/notcurses/style.zig new file mode 100644 index 0000000..4420bd5 --- /dev/null +++ b/src/renderer/notcurses/style.zig @@ -0,0 +1,14 @@ +pub const StyleBits = packed struct(u5) { + struck: bool = false, + bold: bool = false, + undercurl: bool = false, + underline: bool = false, + italic: bool = false, + + pub const struck: StyleBits = .{ .struck = true }; + pub const bold: StyleBits = .{ .bold = true }; + pub const undercurl: StyleBits = .{ .undercurl = true }; + pub const underline: StyleBits = .{ .underline = true }; + pub const italic: StyleBits = .{ .italic = true }; + pub const normal: StyleBits = .{}; +}; diff --git a/src/tui/Box.zig b/src/tui/Box.zig index 7d8cb10..cb31862 100644 --- a/src/tui/Box.zig +++ b/src/tui/Box.zig @@ -1,4 +1,4 @@ -const Plane = @import("notcurses").Plane; +const Plane = @import("renderer").Plane; const Self = @This(); @@ -8,14 +8,14 @@ h: usize = 1, w: usize = 1, pub fn opts(self: Self, name_: [:0]const u8) Plane.Options { - return self.opts_flags(name_, 0); + return self.opts_flags(name_, Plane.option.none); } pub fn opts_vscroll(self: Self, name_: [:0]const u8) Plane.Options { return self.opts_flags(name_, Plane.option.VSCROLL); } -fn opts_flags(self: Self, name_: [:0]const u8, flags: u64) Plane.Options { +fn opts_flags(self: Self, name_: [:0]const u8, flags: Plane.option) Plane.Options { return Plane.Options{ .y = @intCast(self.y), .x = @intCast(self.x), diff --git a/src/tui/Button.zig b/src/tui/Button.zig index 312ad8a..a5ead47 100644 --- a/src/tui/Button.zig +++ b/src/tui/Button.zig @@ -1,7 +1,10 @@ const std = @import("std"); -const nc = @import("notcurses"); const tp = @import("thespian"); +const Plane = @import("renderer").Plane; +const key = @import("renderer").input.key; +const event_type = @import("renderer").input.event_type; + const Widget = @import("Widget.zig"); const command = @import("command.zig"); const tui = @import("tui.zig"); @@ -23,7 +26,7 @@ pub fn Options(context: type) type { pub fn do_nothing(_: *context, _: *State(Context)) void {} 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.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(); _ = self.plane.print(" {s} ", .{self.opts.label}) catch {}; @@ -40,10 +43,10 @@ pub fn Options(context: type) type { }; } -pub fn create(ctx_type: type, a: std.mem.Allocator, parent: nc.Plane, opts: Options(ctx_type)) !*State(ctx_type) { +pub fn create(ctx_type: type, a: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) !*State(ctx_type) { const Self = State(ctx_type); const self = try a.create(Self); - var n = try nc.Plane.init(&opts.pos.opts(@typeName(Self)), parent); + var n = try Plane.init(&opts.pos.opts(@typeName(Self)), parent); errdefer n.deinit(); self.* = .{ .a = a, @@ -55,15 +58,15 @@ pub fn create(ctx_type: type, a: std.mem.Allocator, parent: nc.Plane, opts: Opti return self; } -pub fn create_widget(ctx_type: type, a: std.mem.Allocator, parent: nc.Plane, opts: Options(ctx_type)) !Widget { +pub fn create_widget(ctx_type: type, a: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) !Widget { return Widget.to(try create(ctx_type, a, parent, opts)); } pub fn State(ctx_type: type) type { return struct { a: std.mem.Allocator, - parent: nc.Plane, - plane: nc.Plane, + parent: Plane, + plane: Plane, active: bool = false, hover: bool = false, opts: Options(ctx_type), @@ -87,22 +90,22 @@ pub fn State(ctx_type: type) type { pub fn receive(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool { var btn: u32 = 0; - if (try m.match(.{ "B", nc.event_type.PRESS, tp.extract(&btn), tp.any, tp.any, tp.any, tp.any, tp.any })) { + if (try m.match(.{ "B", event_type.PRESS, tp.extract(&btn), 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, tp.extract(&btn), tp.any, tp.any, tp.any, tp.any, tp.any })) { + } else if (try m.match(.{ "B", event_type.RELEASE, tp.extract(&btn), tp.any, tp.any, tp.any, tp.any, tp.any })) { self.call_click_handler(btn); self.active = false; tui.need_render(); return true; - } else if (try m.match(.{ "D", nc.event_type.RELEASE, tp.extract(&btn), tp.any, tp.any, tp.any, tp.any, tp.any })) { + } else if (try m.match(.{ "D", event_type.RELEASE, tp.extract(&btn), tp.any, tp.any, tp.any, tp.any, tp.any })) { self.call_click_handler(btn); 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.renderer.request_mouse_cursor_pointer(self.hover); tui.need_render(); return true; } @@ -112,9 +115,9 @@ pub fn State(ctx_type: type) type { fn call_click_handler(self: *Self, btn: u32) void { if (!self.hover) return; switch (btn) { - nc.key.BUTTON1 => self.opts.on_click(&self.opts.ctx, self), - nc.key.BUTTON2 => self.opts.on_click2(&self.opts.ctx, self), - nc.key.BUTTON3 => self.opts.on_click3(&self.opts.ctx, self), + key.BUTTON1 => self.opts.on_click(&self.opts.ctx, self), + key.BUTTON2 => self.opts.on_click2(&self.opts.ctx, self), + key.BUTTON3 => self.opts.on_click3(&self.opts.ctx, self), else => {}, } } diff --git a/src/tui/InputBox.zig b/src/tui/InputBox.zig index 48fb038..ad5ba1a 100644 --- a/src/tui/InputBox.zig +++ b/src/tui/InputBox.zig @@ -1,7 +1,10 @@ const std = @import("std"); -const nc = @import("notcurses"); const tp = @import("thespian"); +const Plane = @import("renderer").Plane; +const key = @import("renderer").input.key; +const event_type = @import("renderer").input.event_type; + const Widget = @import("Widget.zig"); const command = @import("command.zig"); const tui = @import("tui.zig"); @@ -20,7 +23,7 @@ pub fn Options(context: type) type { pub fn do_nothing(_: context, _: *State(Context)) void {} pub fn on_render_default(_: context, self: *State(Context), theme: *const Widget.Theme) bool { - tui.set_base_style(&self.plane, " ", if (self.text.items.len > 0) theme.input else theme.input_placeholder); + self.plane.set_base_style(" ", if (self.text.items.len > 0) theme.input else theme.input_placeholder); self.plane.erase(); self.plane.home(); if (self.text.items.len > 0) { @@ -33,7 +36,7 @@ pub fn Options(context: type) type { self.plane.cursor_move_yx(0, pos + 1) catch return false; var cell = self.plane.cell_init(); _ = self.plane.at_cursor_cell(&cell) catch return false; - tui.set_cell_style(&cell, theme.editor_cursor); + cell.set_style(theme.editor_cursor); _ = self.plane.putc(&cell) catch {}; } return false; @@ -45,10 +48,10 @@ pub fn Options(context: type) type { }; } -pub fn create(ctx_type: type, a: std.mem.Allocator, parent: nc.Plane, opts: Options(ctx_type)) !Widget { +pub fn create(ctx_type: type, a: std.mem.Allocator, parent: 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); + var n = try Plane.init(&opts.pos.opts(@typeName(Self)), parent); errdefer n.deinit(); self.* = .{ .parent = parent, @@ -64,8 +67,8 @@ pub fn create(ctx_type: type, a: std.mem.Allocator, parent: nc.Plane, opts: Opti pub fn State(ctx_type: type) type { return struct { - parent: nc.Plane, - plane: nc.Plane, + parent: Plane, + plane: Plane, active: bool = false, hover: bool = false, label: std.ArrayList(u8), @@ -92,22 +95,22 @@ pub fn State(ctx_type: type) type { } 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 })) { + if (try m.match(.{ "B", event_type.PRESS, 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 })) { + } else if (try m.match(.{ "B", event_type.RELEASE, key.BUTTON1, tp.any, tp.any, tp.any, tp.any, tp.any })) { 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 })) { + } else if (try m.match(.{ "D", event_type.RELEASE, key.BUTTON1, tp.any, tp.any, tp.any, tp.any, tp.any })) { 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.renderer.request_mouse_cursor_pointer(self.hover); tui.need_render(); return true; } diff --git a/src/tui/Menu.zig b/src/tui/Menu.zig index 0ec05fe..3fd682e 100644 --- a/src/tui/Menu.zig +++ b/src/tui/Menu.zig @@ -1,7 +1,8 @@ const std = @import("std"); -const nc = @import("notcurses"); const tp = @import("thespian"); +const planeutils = @import("renderer").planeutils; + const Widget = @import("Widget.zig"); const WidgetList = @import("WidgetList.zig"); const Button = @import("Button.zig"); @@ -21,8 +22,7 @@ pub fn Options(context: type) type { pub fn on_render_default(_: context, button: *Button.State(*State(Context)), theme: *const Widget.Theme, selected: bool) bool { const style_base = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor; - const bg_alpha: c_uint = if (button.active or button.hover or selected) nc.ALPHA_OPAQUE else nc.ALPHA_TRANSPARENT; - try tui.set_base_style_alpha(button.plane, " ", style_base, nc.ALPHA_TRANSPARENT, bg_alpha); + button.plane.set_base_style(" ", style_base); button.plane.erase(); button.plane.home(); _ = button.plane.print(" {s} ", .{button.opts.label}) catch {}; diff --git a/src/tui/Widget.zig b/src/tui/Widget.zig index 1af2d31..587c757 100644 --- a/src/tui/Widget.zig +++ b/src/tui/Widget.zig @@ -1,8 +1,9 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); +const Plane = @import("renderer").Plane; + pub const Box = @import("Box.zig"); pub const EventHandler = @import("EventHandler.zig"); pub const Theme = @import("theme"); @@ -10,7 +11,7 @@ pub const themes = @import("themes").themes; pub const scopes = @import("themes").scopes; ptr: *anyopaque, -plane: *nc.Plane, +plane: *Plane, vtable: *const VTable, const Self = @This(); @@ -209,10 +210,10 @@ pub fn walk(self: *Self, walk_ctx: *anyopaque, f: WalkFn) bool { return if (self.vtable.walk(self.ptr, walk_ctx, f, self)) true else f(walk_ctx, self); } -pub fn empty(a: Allocator, parent: nc.Plane, layout_: Layout) !Self { - const child: type = struct { plane: nc.Plane, layout: Layout }; +pub fn empty(a: Allocator, parent: Plane, layout_: Layout) !Self { + const child: type = struct { plane: Plane, layout: Layout }; const widget = try a.create(child); - const n = try nc.Plane.init(&(Box{}).opts("empty"), parent); + const n = try Plane.init(&(Box{}).opts("empty"), parent); widget.* = .{ .plane = n, .layout = layout_ }; return .{ .ptr = widget, diff --git a/src/tui/WidgetList.zig b/src/tui/WidgetList.zig index fe24eed..66b4262 100644 --- a/src/tui/WidgetList.zig +++ b/src/tui/WidgetList.zig @@ -2,8 +2,10 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; -const nc = @import("notcurses"); const tp = @import("thespian"); + +const Plane = @import("renderer").Plane; + const Widget = @import("Widget.zig"); const Box = @import("Box.zig"); @@ -17,8 +19,8 @@ const WidgetState = struct { layout: Layout = .{}, }; -plane: nc.Plane, -parent: nc.Plane, +plane: Plane, +parent: Plane, a: Allocator, widgets: ArrayList(WidgetState), layout: Layout, @@ -51,7 +53,7 @@ pub fn createBox(a: Allocator, parent: Widget, name: [:0]const u8, dir: Directio fn init(a: Allocator, parent: Widget, name: [:0]const u8, dir: Direction, layout_: Layout, box: Box) !Self { return .{ - .plane = try nc.Plane.init(&box.opts(name), parent.plane.*), + .plane = try Plane.init(&box.opts(name), parent.plane.*), .parent = parent.plane.*, .a = a, .widgets = ArrayList(WidgetState).init(a), diff --git a/src/tui/WidgetStack.zig b/src/tui/WidgetStack.zig index f2417e7..5d96dc0 100644 --- a/src/tui/WidgetStack.zig +++ b/src/tui/WidgetStack.zig @@ -3,7 +3,6 @@ const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const eql = std.mem.eql; -const nc = @import("notcurses"); const tp = @import("thespian"); const Widget = @import("Widget.zig"); diff --git a/src/tui/editor.zig b/src/tui/editor.zig index f25def2..05fbcec 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1,6 +1,5 @@ const std = @import("std"); const builtin = @import("builtin"); -const nc = @import("notcurses"); const tp = @import("thespian"); const cbor = @import("cbor"); const log = @import("log"); @@ -11,6 +10,11 @@ const text_manip = @import("text_manip"); const syntax = @import("syntax"); const project_manager = @import("project_manager"); +const Plane = @import("renderer").Plane; +const Cell = @import("renderer").Cell; +const key = @import("renderer").input.key; +const event_type = @import("renderer").input.event_type; + const scrollbar_v = @import("scrollbar_v.zig"); const editor_gutter = @import("editor_gutter.zig"); const EventHandler = @import("EventHandler.zig"); @@ -31,9 +35,6 @@ const fmt = std.fmt; const split = std.mem.split; const time = std.time; -const A = nc.Align; -const key = nc.key; - const scroll_step_small = 3; const scroll_page_ratio = 3; const scroll_cursor_min_border_distance = 5; @@ -188,7 +189,7 @@ pub const Editor = struct { pub const Target = Self; a: Allocator, - plane: nc.Plane, + plane: Plane, logger: log.Logger, file_path: ?[]const u8, @@ -307,7 +308,7 @@ pub const Editor = struct { } } - fn init(self: *Self, a: Allocator, n: nc.Plane) void { + fn init(self: *Self, a: Allocator, n: Plane) void { const logger = log.logger("editor"); var frame_rate = tp.env.get().num("frame-rate"); if (frame_rate == 0) frame_rate = 60; @@ -429,7 +430,7 @@ pub const Editor = struct { self.buffer = null; self.plane.erase(); self.plane.home(); - self.plane.context().cursor_disable() catch {}; + tui.current().rdr.cursor_disable(); _ = try self.handlers.msg(.{ "E", "close" }); if (self.syntax) |_| if (self.file_path) |file_path| project_manager.did_close(file_path) catch {}; @@ -718,7 +719,7 @@ pub const Editor = struct { const frame = tracy.initZone(@src(), .{ .name = "editor render screen" }); defer frame.deinit(); - tui.set_base_style(&self.plane, " ", theme.editor); + self.plane.set_base_style(" ", theme.editor); self.plane.erase(); if (hl_row) |_| self.render_line_highlight(&self.get_primary().cursor, theme) catch {}; @@ -735,9 +736,9 @@ pub const Editor = struct { var y: c_int = @intCast(cursor.row); var x: c_int = @intCast(cursor.col); self.plane.rel_yx_to_abs(&y, &x); - self.plane.context().cursor_enable(y, x) catch {}; + tui.current().rdr.cursor_enable(y, x) catch {}; } else { - self.plane.context().cursor_disable() catch {}; + tui.current().rdr.cursor_disable(); } } @@ -772,7 +773,7 @@ pub const Editor = struct { } } - fn render_matches(self: *const Self, last_idx: *usize, theme: *const Widget.Theme, cell: *nc.Cell) void { + fn render_matches(self: *const Self, last_idx: *usize, theme: *const Widget.Theme, cell: *Cell) void { var y: c_uint = undefined; var x: c_uint = undefined; self.plane.cursor_yx(&y, &x); @@ -791,7 +792,7 @@ pub const Editor = struct { } } - fn render_selections(self: *const Self, theme: *const Widget.Theme, cell: *nc.Cell) void { + fn render_selections(self: *const Self, theme: *const Widget.Theme, cell: *Cell) void { var y: c_uint = undefined; var x: c_uint = undefined; self.plane.cursor_yx(&y, &x); @@ -837,7 +838,7 @@ pub const Editor = struct { } } - fn get_line_end_space_begin(plane: nc.Plane, screen_width: usize, screen_row: usize) usize { + fn get_line_end_space_begin(plane: Plane, screen_width: usize, screen_row: usize) usize { var pos = screen_width; var cell = plane.cell_init(); while (pos > 0) : (pos -= 1) { @@ -849,49 +850,49 @@ pub const Editor = struct { } fn render_diagnostic_message(self: *const Self, message: []const u8, y: usize, max_space: usize, style: Widget.Theme.Style) void { - tui.set_style(&self.plane, style); - _ = self.plane.print_aligned(@intCast(y), nc.Align.right, "{s}", .{message[0..@min(max_space, message.len)]}) catch {}; + self.plane.set_style(style); + _ = self.plane.print_aligned_right(@intCast(y), "{s}", .{message[0..@min(max_space, message.len)]}) catch {}; } inline fn render_diagnostic_cell(self: *const Self, _: Widget.Theme.Style) void { var cell = self.plane.cell_init(); _ = self.plane.at_cursor_cell(&cell) catch return; - tui.set_cell_style(&cell, .{ .fs = .undercurl }); + cell.set_style(.{ .fs = .undercurl }); _ = self.plane.putc(&cell) catch {}; } inline fn render_cursor_cell(self: *const Self, theme: *const Widget.Theme) void { var cell = self.plane.cell_init(); _ = self.plane.at_cursor_cell(&cell) catch return; - tui.set_cell_style(&cell, theme.editor_cursor); + cell.set_style(theme.editor_cursor); _ = self.plane.putc(&cell) catch {}; } - inline fn render_selection_cell(_: *const Self, theme: *const Widget.Theme, cell: *nc.Cell) void { - tui.set_cell_style_bg(cell, theme.editor_selection); + inline fn render_selection_cell(_: *const Self, theme: *const Widget.Theme, cell: *Cell) void { + cell.set_style_bg(theme.editor_selection); } - inline fn render_match_cell(_: *const Self, theme: *const Widget.Theme, cell: *nc.Cell, match: Match) void { - tui.set_cell_style_bg(cell, if (match.style) |style| style else theme.editor_match); + inline fn render_match_cell(_: *const Self, theme: *const Widget.Theme, cell: *Cell, match: Match) void { + cell.set_style_bg(if (match.style) |style| style else theme.editor_match); } - inline fn render_line_highlight_cell(_: *const Self, theme: *const Widget.Theme, cell: *nc.Cell) void { - tui.set_cell_style_bg(cell, theme.editor_line_highlight); + inline fn render_line_highlight_cell(_: *const Self, theme: *const Widget.Theme, cell: *Cell) void { + cell.set_style_bg(theme.editor_line_highlight); } - inline fn render_control_code(self: *const Self, c: *nc.Cell, n: nc.Plane, code: u8, theme: *const Widget.Theme) struct { usize, usize } { + inline fn render_control_code(self: *const Self, c: *Cell, n: Plane, code: u8, theme: *const Widget.Theme) struct { usize, usize } { const val = Buffer.unicode.control_code_to_unicode(code); if (self.show_whitespace) - tui.set_cell_style(c, theme.editor_whitespace); + c.set_style(theme.editor_whitespace); _ = n.cell_load(c, val) catch {}; return .{ 1, 1 }; } - inline fn render_eol(self: *const Self, n: nc.Plane, theme: *const Widget.Theme) nc.Cell { + inline fn render_eol(self: *const Self, n: Plane, theme: *const Widget.Theme) Cell { var cell = n.cell_init(); const c = &cell; if (self.show_whitespace) { - tui.set_cell_style(c, theme.editor_whitespace); + c.set_style(theme.editor_whitespace); //_ = n.cell_load(c, "$") catch {}; //_ = n.cell_load(c, " ") catch {}; //_ = n.cell_load(c, "⏎") catch {}; @@ -912,16 +913,16 @@ pub const Editor = struct { return cell; } - inline fn render_terminator(n: nc.Plane, theme: *const Widget.Theme) nc.Cell { + inline fn render_terminator(n: Plane, theme: *const Widget.Theme) Cell { var cell = n.cell_init(); - tui.set_cell_style(&cell, theme.editor); + cell.set_style(theme.editor); _ = n.cell_load(&cell, "\u{2003}") catch unreachable; return cell; } - inline fn render_space(self: *const Self, c: *nc.Cell, n: nc.Plane, theme: *const Widget.Theme) struct { usize, usize } { + inline fn render_space(self: *const Self, c: *Cell, n: Plane, theme: *const Widget.Theme) struct { usize, usize } { if (self.show_whitespace) { - tui.set_cell_style(c, theme.editor_whitespace); + c.set_style(theme.editor_whitespace); _ = n.cell_load(c, "·") catch {}; //_ = n.cell_load(c, "•") catch {}; //_ = n.cell_load(c, "⁃") catch {}; @@ -940,9 +941,9 @@ pub const Editor = struct { return .{ 1, 1 }; } - inline fn render_tab(self: *const Self, c: *nc.Cell, n: nc.Plane, abs_col: usize, theme: *const Widget.Theme) struct { usize, usize } { + inline fn render_tab(self: *const Self, c: *Cell, n: Plane, abs_col: usize, theme: *const Widget.Theme) struct { usize, usize } { if (self.show_whitespace) { - tui.set_cell_style(c, theme.editor_whitespace); + c.set_style(theme.editor_whitespace); _ = n.cell_load(c, "→") catch {}; //_ = n.cell_load(c, "⭲") catch {}; } else { @@ -951,9 +952,9 @@ pub const Editor = struct { return .{ 1, 9 - (abs_col % 8) }; } - inline fn render_egc(c: *nc.Cell, n: nc.Plane, egc: [:0]const u8) struct { usize, usize } { + inline fn render_egc(c: *Cell, n: Plane, egc: [:0]const u8) struct { usize, usize } { const bytes = n.cell_load(c, egc) catch return .{ 1, 1 }; - const colcount = nc.cell_cols(c); + const colcount = c.columns(); return .{ bytes, colcount }; } @@ -1007,7 +1008,7 @@ pub const Editor = struct { ctx.self.plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return; var cell = ctx.self.plane.cell_init(); _ = ctx.self.plane.at_cursor_cell(&cell) catch return; - tui.set_cell_style(&cell, style); + cell.set_style(style); _ = ctx.self.plane.putc(&cell) catch {}; } }; @@ -1850,7 +1851,7 @@ pub const Editor = struct { if (self.clipboard) |old| self.a.free(old); self.clipboard = text; - tui.current().copy_to_system_clipboard(text); + tui.renderer.copy_to_system_clipboard(self.a, text); } fn copy_selection(root: Buffer.Root, sel: Selection, text_a: Allocator) ![]const u8 { @@ -1985,7 +1986,7 @@ pub const Editor = struct { } pub fn system_paste(_: *Self, _: command.Context) tp.result { - tui.current().request_system_clipboard(); + tui.renderer.request_system_clipboard(); } pub fn delete_forward(self: *Self, _: command.Context) tp.result { @@ -2857,12 +2858,12 @@ pub const Editor = struct { pub fn enable_jump_mode(self: *Self, _: command.Context) tp.result { self.jump_mode = true; - tui.current().request_mouse_cursor_pointer(true); + tui.renderer.request_mouse_cursor_pointer(true); } pub fn disable_jump_mode(self: *Self, _: command.Context) tp.result { self.jump_mode = false; - tui.current().request_mouse_cursor_text(true); + tui.renderer.request_mouse_cursor_text(true); } fn update_syntax(self: *Self) !void { @@ -3530,8 +3531,8 @@ pub fn create(a: Allocator, parent: Widget) !Widget { } pub const EditorWidget = struct { - plane: nc.Plane, - parent: nc.Plane, + plane: Plane, + parent: Plane, editor: Editor, commands: Commands = undefined, @@ -3558,7 +3559,7 @@ pub const EditorWidget = struct { } fn init(self: *Self, a: Allocator, parent: Widget) !void { - var n = try nc.Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*); + var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*); errdefer n.deinit(); self.* = .{ @@ -3612,9 +3613,9 @@ pub const EditorWidget = struct { self.editor.add_match(m) catch {}; } else if (try m.match(.{ "H", tp.extract(&self.hover) })) { if (self.editor.jump_mode) - tui.current().request_mouse_cursor_pointer(self.hover) + tui.renderer.request_mouse_cursor_pointer(self.hover) else - tui.current().request_mouse_cursor_text(self.hover); + tui.renderer.request_mouse_cursor_text(self.hover); } else if (try m.match(.{ "show_whitespace", tp.extract(&self.editor.show_whitespace) })) { _ = ""; } else { @@ -3624,15 +3625,15 @@ pub const EditorWidget = struct { } fn mouse_click_event(self: *Self, evtype: c_int, btn: c_int, y: c_int, x: c_int, ypx: c_int, xpx: c_int) tp.result { - if (evtype != nc.event_type.PRESS) return; + if (evtype != event_type.PRESS) return; const ret = (switch (btn) { - nc.key.BUTTON1 => &mouse_click_button1, - nc.key.BUTTON2 => &mouse_click_button2, - nc.key.BUTTON3 => &mouse_click_button3, - nc.key.BUTTON4 => &mouse_click_button4, - nc.key.BUTTON5 => &mouse_click_button5, - nc.key.BUTTON8 => &mouse_click_button8, //back - nc.key.BUTTON9 => &mouse_click_button9, //forward + key.BUTTON1 => &mouse_click_button1, + key.BUTTON2 => &mouse_click_button2, + key.BUTTON3 => &mouse_click_button3, + key.BUTTON4 => &mouse_click_button4, + key.BUTTON5 => &mouse_click_button5, + key.BUTTON8 => &mouse_click_button8, //back + key.BUTTON9 => &mouse_click_button9, //forward else => return, })(self, y, x, ypx, xpx); self.last_btn = btn; @@ -3641,11 +3642,11 @@ pub const EditorWidget = struct { } fn mouse_drag_event(self: *Self, evtype: c_int, btn: c_int, y: c_int, x: c_int, ypx: c_int, xpx: c_int) tp.result { - if (evtype != nc.event_type.PRESS) return; + if (evtype != event_type.PRESS) return; return (switch (btn) { - nc.key.BUTTON1 => &mouse_drag_button1, - nc.key.BUTTON2 => &mouse_drag_button2, - nc.key.BUTTON3 => &mouse_drag_button3, + key.BUTTON1 => &mouse_drag_button1, + key.BUTTON2 => &mouse_drag_button2, + key.BUTTON3 => &mouse_drag_button3, else => return, })(self, y, x, ypx, xpx); } @@ -3653,7 +3654,7 @@ pub const EditorWidget = struct { fn mouse_click_button1(self: *Self, y: c_int, x: c_int, _: c_int, _: c_int) tp.result { var y_, var x_ = .{ y, x }; self.editor.plane.abs_yx_to_rel(&y_, &x_); - if (self.last_btn == nc.key.BUTTON1) { + if (self.last_btn == key.BUTTON1) { const click_time_ms = time.milliTimestamp() - self.last_btn_time_ms; if (click_time_ms <= double_click_time_ms) { if (self.last_btn_count == 2) { diff --git a/src/tui/editor_gutter.zig b/src/tui/editor_gutter.zig index d4adf59..3c8686c 100644 --- a/src/tui/editor_gutter.zig +++ b/src/tui/editor_gutter.zig @@ -1,12 +1,16 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); const tracy = @import("tracy"); const diff = @import("diff"); const cbor = @import("cbor"); const root = @import("root"); +const Plane = @import("renderer").Plane; +const style = @import("renderer").style; +const key = @import("renderer").input.key; +const event_type = @import("renderer").input.event_type; + const Widget = @import("Widget.zig"); const WidgetList = @import("WidgetList.zig"); const EventHandler = @import("EventHandler.zig"); @@ -16,7 +20,7 @@ const command = @import("command.zig"); const ed = @import("editor.zig"); a: Allocator, -plane: nc.Plane, +plane: Plane, parent: Widget, lines: u32 = 0, @@ -40,7 +44,7 @@ pub fn create(a: Allocator, parent: Widget, event_source: Widget, editor: *ed.Ed const self: *Self = try a.create(Self); self.* = .{ .a = a, - .plane = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent.plane.*), + .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent.plane.*), .parent = parent, .linenum = tui.current().config.gutter_line_numbers, .relative = tui.current().config.gutter_line_numbers_relative, @@ -87,15 +91,15 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { var y: i32 = undefined; var ypx: i32 = undefined; - if (try m.match(.{ "B", nc.event_type.PRESS, nc.key.BUTTON1, tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) + if (try m.match(.{ "B", event_type.PRESS, key.BUTTON1, tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) return self.primary_click(y); - if (try m.match(.{ "B", nc.event_type.PRESS, nc.key.BUTTON3, tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) + if (try m.match(.{ "B", event_type.PRESS, key.BUTTON3, tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) return self.secondary_click(); - if (try m.match(.{ "D", nc.event_type.PRESS, nc.key.BUTTON1, tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) + if (try m.match(.{ "D", event_type.PRESS, key.BUTTON1, tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) return self.primary_drag(y); - if (try m.match(.{ "B", nc.event_type.PRESS, nc.key.BUTTON4, tp.more })) + if (try m.match(.{ "B", event_type.PRESS, key.BUTTON4, tp.more })) return self.mouse_click_button4(); - if (try m.match(.{ "B", nc.event_type.PRESS, nc.key.BUTTON5, tp.more })) + if (try m.match(.{ "B", event_type.PRESS, key.BUTTON5, tp.more })) return self.mouse_click_button5(); return false; @@ -119,7 +123,7 @@ inline fn get_width(self: *Self) usize { pub fn render(self: *Self, theme: *const Widget.Theme) bool { const frame = tracy.initZone(@src(), .{ .name = "gutter render" }); defer frame.deinit(); - tui.set_base_style(&self.plane, " ", theme.editor_gutter); + self.plane.set_base_style(" ", theme.editor_gutter); self.plane.erase(); if (self.linenum) { const relative = self.relative or if (tui.current().input_mode) |mode| mode.line_numbers == .relative else false; @@ -158,13 +162,13 @@ pub fn render_linear(self: *Self, theme: *const Widget.Theme) void { while (rows > 0) : (rows -= 1) { if (linenum > self.lines) return; if (linenum == self.line + 1) { - tui.set_base_style(&self.plane, " ", theme.editor_gutter_active); - self.plane.on_styles(nc.style.bold); + self.plane.set_base_style(" ", theme.editor_gutter_active); + self.plane.on_styles(style.bold); } else { - tui.set_base_style(&self.plane, " ", theme.editor_gutter); - self.plane.off_styles(nc.style.bold); + self.plane.set_base_style(" ", theme.editor_gutter); + self.plane.off_styles(style.bold); } - _ = self.plane.putstr_aligned(@intCast(pos), nc.Align.right, std.fmt.bufPrintZ(&buf, "{d} ", .{linenum}) catch return) catch {}; + _ = self.plane.print_aligned_right(@intCast(pos), "{s}", .{std.fmt.bufPrintZ(&buf, "{d} ", .{linenum}) catch return}) catch {}; if (self.highlight and linenum == self.line + 1) self.render_line_highlight(pos, theme); self.render_diff_symbols(&diff_symbols, pos, linenum, theme); @@ -184,10 +188,10 @@ pub fn render_relative(self: *Self, theme: *const Widget.Theme) void { var buf: [31:0]u8 = undefined; while (rows > 0) : (rows -= 1) { if (pos > self.lines - row) return; - tui.set_base_style(&self.plane, " ", if (linenum == 0) theme.editor_gutter_active else theme.editor_gutter); + self.plane.set_base_style(" ", if (linenum == 0) theme.editor_gutter_active else theme.editor_gutter); const val = @abs(if (linenum == 0) line else linenum); const fmt = std.fmt.bufPrintZ(&buf, "{d} ", .{val}) catch return; - _ = self.plane.putstr_aligned(@intCast(pos), nc.Align.right, if (fmt.len > 6) "==> " else fmt) catch {}; + _ = self.plane.print_aligned_right(@intCast(pos), "{s}", .{if (fmt.len > 6) "==> " else fmt}) catch {}; if (self.highlight and linenum == 0) self.render_line_highlight(pos, theme); self.render_diff_symbols(&diff_symbols, pos, abs_linenum, theme); @@ -202,7 +206,7 @@ inline fn render_line_highlight(self: *Self, pos: usize, theme: *const Widget.Th self.plane.cursor_move_yx(@intCast(pos), @intCast(i)) catch return; var cell = self.plane.cell_init(); _ = self.plane.at_cursor_cell(&cell) catch return; - tui.set_cell_style_bg(&cell, theme.editor_line_highlight); + cell.set_style_bg(theme.editor_line_highlight); _ = self.plane.putc(&cell) catch {}; } } @@ -227,7 +231,7 @@ inline fn render_diff_symbols(self: *Self, diff_symbols: *[]Symbol, pos: usize, self.plane.cursor_move_yx(@intCast(pos), @intCast(self.get_width() - 1)) catch return; var cell = self.plane.cell_init(); _ = self.plane.at_cursor_cell(&cell) catch return; - tui.set_cell_style_fg(&cell, switch (sym.kind) { + cell.set_style_fg(switch (sym.kind) { .insert => theme.editor_gutter_added, .modified => theme.editor_gutter_modified, .delete => theme.editor_gutter_deleted, @@ -243,7 +247,7 @@ fn render_diagnostics(self: *Self, theme: *const Widget.Theme) void { fn render_diagnostic(self: *Self, diag: *const ed.Diagnostic, theme: *const Widget.Theme) void { const row = diag.sel.begin.row; if (!(self.row < row and row < self.row + self.rows)) return; - const style = switch (diag.get_severity()) { + const style_ = switch (diag.get_severity()) { .Error => theme.editor_error, .Warning => theme.editor_warning, .Information => theme.editor_information, @@ -259,7 +263,7 @@ fn render_diagnostic(self: *Self, diag: *const ed.Diagnostic, theme: *const Widg self.plane.cursor_move_yx(@intCast(y), 0) catch return; var cell = self.plane.cell_init(); _ = self.plane.at_cursor_cell(&cell) catch return; - tui.set_cell_style_fg(&cell, style); + cell.set_style_fg(style_); _ = self.plane.cell_load(&cell, icon) catch {}; _ = self.plane.putc(&cell) catch {}; } diff --git a/src/tui/fonts.zig b/src/tui/fonts.zig index 264cf56..4af8576 100644 --- a/src/tui/fonts.zig +++ b/src/tui/fonts.zig @@ -1,11 +1,11 @@ -const nc = @import("notcurses"); +const Plane = @import("renderer").Plane; -pub fn print_string_large(n: nc.Plane, s: []const u8) !void { +pub fn print_string_large(n: Plane, s: []const u8) !void { for (s) |c| print_char_large(n, c) catch break; } -pub fn print_char_large(n: nc.Plane, char: u8) !void { +pub fn print_char_large(n: Plane, char: u8) !void { const bitmap = font8x8[char]; for (0..8) |y| { for (0..8) |x| { @@ -21,14 +21,14 @@ pub fn print_char_large(n: nc.Plane, char: u8) !void { n.cursor_move_rel(-8, 8) catch {}; } -pub fn print_string_medium(n: nc.Plane, s: []const u8) !void { +pub fn print_string_medium(n: Plane, s: []const u8) !void { for (s) |c| print_char_medium(n, c) catch break; } const QUADBLOCKS = [_][:0]const u8{ " ", "▘", "▝", "▀", "▖", "▌", "▞", "▛", "▗", "▚", "▐", "▜", "▄", "▙", "▟", "█" }; -pub fn print_char_medium(n: nc.Plane, char: u8) !void { +pub fn print_char_medium(n: Plane, char: u8) !void { const bitmap = font8x8[char]; for (0..4) |y| { for (0..4) |x| { diff --git a/src/tui/home.zig b/src/tui/home.zig index 64df30a..1278b96 100644 --- a/src/tui/home.zig +++ b/src/tui/home.zig @@ -1,7 +1,11 @@ const std = @import("std"); -const nc = @import("notcurses"); const tp = @import("thespian"); +const Plane = @import("renderer").Plane; +const planeutils = @import("renderer").planeutils; +const channels_ = @import("renderer").channels; +const style_ = @import("renderer").style; + const Widget = @import("Widget.zig"); const WidgetList = @import("WidgetList.zig"); const Button = @import("Button.zig"); @@ -11,9 +15,9 @@ const command = @import("command.zig"); const fonts = @import("fonts.zig"); a: std.mem.Allocator, -background: nc.Plane, -plane: nc.Plane, -parent: nc.Plane, +background: Plane, +plane: Plane, +parent: Plane, fire: ?Fire = null, commands: Commands = undefined, menu: *Menu.State(*Self), @@ -22,9 +26,9 @@ 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.*); + var background = try Plane.init(&(Widget.Box{}).opts("background"), parent.plane.*); errdefer background.deinit(); - var n = try nc.Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*); + var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*); errdefer n.deinit(); const w = Widget.to(self); @@ -68,49 +72,31 @@ pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) boo 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.renderer.request_mouse_cursor_default(hover); tui.need_render(); return true; } return false; } -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), - .undercurl => plane.set_styles(nc.style.undercurl), - .strikethrough => plane.set_styles(nc.style.struck), - }; -} - fn menu_on_render(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool { const style_base = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor; - const bg_alpha: c_uint = if (button.active or button.hover or selected) nc.ALPHA_OPAQUE else nc.ALPHA_TRANSPARENT; - try tui.set_base_style_alpha(button.plane, " ", style_base, nc.ALPHA_OPAQUE, bg_alpha); + if (button.active or button.hover or selected) { + button.plane.set_base_style(" ", style_base); + } else { + button.plane.set_base_style_bg_transparent(" ", style_base); + } 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.set_style(style_subtext); + button.plane.set_style(style_text); const pointer = if (selected) "⏵" else " "; _ = button.plane.print("{s}{s}", .{ pointer, button.opts.label[0..sep] }) catch {}; - set_style(button.plane, style_keybind); + button.plane.set_style(style_keybind); _ = button.plane.print("{s}", .{button.opts.label[sep + 1 ..]}) catch {}; return false; } @@ -146,10 +132,10 @@ fn menu_action_quit(_: **Menu.State(*Self), _: *Button.State(*Menu.State(*Self)) pub fn render(self: *Self, theme: *const Widget.Theme) bool { const more = self.menu.render(theme); - try tui.set_base_style_alpha(self.background, " ", theme.editor, nc.ALPHA_OPAQUE, nc.ALPHA_OPAQUE); + self.background.set_base_style(" ", theme.editor); self.background.erase(); self.background.home(); - try tui.set_base_style_alpha(self.plane, "", theme.editor, nc.ALPHA_TRANSPARENT, nc.ALPHA_TRANSPARENT); + self.plane.set_base_style_transparent("", theme.editor); self.plane.erase(); self.plane.home(); if (self.fire) |*fire| fire.render(); @@ -161,31 +147,31 @@ 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) { - set_style(self.plane, style_title); + self.plane.set_style_bg_transparent(style_title); self.plane.cursor_move_yx(2, 4) catch return more; fonts.print_string_large(self.plane, title) catch return more; - set_style(self.plane, style_subtext); + self.plane.set_style_bg_transparent(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) { - set_style(self.plane, style_title); + self.plane.set_style_bg_transparent(style_title); self.plane.cursor_move_yx(2, 4) catch return more; fonts.print_string_medium(self.plane, title) catch return more; - set_style(self.plane, style_subtext); + self.plane.set_style_bg_transparent(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 { - set_style(self.plane, style_title); + self.plane.set_style_bg_transparent(style_title); self.plane.cursor_move_yx(1, 4) catch return more; _ = self.plane.print(title, .{}) catch return more; - set_style(self.plane, style_subtext); + self.plane.set_style_bg_transparent(style_subtext); self.plane.cursor_move_yx(3, 6) catch return more; _ = self.plane.print(subtext, .{}) catch {}; @@ -235,7 +221,7 @@ const Fire = struct { const px = "▀"; allocator: std.mem.Allocator, - plane: nc.Plane, + plane: Plane, prng: std.rand.DefaultPrng, //scope cache - spread fire @@ -253,7 +239,7 @@ const Fire = struct { const MAX_COLOR = 256; const LAST_COLOR = MAX_COLOR - 1; - fn init(a: std.mem.Allocator, plane: nc.Plane, pos: Widget.Box) !Fire { + fn init(a: std.mem.Allocator, plane: Plane, pos: Widget.Box) !Fire { const FIRE_H = @as(u16, @intCast(pos.h)) * 2; const FIRE_W = @as(u16, @intCast(pos.w)); var self: Fire = .{ diff --git a/src/tui/inputview.zig b/src/tui/inputview.zig index 0de1622..1a5287a 100644 --- a/src/tui/inputview.zig +++ b/src/tui/inputview.zig @@ -4,19 +4,18 @@ const time = @import("std").time; const Allocator = @import("std").mem.Allocator; const Mutex = @import("std").Thread.Mutex; -const nc = @import("notcurses"); const tp = @import("thespian"); +const Plane = @import("renderer").Plane; + const tui = @import("tui.zig"); const Widget = @import("Widget.zig"); const EventHandler = @import("EventHandler.zig"); -const A = nc.Align; - pub const name = "inputview"; -parent: nc.Plane, -plane: nc.Plane, +parent: Plane, +plane: Plane, lastbuf: [4096]u8 = undefined, last: []u8 = "", last_count: u64 = 0, @@ -25,15 +24,15 @@ last_tdiff: i64 = 0, const Self = @This(); -pub fn create(a: Allocator, parent: nc.Plane) !Widget { +pub fn create(a: Allocator, parent: Plane) !Widget { const self: *Self = try a.create(Self); self.* = try init(parent); try tui.current().input_listeners.add(EventHandler.bind(self, listen)); return Widget.to(self); } -fn init(parent: nc.Plane) !Self { - var n = try nc.Plane.init(&(Widget.Box{}).opts_vscroll(@typeName(Self)), parent); +fn init(parent: Plane) !Self { + var n = try Plane.init(&(Widget.Box{}).opts_vscroll(@typeName(Self)), parent); errdefer n.deinit(); return .{ .parent = parent, @@ -49,7 +48,7 @@ pub fn deinit(self: *Self, a: Allocator) void { } pub fn render(self: *Self, theme: *const Widget.Theme) bool { - tui.set_base_style(&self.plane, " ", theme.panel); + self.plane.set_base_style(" ", theme.panel); return false; } diff --git a/src/tui/inspector_view.zig b/src/tui/inspector_view.zig index 6d4288d..2da7cd6 100644 --- a/src/tui/inspector_view.zig +++ b/src/tui/inspector_view.zig @@ -3,23 +3,23 @@ const fmt = @import("std").fmt; const time = @import("std").time; const Allocator = @import("std").mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); const Buffer = @import("Buffer"); const color = @import("color"); const syntax = @import("syntax"); +const Plane = @import("renderer").Plane; +const style = @import("renderer").style; + const tui = @import("tui.zig"); const Widget = @import("Widget.zig"); const EventHandler = @import("EventHandler.zig"); const mainview = @import("mainview.zig"); const ed = @import("editor.zig"); -const A = nc.Align; - pub const name = @typeName(Self); -plane: nc.Plane, +plane: Plane, editor: *ed.Editor, need_render: bool = true, need_clear: bool = false, @@ -30,11 +30,11 @@ last_node: usize = 0, const Self = @This(); -pub fn create(a: Allocator, parent: nc.Plane) !Widget { +pub fn create(a: Allocator, parent: Plane) !Widget { if (tui.current().mainview.dynamic_cast(mainview)) |mv_| if (mv_.get_editor()) |editor| { const self: *Self = try a.create(Self); self.* = .{ - .plane = try nc.Plane.init(&(Widget.Box{}).opts_vscroll(name), parent), + .plane = try Plane.init(&(Widget.Box{}).opts_vscroll(name), parent), .editor = editor, .pos_cache = try ed.PosToWidthCache.init(a), }; @@ -189,33 +189,33 @@ fn show_color(self: *Self, tag: []const u8, c_: ?Widget.Theme.Color) void { fn show_font(self: *Self, font: ?Widget.Theme.FontStyle) void { if (font) |fs| switch (fs) { .normal => { - self.plane.set_styles(nc.style.none); + self.plane.set_styles(style.normal); _ = self.plane.print(" normal", .{}) catch return; }, .bold => { - self.plane.set_styles(nc.style.bold); + self.plane.set_styles(style.bold); _ = self.plane.print(" bold", .{}) catch return; }, .italic => { - self.plane.set_styles(nc.style.italic); + self.plane.set_styles(style.italic); _ = self.plane.print(" italic", .{}) catch return; }, .underline => { - self.plane.set_styles(nc.style.underline); + self.plane.set_styles(style.underline); _ = self.plane.print(" underline", .{}) catch return; }, .undercurl => { - self.plane.set_styles(nc.style.undercurl); + self.plane.set_styles(style.undercurl); _ = self.plane.print(" undercurl", .{}) catch return; }, .strikethrough => { - self.plane.set_styles(nc.style.struck); + self.plane.set_styles(style.struck); _ = self.plane.print(" strikethrough", .{}) catch return; }, }; - self.plane.set_styles(nc.style.none); + self.plane.set_styles(style.normal); } fn reset_style(self: *Self) void { - tui.set_base_style(&self.plane, " ", (self.theme orelse return).panel); + self.plane.set_base_style(" ", (self.theme orelse return).panel); } diff --git a/src/tui/logview.zig b/src/tui/logview.zig index 55a6e77..1134ee8 100644 --- a/src/tui/logview.zig +++ b/src/tui/logview.zig @@ -4,20 +4,20 @@ const time = @import("std").time; const Allocator = @import("std").mem.Allocator; const Mutex = @import("std").Thread.Mutex; -const nc = @import("notcurses"); const tp = @import("thespian"); const log = @import("log"); +const Plane = @import("renderer").Plane; + const tui = @import("tui.zig"); const Widget = @import("Widget.zig"); const MessageFilter = @import("MessageFilter.zig"); const escape = fmt.fmtSliceEscapeLower; -const A = nc.Align; pub const name = @typeName(Self); -plane: nc.Plane, +plane: Plane, lastbuf_src: [128]u8 = undefined, lastbuf_msg: [log.max_log_message]u8 = undefined, last_src: []u8 = "", @@ -28,15 +28,15 @@ last_tdiff: i64 = 0, const Self = @This(); -pub fn create(a: Allocator, parent: nc.Plane) !Widget { +pub fn create(a: Allocator, parent: Plane) !Widget { const self: *Self = try a.create(Self); self.* = init(parent) catch |e| return tp.exit_error(e); try tui.current().message_filters.add(MessageFilter.bind(self, log_receive)); return Widget.to(self); } -fn init(parent: nc.Plane) !Self { - var n = try nc.Plane.init(&(Widget.Box{}).opts_vscroll(name), parent); +fn init(parent: Plane) !Self { + var n = try Plane.init(&(Widget.Box{}).opts_vscroll(name), parent); errdefer n.deinit(); return .{ .plane = n, @@ -51,7 +51,7 @@ pub fn deinit(self: *Self, a: Allocator) void { } pub fn render(self: *Self, theme: *const Widget.Theme) bool { - tui.set_base_style(&self.plane, " ", theme.panel); + self.plane.set_base_style(" ", theme.panel); return false; } diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 7ac80d8..a28e440 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const nc = @import("notcurses"); const tp = @import("thespian"); const cbor = @import("cbor"); const tracy = @import("tracy"); @@ -7,6 +6,8 @@ const root = @import("root"); const location_history = @import("location_history"); const project_manager = @import("project_manager"); +const Plane = @import("renderer").Plane; + const tui = @import("tui.zig"); const command = @import("command.zig"); const Box = @import("Box.zig"); @@ -21,7 +22,7 @@ const Self = @This(); const Commands = command.Collection(cmds); a: std.mem.Allocator, -plane: nc.Plane, +plane: Plane, widgets: *WidgetList, widgets_widget: Widget, floating_views: WidgetStack, @@ -43,7 +44,7 @@ const NavState = struct { matches: usize = 0, }; -pub fn create(a: std.mem.Allocator, n: nc.Plane) !Widget { +pub fn create(a: std.mem.Allocator, n: Plane) !Widget { try project_manager.open_cwd(); const self = try a.create(Self); self.* = .{ diff --git a/src/tui/message_box.zig b/src/tui/message_box.zig deleted file mode 100644 index 4e97061..0000000 --- a/src/tui/message_box.zig +++ /dev/null @@ -1,44 +0,0 @@ -const Allocator = @import("std").mem.Allocator; -const nc = @import("notcurses"); -const tp = @import("thespian"); -const Widget = @import("Widget.zig"); -const tui = @import("tui.zig"); - -pub const name = @typeName(Self); -const Self = @This(); - -plane: nc.Plane, - -const y_pos = 10; -const y_pos_hidden = -15; -const x_pos = 10; - -pub fn create(a: Allocator, parent: nc.Plane) !Widget { - const self: *Self = try a.create(Self); - self.* = try init(parent); - return Widget.to(self); -} - -pub fn init(parent: nc.Plane) !Self { - var n = try nc.Plane.init(&(Widget.Box{}).opts_vscroll(name), parent); - errdefer n.deinit(); - return .{ - .plane = n, - }; -} - -pub fn deinit(self: *Self, a: Allocator) void { - self.plane.deinit(); - a.destroy(self); -} - -pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { - _ = self; - _ = m; - return false; -} - -pub fn render(self: *Self, theme: *const Widget.Theme) bool { - tui.set_base_style(&self.plane, " ", theme.sidebar); - return false; -} diff --git a/src/tui/mode/input/flow.zig b/src/tui/mode/input/flow.zig index 5970fe9..3737f02 100644 --- a/src/tui/mode/input/flow.zig +++ b/src/tui/mode/input/flow.zig @@ -1,7 +1,11 @@ -const nc = @import("notcurses"); const tp = @import("thespian"); const root = @import("root"); +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; +const egc_ = @import("renderer").egc; + const tui = @import("../../tui.zig"); const command = @import("../../command.zig"); const EventHandler = @import("../../EventHandler.zig"); @@ -10,8 +14,6 @@ const Allocator = @import("std").mem.Allocator; const ArrayList = @import("std").ArrayList; const json = @import("std").json; const eql = @import("std").mem.eql; -const mod = nc.mod; -const key = nc.key; const Self = @This(); const input_buffer_size = 1024; @@ -60,9 +62,9 @@ pub fn add_keybind() void {} fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) tp.result { return switch (evtype) { - nc.event_type.PRESS => self.mapPress(keypress, egc, modifiers), - nc.event_type.REPEAT => self.mapPress(keypress, egc, modifiers), - nc.event_type.RELEASE => self.mapRelease(keypress, egc, modifiers), + event_type.PRESS => self.mapPress(keypress, egc, modifiers), + event_type.REPEAT => self.mapPress(keypress, egc, modifiers), + event_type.RELEASE => self.mapRelease(keypress, egc, modifiers), else => {}, }; } @@ -248,7 +250,7 @@ fn insert_code_point(self: *Self, c: u32) tp.result { if (self.input.items.len + 4 > input_buffer_size) try self.flush_input(); var buf: [6]u8 = undefined; - const bytes = nc.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); + const bytes = egc_.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); self.input.appendSlice(buf[0..bytes]) catch |e| return tp.exit_error(e); } diff --git a/src/tui/mode/input/home.zig b/src/tui/mode/input/home.zig index c42df9b..b8946b7 100644 --- a/src/tui/mode/input/home.zig +++ b/src/tui/mode/input/home.zig @@ -1,8 +1,11 @@ const std = @import("std"); -const nc = @import("notcurses"); const tp = @import("thespian"); const root = @import("root"); +const key = @import("renderer").input.key; +const event_type = @import("renderer").input.event_type; +const mod = @import("renderer").input.modifier; + const tui = @import("../../tui.zig"); const command = @import("../../command.zig"); const EventHandler = @import("../../EventHandler.zig"); @@ -41,8 +44,8 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { fn mapEvent(self: *Self, evtype: u32, keypress: u32, modifiers: u32) tp.result { return switch (evtype) { - nc.event_type.PRESS => self.mapPress(keypress, modifiers), - nc.event_type.REPEAT => self.mapPress(keypress, modifiers), + event_type.PRESS => self.mapPress(keypress, modifiers), + event_type.REPEAT => self.mapPress(keypress, modifiers), else => {}, }; } @@ -50,7 +53,7 @@ fn mapEvent(self: *Self, evtype: u32, keypress: u32, modifiers: u32) tp.result { 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) { + mod.CTRL => switch (keynormal) { 'F' => self.sheeran(), 'J' => self.cmd("toggle_logview", .{}), 'Q' => self.cmd("quit", .{}), @@ -60,7 +63,7 @@ fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result { '/' => self.cmd("open_help", .{}), else => {}, }, - nc.mod.CTRL | nc.mod.SHIFT => switch (keynormal) { + mod.CTRL | mod.SHIFT => switch (keynormal) { 'Q' => self.cmd("quit_without_saving", .{}), 'R' => self.cmd("restart", .{}), 'F' => self.cmd("enter_find_in_files_mode", .{}), @@ -69,11 +72,11 @@ fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result { '/' => self.cmd("open_help", .{}), else => {}, }, - nc.mod.ALT => switch (keynormal) { + mod.ALT => switch (keynormal) { 'L' => self.cmd("toggle_logview", .{}), 'I' => self.cmd("toggle_inputview", .{}), - nc.key.LEFT => self.cmd("jump_back", .{}), - nc.key.RIGHT => self.cmd("jump_forward", .{}), + key.LEFT => self.cmd("jump_back", .{}), + key.RIGHT => self.cmd("jump_forward", .{}), else => {}, }, 0 => switch (keypress) { @@ -85,15 +88,15 @@ fn mapPress(self: *Self, keypress: u32, modifiers: u32) tp.result { 'c' => self.cmd("open_config", .{}), 'q' => self.cmd("quit", .{}), - nc.key.F01 => self.cmd("open_help", .{}), - nc.key.F06 => self.cmd("open_config", .{}), - 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.UP => self.cmd("home_menu_up", .{}), - nc.key.DOWN => self.cmd("home_menu_down", .{}), - nc.key.ENTER => self.cmd("home_menu_activate", .{}), + key.F01 => self.cmd("open_help", .{}), + key.F06 => self.cmd("open_config", .{}), + key.F09 => self.cmd("theme_prev", .{}), + key.F10 => self.cmd("theme_next", .{}), + key.F11 => self.cmd("toggle_logview", .{}), + key.F12 => self.cmd("toggle_inputview", .{}), + key.UP => self.cmd("home_menu_up", .{}), + key.DOWN => self.cmd("home_menu_down", .{}), + key.ENTER => self.cmd("home_menu_activate", .{}), else => {}, }, else => {}, diff --git a/src/tui/mode/input/vim/insert.zig b/src/tui/mode/input/vim/insert.zig index dab0b4c..4478b0d 100644 --- a/src/tui/mode/input/vim/insert.zig +++ b/src/tui/mode/input/vim/insert.zig @@ -1,7 +1,11 @@ -const nc = @import("notcurses"); const tp = @import("thespian"); const root = @import("root"); +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; +const egc_ = @import("renderer").egc; + const tui = @import("../../../tui.zig"); const command = @import("../../../command.zig"); const EventHandler = @import("../../../EventHandler.zig"); @@ -10,8 +14,6 @@ const Allocator = @import("std").mem.Allocator; const ArrayList = @import("std").ArrayList; const json = @import("std").json; const eql = @import("std").mem.eql; -const mod = nc.mod; -const key = nc.key; const Self = @This(); const input_buffer_size = 1024; @@ -63,9 +65,9 @@ pub fn add_keybind() void {} fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) tp.result { return switch (evtype) { - nc.event_type.PRESS => self.mapPress(keypress, egc, modifiers), - nc.event_type.REPEAT => self.mapPress(keypress, egc, modifiers), - nc.event_type.RELEASE => self.mapRelease(keypress, egc, modifiers), + event_type.PRESS => self.mapPress(keypress, egc, modifiers), + event_type.REPEAT => self.mapPress(keypress, egc, modifiers), + event_type.RELEASE => self.mapRelease(keypress, egc, modifiers), else => {}, }; } @@ -249,7 +251,7 @@ fn insert_code_point(self: *Self, c: u32) tp.result { if (self.input.items.len + 4 > input_buffer_size) try self.flush_input(); var buf: [6]u8 = undefined; - const bytes = nc.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); + const bytes = egc_.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); self.input.appendSlice(buf[0..bytes]) catch |e| return tp.exit_error(e); } diff --git a/src/tui/mode/input/vim/normal.zig b/src/tui/mode/input/vim/normal.zig index 09a53c4..6ee8d9b 100644 --- a/src/tui/mode/input/vim/normal.zig +++ b/src/tui/mode/input/vim/normal.zig @@ -1,7 +1,11 @@ -const nc = @import("notcurses"); const tp = @import("thespian"); const root = @import("root"); +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; +const egc_ = @import("renderer").egc; + const tui = @import("../../../tui.zig"); const command = @import("../../../command.zig"); const EventHandler = @import("../../../EventHandler.zig"); @@ -10,8 +14,6 @@ const Allocator = @import("std").mem.Allocator; const ArrayList = @import("std").ArrayList; const json = @import("std").json; const eql = @import("std").mem.eql; -const mod = nc.mod; -const key = nc.key; const Self = @This(); const input_buffer_size = 1024; @@ -64,9 +66,9 @@ pub fn add_keybind() void {} fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) tp.result { return switch (evtype) { - nc.event_type.PRESS => self.mapPress(keypress, egc, modifiers), - nc.event_type.REPEAT => self.mapPress(keypress, egc, modifiers), - nc.event_type.RELEASE => self.mapRelease(keypress, egc, modifiers), + event_type.PRESS => self.mapPress(keypress, egc, modifiers), + event_type.REPEAT => self.mapPress(keypress, egc, modifiers), + event_type.RELEASE => self.mapRelease(keypress, egc, modifiers), else => {}, }; } @@ -451,7 +453,7 @@ fn insert_code_point(self: *Self, c: u32) tp.result { if (self.input.items.len + 4 > input_buffer_size) try self.flush_input(); var buf: [6]u8 = undefined; - const bytes = nc.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); + const bytes = egc_.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); self.input.appendSlice(buf[0..bytes]) catch |e| return tp.exit_error(e); } diff --git a/src/tui/mode/input/vim/visual.zig b/src/tui/mode/input/vim/visual.zig index 7bc52ac..df6c945 100644 --- a/src/tui/mode/input/vim/visual.zig +++ b/src/tui/mode/input/vim/visual.zig @@ -1,7 +1,11 @@ -const nc = @import("notcurses"); const tp = @import("thespian"); const root = @import("root"); +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; +const egc_ = @import("renderer").egc; + const tui = @import("../../../tui.zig"); const command = @import("../../../command.zig"); const EventHandler = @import("../../../EventHandler.zig"); @@ -10,8 +14,6 @@ const Allocator = @import("std").mem.Allocator; const ArrayList = @import("std").ArrayList; const json = @import("std").json; const eql = @import("std").mem.eql; -const mod = nc.mod; -const key = nc.key; const Self = @This(); const input_buffer_size = 1024; @@ -64,9 +66,9 @@ pub fn add_keybind() void {} fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) tp.result { return switch (evtype) { - nc.event_type.PRESS => self.mapPress(keypress, egc, modifiers), - nc.event_type.REPEAT => self.mapPress(keypress, egc, modifiers), - nc.event_type.RELEASE => self.mapRelease(keypress, egc, modifiers), + event_type.PRESS => self.mapPress(keypress, egc, modifiers), + event_type.REPEAT => self.mapPress(keypress, egc, modifiers), + event_type.RELEASE => self.mapRelease(keypress, egc, modifiers), else => {}, }; } @@ -409,7 +411,7 @@ fn insert_code_point(self: *Self, c: u32) tp.result { if (self.input.items.len + 4 > input_buffer_size) try self.flush_input(); var buf: [6]u8 = undefined; - const bytes = nc.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); + const bytes = egc_.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); self.input.appendSlice(buf[0..bytes]) catch |e| return tp.exit_error(e); } diff --git a/src/tui/mode/mini/find.zig b/src/tui/mode/mini/find.zig index 9061ceb..7531ec1 100644 --- a/src/tui/mode/mini/find.zig +++ b/src/tui/mode/mini/find.zig @@ -1,6 +1,10 @@ -const nc = @import("notcurses"); const tp = @import("thespian"); +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; +const egc_ = @import("renderer").egc; + const tui = @import("../../tui.zig"); const mainview = @import("../../mainview.zig"); const command = @import("../../command.zig"); @@ -10,8 +14,6 @@ const ed = @import("../../editor.zig"); const Allocator = @import("std").mem.Allocator; const json = @import("std").json; const eql = @import("std").mem.eql; -const mod = nc.mod; -const key = nc.key; const Self = @This(); @@ -83,9 +85,9 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) tp.result { switch (evtype) { - nc.event_type.PRESS => try self.mapPress(keypress, egc, modifiers), - nc.event_type.REPEAT => try self.mapPress(keypress, egc, modifiers), - nc.event_type.RELEASE => try self.mapRelease(keypress, egc, modifiers), + event_type.PRESS => try self.mapPress(keypress, egc, modifiers), + event_type.REPEAT => try self.mapPress(keypress, egc, modifiers), + event_type.RELEASE => try self.mapRelease(keypress, egc, modifiers), else => {}, } } @@ -159,7 +161,7 @@ fn mapRelease(self: *Self, keypress: u32, _: u32, _: u32) tp.result { fn insert_code_point(self: *Self, c: u32) tp.result { if (self.input.len + 16 > self.buf.len) try self.flush_input(); - const bytes = nc.ucs32_to_utf8(&[_]u32{c}, self.buf[self.input.len..]) catch |e| return tp.exit_error(e); + const bytes = egc_.ucs32_to_utf8(&[_]u32{c}, self.buf[self.input.len..]) catch |e| return tp.exit_error(e); self.input = self.buf[0 .. self.input.len + bytes]; } diff --git a/src/tui/mode/mini/find_in_files.zig b/src/tui/mode/mini/find_in_files.zig index 4853238..1b9b00f 100644 --- a/src/tui/mode/mini/find_in_files.zig +++ b/src/tui/mode/mini/find_in_files.zig @@ -1,6 +1,10 @@ -const nc = @import("notcurses"); const tp = @import("thespian"); +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; +const egc_ = @import("renderer").egc; + const tui = @import("../../tui.zig"); const mainview = @import("../../mainview.zig"); const command = @import("../../command.zig"); @@ -10,8 +14,6 @@ const ed = @import("../../editor.zig"); const Allocator = @import("std").mem.Allocator; const json = @import("std").json; const eql = @import("std").mem.eql; -const mod = nc.mod; -const key = nc.key; const Self = @This(); @@ -82,9 +84,9 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) tp.result { switch (evtype) { - nc.event_type.PRESS => try self.mapPress(keypress, egc, modifiers), - nc.event_type.REPEAT => try self.mapPress(keypress, egc, modifiers), - nc.event_type.RELEASE => try self.mapRelease(keypress, egc, modifiers), + event_type.PRESS => try self.mapPress(keypress, egc, modifiers), + event_type.REPEAT => try self.mapPress(keypress, egc, modifiers), + event_type.RELEASE => try self.mapRelease(keypress, egc, modifiers), else => {}, } } @@ -156,7 +158,7 @@ fn mapRelease(self: *Self, keypress: u32, _: u32, _: u32) tp.result { fn insert_code_point(self: *Self, c: u32) tp.result { if (self.input.len + 16 > self.buf.len) try self.flush_input(); - const bytes = nc.ucs32_to_utf8(&[_]u32{c}, self.buf[self.input.len..]) catch |e| return tp.exit_error(e); + const bytes = egc_.ucs32_to_utf8(&[_]u32{c}, self.buf[self.input.len..]) catch |e| return tp.exit_error(e); self.input = self.buf[0 .. self.input.len + bytes]; } diff --git a/src/tui/mode/mini/goto.zig b/src/tui/mode/mini/goto.zig index 77ab070..a22dd90 100644 --- a/src/tui/mode/mini/goto.zig +++ b/src/tui/mode/mini/goto.zig @@ -1,6 +1,9 @@ -const nc = @import("notcurses"); const tp = @import("thespian"); +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; + const tui = @import("../../tui.zig"); const mainview = @import("../../mainview.zig"); const command = @import("../../command.zig"); @@ -10,8 +13,6 @@ const Allocator = @import("std").mem.Allocator; const json = @import("std").json; const eql = @import("std").mem.eql; const fmt = @import("std").fmt; -const mod = nc.mod; -const key = nc.key; const Self = @This(); @@ -64,8 +65,8 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { fn mapEvent(self: *Self, evtype: u32, keypress: u32, modifiers: u32) tp.result { switch (evtype) { - nc.event_type.PRESS => try self.mapPress(keypress, modifiers), - nc.event_type.REPEAT => try self.mapPress(keypress, modifiers), + event_type.PRESS => try self.mapPress(keypress, modifiers), + event_type.REPEAT => try self.mapPress(keypress, modifiers), else => {}, } } diff --git a/src/tui/mode/mini/move_to_char.zig b/src/tui/mode/mini/move_to_char.zig index 6c29913..b46ef91 100644 --- a/src/tui/mode/mini/move_to_char.zig +++ b/src/tui/mode/mini/move_to_char.zig @@ -1,6 +1,10 @@ -const nc = @import("notcurses"); const tp = @import("thespian"); +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; +const egc_ = @import("renderer").egc; + const tui = @import("../../tui.zig"); const mainview = @import("../../mainview.zig"); const command = @import("../../command.zig"); @@ -10,8 +14,6 @@ const Allocator = @import("std").mem.Allocator; const json = @import("std").json; const eql = @import("std").mem.eql; const fmt = @import("std").fmt; -const mod = nc.mod; -const key = nc.key; const Self = @This(); @@ -76,7 +78,7 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) tp.result { switch (evtype) { - nc.event_type.PRESS => try self.mapPress(keypress, egc, modifiers), + event_type.PRESS => try self.mapPress(keypress, egc, modifiers), else => {}, } } @@ -112,7 +114,7 @@ fn execute_operation(self: *Self, c: u32) void { }, }; var buf: [6]u8 = undefined; - const bytes = nc.ucs32_to_utf8(&[_]u32{c}, &buf) catch return; + const bytes = egc_.ucs32_to_utf8(&[_]u32{c}, &buf) catch return; command.executeName(cmd, command.fmt(.{buf[0..bytes]})) catch {}; command.executeName("exit_mini_mode", .{}) catch {}; } diff --git a/src/tui/mode/mini/open_file.zig b/src/tui/mode/mini/open_file.zig index a29cbfc..9d8558c 100644 --- a/src/tui/mode/mini/open_file.zig +++ b/src/tui/mode/mini/open_file.zig @@ -1,7 +1,11 @@ const std = @import("std"); -const nc = @import("notcurses"); const tp = @import("thespian"); +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; +const egc_ = @import("renderer").egc; + const tui = @import("../../tui.zig"); const mainview = @import("../../mainview.zig"); const command = @import("../../command.zig"); @@ -68,9 +72,9 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) tp.result { switch (evtype) { - nc.event_type.PRESS => try self.mapPress(keypress, egc, modifiers), - nc.event_type.REPEAT => try self.mapPress(keypress, egc, modifiers), - nc.event_type.RELEASE => try self.mapRelease(keypress, egc, modifiers), + event_type.PRESS => try self.mapPress(keypress, egc, modifiers), + event_type.REPEAT => try self.mapPress(keypress, egc, modifiers), + event_type.RELEASE => try self.mapRelease(keypress, egc, modifiers), else => {}, } } @@ -78,7 +82,7 @@ fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) t fn mapPress(self: *Self, keypress: u32, egc: 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) { + mod.CTRL => switch (keynormal) { 'Q' => self.cmd("quit", .{}), 'V' => self.cmd("system_paste", .{}), 'U' => self.file_path.clearRetainingCapacity(), @@ -86,30 +90,30 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { 'C' => self.cancel(), 'L' => self.cmd("scroll_view_center", .{}), 'I' => self.insert_bytes("\t"), - nc.key.SPACE => self.cancel(), - nc.key.BACKSPACE => self.file_path.clearRetainingCapacity(), + key.SPACE => self.cancel(), + key.BACKSPACE => self.file_path.clearRetainingCapacity(), else => {}, }, - nc.mod.ALT => switch (keynormal) { + mod.ALT => switch (keynormal) { 'V' => self.cmd("system_paste", .{}), else => {}, }, - nc.mod.ALT | nc.mod.SHIFT => switch (keynormal) { + mod.ALT | mod.SHIFT => switch (keynormal) { 'V' => self.cmd("system_paste", .{}), else => {}, }, - nc.mod.SHIFT => switch (keypress) { - else => if (!nc.key.synthesized_p(keypress)) + mod.SHIFT => switch (keypress) { + else => if (!key.synthesized_p(keypress)) self.insert_code_point(egc) else {}, }, 0 => switch (keypress) { - nc.key.ESC => self.cancel(), - nc.key.ENTER => self.navigate(), - nc.key.BACKSPACE => if (self.file_path.items.len > 0) { + key.ESC => self.cancel(), + key.ENTER => self.navigate(), + key.BACKSPACE => if (self.file_path.items.len > 0) { self.file_path.shrinkRetainingCapacity(self.file_path.items.len - 1); }, - else => if (!nc.key.synthesized_p(keypress)) + else => if (!key.synthesized_p(keypress)) self.insert_code_point(egc) else {}, }, @@ -121,7 +125,7 @@ fn mapRelease(_: *Self, _: u32, _: u32, _: u32) tp.result {} fn insert_code_point(self: *Self, c: u32) tp.result { var buf: [32]u8 = undefined; - const bytes = nc.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); + const bytes = egc_.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); self.file_path.appendSlice(buf[0..bytes]) catch |e| return tp.exit_error(e); } diff --git a/src/tui/mode/overlay/open_recent.zig b/src/tui/mode/overlay/open_recent.zig index a9ea72f..819e7f2 100644 --- a/src/tui/mode/overlay/open_recent.zig +++ b/src/tui/mode/overlay/open_recent.zig @@ -1,9 +1,15 @@ const std = @import("std"); -const nc = @import("notcurses"); const tp = @import("thespian"); const log = @import("log"); const cbor = @import("cbor"); +const Plane = @import("renderer").Plane; +const planeutils = @import("renderer").planeutils; +const key = @import("renderer").input.key; +const mod = @import("renderer").input.modifier; +const event_type = @import("renderer").input.event_type; +const egc_ = @import("renderer").egc; + const tui = @import("../../tui.zig"); const command = @import("../../command.zig"); const EventHandler = @import("../../EventHandler.zig"); @@ -66,7 +72,7 @@ pub fn deinit(self: *Self) void { fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool { const style_base = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget; - try tui.set_base_style_alpha(button.plane, " ", style_base, nc.ALPHA_OPAQUE, nc.ALPHA_OPAQUE); + button.plane.set_base_style(" ", style_base); button.plane.erase(); button.plane.home(); var file_path: []const u8 = undefined; @@ -89,11 +95,11 @@ fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *c return false; } -fn render_cell(plane: nc.Plane, y: usize, x: usize, style: Widget.Theme.Style) !void { +fn render_cell(plane: Plane, y: usize, x: usize, style: Widget.Theme.Style) !void { plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return; var cell = plane.cell_init(); _ = plane.at_cursor_cell(&cell) catch return; - tui.set_cell_style(&cell, style); + cell.set_style(style); _ = plane.putc(&cell) catch {}; } @@ -203,9 +209,9 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) tp.result { return switch (evtype) { - nc.event_type.PRESS => self.mapPress(keypress, egc, modifiers), - nc.event_type.REPEAT => self.mapPress(keypress, egc, modifiers), - nc.event_type.RELEASE => self.mapRelease(keypress, modifiers), + event_type.PRESS => self.mapPress(keypress, egc, modifiers), + event_type.REPEAT => self.mapPress(keypress, egc, modifiers), + event_type.RELEASE => self.mapRelease(keypress, modifiers), else => {}, }; } @@ -213,7 +219,7 @@ fn mapEvent(self: *Self, evtype: u32, keypress: u32, egc: u32, modifiers: u32) t fn mapPress(self: *Self, keypress: u32, egc: 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) { + mod.CTRL => switch (keynormal) { 'J' => self.cmd("toggle_logview", .{}), 'Q' => self.cmd("quit", .{}), 'W' => self.cmd("close_file", .{}), @@ -223,14 +229,14 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { 'V' => self.cmd("system_paste", .{}), 'C' => self.cmd("exit_overlay_mode", .{}), 'G' => self.cmd("exit_overlay_mode", .{}), - nc.key.ESC => self.cmd("exit_overlay_mode", .{}), - nc.key.UP => self.cmd("open_recent_menu_up", .{}), - nc.key.DOWN => self.cmd("open_recent_menu_down", .{}), - nc.key.ENTER => self.cmd("open_recent_menu_activate", .{}), - nc.key.BACKSPACE => self.delete_word(), + key.ESC => self.cmd("exit_overlay_mode", .{}), + key.UP => self.cmd("open_recent_menu_up", .{}), + key.DOWN => self.cmd("open_recent_menu_down", .{}), + key.ENTER => self.cmd("open_recent_menu_activate", .{}), + key.BACKSPACE => self.delete_word(), else => {}, }, - nc.mod.CTRL | nc.mod.SHIFT => switch (keynormal) { + mod.CTRL | mod.SHIFT => switch (keynormal) { 'Q' => self.cmd("quit_without_saving", .{}), 'W' => self.cmd("close_file_without_saving", .{}), 'R' => self.cmd("restart", .{}), @@ -239,27 +245,27 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { 'E' => self.cmd("open_recent_menu_up", .{}), else => {}, }, - nc.mod.ALT => switch (keynormal) { + mod.ALT => switch (keynormal) { 'L' => self.cmd("toggle_logview", .{}), 'I' => self.cmd("toggle_inputview", .{}), else => {}, }, - nc.mod.SHIFT => switch (keypress) { - else => if (!nc.key.synthesized_p(keypress)) + mod.SHIFT => switch (keypress) { + else => if (!key.synthesized_p(keypress)) self.insert_code_point(egc) 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.UP => self.cmd("open_recent_menu_up", .{}), - nc.key.DOWN => self.cmd("open_recent_menu_down", .{}), - nc.key.ENTER => self.cmd("open_recent_menu_activate", .{}), - nc.key.BACKSPACE => self.delete_code_point(), - else => if (!nc.key.synthesized_p(keypress)) + key.F09 => self.cmd("theme_prev", .{}), + key.F10 => self.cmd("theme_next", .{}), + key.F11 => self.cmd("toggle_logview", .{}), + key.F12 => self.cmd("toggle_inputview", .{}), + key.ESC => self.cmd("exit_overlay_mode", .{}), + key.UP => self.cmd("open_recent_menu_up", .{}), + key.DOWN => self.cmd("open_recent_menu_down", .{}), + key.ENTER => self.cmd("open_recent_menu_activate", .{}), + key.BACKSPACE => self.delete_code_point(), + else => if (!key.synthesized_p(keypress)) self.insert_code_point(egc) else {}, }, @@ -269,7 +275,7 @@ fn mapPress(self: *Self, keypress: u32, egc: u32, modifiers: u32) tp.result { fn mapRelease(self: *Self, keypress: u32, _: u32) tp.result { return switch (keypress) { - nc.key.LCTRL, nc.key.RCTRL => if (self.menu.selected orelse 0 > 0) return self.cmd("open_recent_menu_activate", .{}), + key.LCTRL, key.RCTRL => if (self.menu.selected orelse 0 > 0) return self.cmd("open_recent_menu_activate", .{}), else => {}, }; } @@ -307,7 +313,7 @@ fn delete_code_point(self: *Self) tp.result { fn insert_code_point(self: *Self, c: u32) tp.result { var buf: [6]u8 = undefined; - const bytes = nc.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); + const bytes = egc_.ucs32_to_utf8(&[_]u32{c}, &buf) catch |e| return tp.exit_error(e); self.inputbox.text.appendSlice(buf[0..bytes]) catch |e| return tp.exit_error(e); self.inputbox.cursor = self.inputbox.text.items.len; return self.start_query(); diff --git a/src/tui/scrollbar_v.zig b/src/tui/scrollbar_v.zig index c3d9769..60c130e 100644 --- a/src/tui/scrollbar_v.zig +++ b/src/tui/scrollbar_v.zig @@ -1,13 +1,16 @@ const Allocator = @import("std").mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); const tracy = @import("tracy"); +const Plane = @import("renderer").Plane; +const key = @import("renderer").input.key; +const event_type = @import("renderer").input.event_type; + const Widget = @import("Widget.zig"); const EventHandler = @import("EventHandler.zig"); const tui = @import("tui.zig"); -plane: nc.Plane, +plane: Plane, pos_scrn: u32 = 0, view_scrn: u32 = 8, size_scrn: u32 = 8, @@ -33,7 +36,7 @@ pub fn create(a: Allocator, parent: Widget, event_source: Widget) !Widget { fn init(parent: Widget) !Self { return .{ - .plane = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent.plane.*), + .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent.plane.*), .parent = parent, }; } @@ -63,21 +66,21 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { var y: i32 = undefined; var ypx: i32 = undefined; - if (try m.match(.{ "B", nc.event_type.PRESS, nc.key.BUTTON1, tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) { + if (try m.match(.{ "B", event_type.PRESS, key.BUTTON1, tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) { self.active = true; self.move_to(y, ypx); return true; - } else if (try m.match(.{ "B", nc.event_type.RELEASE, tp.more })) { + } else if (try m.match(.{ "B", event_type.RELEASE, tp.more })) { self.active = false; return true; - } else if (try m.match(.{ "D", nc.event_type.PRESS, nc.key.BUTTON1, tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) { + } else if (try m.match(.{ "D", event_type.PRESS, key.BUTTON1, tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) { self.active = true; self.move_to(y, ypx); return true; - } else if (try m.match(.{ "B", nc.event_type.RELEASE, tp.more })) { + } else if (try m.match(.{ "B", event_type.RELEASE, tp.more })) { self.active = false; return true; - } else if (try m.match(.{ "D", nc.event_type.RELEASE, tp.more })) { + } else if (try m.match(.{ "D", event_type.RELEASE, tp.more })) { self.active = false; return true; } else if (try m.match(.{ "H", tp.extract(&self.hover) })) { @@ -118,7 +121,7 @@ fn pos_scrn_to_virt(self: Self, pos_scrn_: u32) u32 { pub fn render(self: *Self, theme: *const Widget.Theme) bool { const frame = tracy.initZone(@src(), .{ .name = "scrollbar_v render" }); defer frame.deinit(); - tui.set_base_style(&self.plane, " ", if (self.active) theme.scrollbar_active else if (self.hover) theme.scrollbar_hover else theme.scrollbar); + self.plane.set_base_style(" ", if (self.active) theme.scrollbar_active else if (self.hover) theme.scrollbar_hover else theme.scrollbar); self.plane.erase(); smooth_bar_at(self.plane, @intCast(self.pos_scrn), @intCast(self.view_scrn)) catch {}; return false; @@ -148,7 +151,7 @@ const eighths_b = [_][]const u8{ "█", "▇", "▆", "▅", "▄", "▃", "▂" const eighths_t = [_][]const u8{ " ", "▔", "🮂", "🮃", "▀", "🮄", "🮅", "🮆" }; const eighths_c: i32 = @intCast(eighths_b.len); -fn smooth_bar_at(plane: nc.Plane, pos_: i32, size_: i32) !void { +fn smooth_bar_at(plane: Plane, pos_: i32, size_: i32) !void { const height: i32 = @intCast(plane.dim_y()); var size = @max(size_, 8); const pos: i32 = @min(height * eighths_c - size, pos_); diff --git a/src/tui/status/diagstate.zig b/src/tui/status/diagstate.zig index 981a63a..cabd008 100644 --- a/src/tui/status/diagstate.zig +++ b/src/tui/status/diagstate.zig @@ -1,9 +1,10 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); const tracy = @import("tracy"); +const Plane = @import("renderer").Plane; + const Widget = @import("../Widget.zig"); const Button = @import("../Button.zig"); const tui = @import("../tui.zig"); @@ -18,7 +19,7 @@ rendered: [:0]const u8 = "", const Self = @This(); -pub fn create(a: Allocator, parent: nc.Plane) !Widget { +pub fn create(a: Allocator, parent: Plane) !Widget { return Button.create_widget(Self, a, parent, .{ .ctx = .{}, .label = "", @@ -39,7 +40,7 @@ pub fn layout(self: *Self, _: *Button.State(Self)) Widget.Layout { pub fn render(self: *Self, btn: *Button.State(Self), theme: *const Widget.Theme) bool { const bg_style = if (btn.active) theme.editor_cursor else if (btn.hover) theme.statusbar_hover else theme.statusbar; - tui.set_base_style(&btn.plane, " ", bg_style); + btn.plane.set_base_style(" ", bg_style); btn.plane.erase(); btn.plane.home(); _ = btn.plane.putstr(self.rendered) catch {}; diff --git a/src/tui/status/filestate.zig b/src/tui/status/filestate.zig index e8b1732..1c3d123 100644 --- a/src/tui/status/filestate.zig +++ b/src/tui/status/filestate.zig @@ -1,10 +1,12 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); const tracy = @import("tracy"); const root = @import("root"); +const Plane = @import("renderer").Plane; +const style = @import("renderer").style; + const Widget = @import("../Widget.zig"); const Button = @import("../Button.zig"); const command = @import("../command.zig"); @@ -31,7 +33,7 @@ project: bool = false, const project_icon = ""; const Self = @This(); -pub fn create(a: Allocator, parent: nc.Plane) !Widget { +pub fn create(a: Allocator, parent: Plane) !Widget { const btn = try Button.create(Self, a, parent, .{ .ctx = .{ .a = a, @@ -73,7 +75,7 @@ pub fn layout(_: *Self, _: *Button.State(Self)) Widget.Layout { pub fn render(self: *Self, btn: *Button.State(Self), theme: *const Widget.Theme) bool { const frame = tracy.initZone(@src(), .{ .name = @typeName(@This()) ++ " render" }); defer frame.deinit(); - tui.set_base_style(&btn.plane, " ", if (btn.active) theme.editor_cursor else theme.statusbar); + btn.plane.set_base_style(" ", if (btn.active) theme.editor_cursor else theme.statusbar); btn.plane.erase(); btn.plane.home(); if (tui.current().mini_mode) |_| @@ -86,8 +88,8 @@ pub fn render(self: *Self, btn: *Button.State(Self), theme: *const Widget.Theme) return false; } -fn render_mini_mode(plane: *nc.Plane, theme: *const Widget.Theme) void { - plane.off_styles(nc.style.italic); +fn render_mini_mode(plane: *Plane, theme: *const Widget.Theme) void { + plane.off_styles(style.italic); const mini_mode = if (tui.current().mini_mode) |m| m else return; _ = plane.print(" {s}", .{mini_mode.text}) catch {}; if (mini_mode.cursor) |cursor| { @@ -95,7 +97,7 @@ fn render_mini_mode(plane: *nc.Plane, theme: *const Widget.Theme) void { plane.cursor_move_yx(0, pos + 1) catch return; var cell = plane.cell_init(); _ = plane.at_cursor_cell(&cell) catch return; - tui.set_cell_style(&cell, theme.editor_cursor); + cell.set_style(theme.editor_cursor); _ = plane.putc(&cell) catch {}; } return; @@ -109,8 +111,8 @@ fn render_mini_mode(plane: *nc.Plane, theme: *const Widget.Theme) void { // 󱣪 Content save check // 󱑛 Content save cog // 󰆔 Content save all -fn render_normal(self: *Self, plane: *nc.Plane, theme: *const Widget.Theme) void { - plane.on_styles(nc.style.italic); +fn render_normal(self: *Self, plane: *Plane, theme: *const Widget.Theme) void { + plane.on_styles(style.italic); _ = plane.putstr(" ") catch {}; if (self.file_icon.len > 0) { self.render_file_icon(plane, theme); @@ -121,8 +123,8 @@ fn render_normal(self: *Self, plane: *nc.Plane, theme: *const Widget.Theme) void return; } -fn render_detailed(self: *Self, plane: *nc.Plane, theme: *const Widget.Theme) void { - plane.on_styles(nc.style.italic); +fn render_detailed(self: *Self, plane: *Plane, theme: *const Widget.Theme) void { + plane.on_styles(style.italic); _ = plane.putstr(" ") catch {}; if (self.file_icon.len > 0) { self.render_file_icon(plane, theme); @@ -153,7 +155,7 @@ fn render_terminal_title(self: *Self) void { if (std.mem.eql(u8, self.title, new_title)) return; @memcpy(self.title_buf[0..new_title.len], new_title); self.title = self.title_buf[0..new_title.len]; - tui.set_terminal_title(self.title); + tui.renderer.set_terminal_title(self.title); } pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool { @@ -193,12 +195,11 @@ pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message return false; } -fn render_file_icon(self: *Self, plane: *nc.Plane, _: *const Widget.Theme) void { +fn render_file_icon(self: *Self, plane: *Plane, _: *const Widget.Theme) void { var cell = plane.cell_init(); _ = plane.at_cursor_cell(&cell) catch return; if (!(self.file_color == 0xFFFFFF or self.file_color == 0x000000 or self.file_color == 0x000001)) { - nc.channels_set_fg_rgb(&cell.channels, self.file_color) catch {}; - nc.channels_set_fg_alpha(&cell.channels, nc.ALPHA_OPAQUE) catch {}; + cell.set_fg_rgb(self.file_color) catch {}; } _ = plane.cell_load(&cell, self.file_icon) catch {}; _ = plane.putc(&cell) catch {}; diff --git a/src/tui/status/keystate.zig b/src/tui/status/keystate.zig index 550ff43..1368d95 100644 --- a/src/tui/status/keystate.zig +++ b/src/tui/status/keystate.zig @@ -1,9 +1,13 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); const tracy = @import("tracy"); +const Plane = @import("renderer").Plane; +const utils = @import("renderer").input.utils; +const key_ = @import("renderer").input.key; +const event_type = @import("renderer").input.event_type; + const Widget = @import("../Widget.zig"); const command = @import("../command.zig"); const tui = @import("../tui.zig"); @@ -11,8 +15,8 @@ const EventHandler = @import("../EventHandler.zig"); const history = 8; -parent: nc.Plane, -plane: nc.Plane, +parent: Plane, +plane: Plane, frame: u64 = 0, idle_frame: u64 = 0, key_active_frame: u64 = 0, @@ -28,15 +32,15 @@ const Self = @This(); const idle_msg = "🐶"; pub const width = idle_msg.len + 20; -pub fn create(a: Allocator, parent: nc.Plane) !Widget { +pub fn create(a: Allocator, parent: Plane) !Widget { const self: *Self = try a.create(Self); self.* = try init(parent); try tui.current().input_listeners.add(EventHandler.bind(self, listen)); return self.widget(); } -fn init(parent: nc.Plane) !Self { - var n = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent); +fn init(parent: Plane) !Self { + var n = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent); errdefer n.deinit(); var frame_rate = tp.env.get().num("frame-rate"); if (frame_rate == 0) frame_rate = 60; @@ -69,15 +73,15 @@ fn render_active(self: *Self) bool { return true; if (c > 0) _ = self.plane.putstr(" ") catch {}; - if (nc.isSuper(k.mod)) + if (utils.isSuper(k.mod)) _ = self.plane.putstr("H-") catch {}; - if (nc.isCtrl(k.mod)) + if (utils.isCtrl(k.mod)) _ = self.plane.putstr("C-") catch {}; - if (nc.isShift(k.mod)) + if (utils.isShift(k.mod)) _ = self.plane.putstr("S-") catch {}; - if (nc.isAlt(k.mod)) + if (utils.isAlt(k.mod)) _ = self.plane.putstr("A-") catch {}; - _ = self.plane.print("{s}", .{nc.key_id_string(k.id)}) catch {}; + _ = self.plane.print("{s}", .{utils.key_id_string(k.id)}) catch {}; c += 1; } return true; @@ -91,7 +95,7 @@ fn render_idle(self: *Self) bool { return self.animate(); } else { const i = @mod(self.idle_frame / 8, idle_spinner.len); - _ = self.plane.print_aligned(0, .center, "{s} {s} {s}", .{ idle_spinner[i], idle_msg, idle_spinner[i] }) catch {}; + _ = self.plane.print_aligned_center(0, "{s} {s} {s}", .{ idle_spinner[i], idle_msg, idle_spinner[i] }) catch {}; } return true; } @@ -99,7 +103,7 @@ fn render_idle(self: *Self) bool { pub fn render(self: *Self, theme: *const Widget.Theme) bool { const frame = tracy.initZone(@src(), .{ .name = @typeName(@This()) ++ " render" }); defer frame.deinit(); - tui.set_base_style(&self.plane, " ", if (self.hover) theme.statusbar_hover else theme.statusbar); + self.plane.set_base_style(" ", if (self.hover) theme.statusbar_hover else theme.statusbar); self.frame += 1; if (self.frame - self.key_active_frame > self.wipe_after_frames) self.unset_key_all(); @@ -163,20 +167,20 @@ fn set_key(self: *Self, key: Key, val: bool) void { pub fn listen(self: *Self, _: tp.pid_ref, m: tp.message) tp.result { var key: u32 = 0; var mod: u32 = 0; - if (try m.match(.{ "I", nc.event_type.PRESS, tp.extract(&key), tp.any, tp.any, tp.extract(&mod), tp.more })) { + if (try m.match(.{ "I", event_type.PRESS, tp.extract(&key), tp.any, tp.any, tp.extract(&mod), tp.more })) { self.set_key(.{ .id = key, .mod = mod }, true); - } else if (try m.match(.{ "I", nc.event_type.RELEASE, tp.extract(&key), tp.any, tp.any, tp.extract(&mod), tp.more })) { + } else if (try m.match(.{ "I", event_type.RELEASE, tp.extract(&key), tp.any, tp.any, tp.extract(&mod), tp.more })) { self.set_key(.{ .id = key, .mod = mod }, false); } } 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 })) { + if (try m.match(.{ "B", event_type.PRESS, key_.BUTTON1, tp.any, tp.any, tp.any, tp.any, tp.any })) { command.executeName("toggle_inputview", .{}) catch {}; return true; } if (try m.match(.{ "H", tp.extract(&self.hover) })) { - tui.current().request_mouse_cursor_pointer(self.hover); + tui.renderer.request_mouse_cursor_pointer(self.hover); return true; } @@ -200,7 +204,7 @@ const eighths_l = [_][]const u8{ "█", "▉", "▊", "▋", "▌", "▍", "▎" const eighths_r = [_][]const u8{ " ", "▕", "🮇", "🮈", "▐", "🮉", "🮊", "🮋" }; const eighths_c = eighths_l.len; -fn smooth_block_at(plane: nc.Plane, pos: u64) void { +fn smooth_block_at(plane: Plane, pos: u64) void { const blk = @mod(pos, eighths_c) + 1; const l = eighths_l[eighths_c - blk]; const r = eighths_r[eighths_c - blk]; diff --git a/src/tui/status/linenumstate.zig b/src/tui/status/linenumstate.zig index 30052a0..a51ed8a 100644 --- a/src/tui/status/linenumstate.zig +++ b/src/tui/status/linenumstate.zig @@ -1,9 +1,10 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); const tracy = @import("tracy"); +const Plane = @import("renderer").Plane; + const Widget = @import("../Widget.zig"); const Button = @import("../Button.zig"); const tui = @import("../tui.zig"); @@ -17,7 +18,7 @@ rendered: [:0]const u8 = "", const Self = @This(); -pub fn create(a: Allocator, parent: nc.Plane) !Widget { +pub fn create(a: Allocator, parent: Plane) !Widget { return Button.create_widget(Self, a, parent, .{ .ctx = .{}, .label = "", @@ -37,7 +38,7 @@ pub fn layout(self: *Self, _: *Button.State(Self)) Widget.Layout { } pub fn render(self: *Self, btn: *Button.State(Self), theme: *const Widget.Theme) bool { - tui.set_base_style(&btn.plane, " ", if (btn.active) theme.editor_cursor else if (btn.hover) theme.statusbar_hover else theme.statusbar); + btn.plane.set_base_style(" ", if (btn.active) theme.editor_cursor else if (btn.hover) theme.statusbar_hover else theme.statusbar); btn.plane.erase(); btn.plane.home(); _ = btn.plane.putstr(self.rendered) catch {}; diff --git a/src/tui/status/minilog.zig b/src/tui/status/minilog.zig index aaae68e..121f425 100644 --- a/src/tui/status/minilog.zig +++ b/src/tui/status/minilog.zig @@ -1,15 +1,16 @@ const std = @import("std"); -const nc = @import("notcurses"); const tp = @import("thespian"); const log = @import("log"); +const Plane = @import("renderer").Plane; + const Widget = @import("../Widget.zig"); const MessageFilter = @import("../MessageFilter.zig"); const tui = @import("../tui.zig"); const mainview = @import("../mainview.zig"); -parent: nc.Plane, -plane: nc.Plane, +parent: Plane, +plane: Plane, msg: std.ArrayList(u8), is_error: bool = false, clear_time: i64 = 0, @@ -18,11 +19,11 @@ const message_display_time_seconds = 2; const error_display_time_seconds = 4; const Self = @This(); -pub fn create(a: std.mem.Allocator, parent: nc.Plane) !Widget { +pub fn create(a: std.mem.Allocator, parent: Plane) !Widget { const self: *Self = try a.create(Self); self.* = .{ .parent = parent, - .plane = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), + .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), .msg = std.ArrayList(u8).init(a), }; try tui.current().message_filters.add(MessageFilter.bind(self, receive_log)); @@ -43,11 +44,11 @@ pub fn layout(self: *Self) Widget.Layout { } pub fn render(self: *Self, theme: *const Widget.Theme) bool { - tui.set_base_style(&self.plane, " ", if (self.msg.items.len > 0) theme.sidebar else theme.statusbar); + self.plane.set_base_style(" ", if (self.msg.items.len > 0) theme.sidebar else theme.statusbar); self.plane.erase(); self.plane.home(); if (self.is_error) - tui.set_base_style(&self.plane, " ", theme.editor_error); + self.plane.set_base_style(" ", theme.editor_error); _ = self.plane.print(" {s} ", .{self.msg.items}) catch {}; const curr_time = std.time.milliTimestamp(); diff --git a/src/tui/status/modestate.zig b/src/tui/status/modestate.zig index 2d91189..a6b6c86 100644 --- a/src/tui/status/modestate.zig +++ b/src/tui/status/modestate.zig @@ -1,10 +1,12 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); const tracy = @import("tracy"); const root = @import("root"); -const Buffer = @import("Buffer"); +const egc = @import("renderer").egc; + +const Plane = @import("renderer").Plane; +const style = @import("renderer").style; const Widget = @import("../Widget.zig"); const Menu = @import("../Menu.zig"); @@ -13,7 +15,7 @@ const command = @import("../command.zig"); const ed = @import("../editor.zig"); const tui = @import("../tui.zig"); -pub fn create(a: Allocator, parent: nc.Plane) !Widget { +pub fn create(a: Allocator, parent: Plane) !Widget { return Button.create_widget(void, a, parent, .{ .ctx = {}, .label = tui.get_mode(), @@ -25,7 +27,7 @@ pub fn create(a: Allocator, parent: nc.Plane) !Widget { pub fn layout(_: *void, _: *Button.State(void)) Widget.Layout { const name = tui.get_mode(); - const width = Buffer.egc_chunk_width(name, 0); + const width = egc.chunk_width(name, 0); const padding: usize = if (is_mini_mode()) 3 else 2; return .{ .static = width + padding }; } @@ -39,8 +41,8 @@ fn is_overlay_mode() 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.set_base_style(" ", if (self.active) theme.editor_cursor else if (self.hover) theme.editor_selection else theme.statusbar_hover); + self.plane.on_styles(style.bold); self.plane.erase(); self.plane.home(); var buf: [31:0]u8 = undefined; diff --git a/src/tui/status/modstate.zig b/src/tui/status/modstate.zig index 1df8030..c8959dd 100644 --- a/src/tui/status/modstate.zig +++ b/src/tui/status/modstate.zig @@ -1,16 +1,19 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); const tracy = @import("tracy"); +const Plane = @import("renderer").Plane; +const key = @import("renderer").input.key; +const event_type = @import("renderer").input.event_type; +const utils = @import("renderer").input.utils; + const Widget = @import("../Widget.zig"); const command = @import("../command.zig"); const tui = @import("../tui.zig"); const EventHandler = @import("../EventHandler.zig"); -parent: nc.Plane, -plane: nc.Plane, +plane: Plane, ctrl: bool = false, shift: bool = false, alt: bool = false, @@ -20,18 +23,17 @@ const Self = @This(); pub const width = 5; -pub fn create(a: Allocator, parent: nc.Plane) !Widget { +pub fn create(a: Allocator, parent: Plane) !Widget { const self: *Self = try a.create(Self); self.* = try init(parent); try tui.current().input_listeners.add(EventHandler.bind(self, listen)); return self.widget(); } -fn init(parent: nc.Plane) !Self { - var n = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent); +fn init(parent: Plane) !Self { + var n = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent); errdefer n.deinit(); return .{ - .parent = parent, .plane = n, }; } @@ -53,7 +55,7 @@ pub fn layout(_: *Self) Widget.Layout { pub fn render(self: *Self, theme: *const Widget.Theme) bool { const frame = tracy.initZone(@src(), .{ .name = @typeName(@This()) ++ " render" }); defer frame.deinit(); - tui.set_base_style(&self.plane, " ", if (self.hover) theme.statusbar_hover else theme.statusbar); + self.plane.set_base_style(" ", if (self.hover) theme.statusbar_hover else theme.statusbar); self.plane.erase(); self.plane.home(); @@ -76,14 +78,14 @@ fn render_modifier(self: *Self, state: bool, off: [:0]const u8, on: [:0]const u8 pub fn listen(self: *Self, _: tp.pid_ref, m: tp.message) tp.result { var mod: u32 = 0; if (try m.match(.{ "I", tp.any, tp.any, tp.any, tp.any, tp.extract(&mod), tp.more })) { - self.ctrl = nc.isCtrl(mod); - self.shift = nc.isShift(mod); - self.alt = nc.isAlt(mod); + self.ctrl = utils.isCtrl(mod); + self.shift = utils.isShift(mod); + self.alt = utils.isAlt(mod); } } 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 })) { + if (try m.match(.{ "B", event_type.PRESS, key.BUTTON1, tp.any, tp.any, tp.any, tp.any, tp.any })) { command.executeName("toggle_inputview", .{}) catch {}; return true; } diff --git a/src/tui/status/selectionstate.zig b/src/tui/status/selectionstate.zig index 9750127..d98d81c 100644 --- a/src/tui/status/selectionstate.zig +++ b/src/tui/status/selectionstate.zig @@ -1,15 +1,15 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const nc = @import("notcurses"); const tp = @import("thespian"); const tracy = @import("tracy"); +const Plane = @import("renderer").Plane; + const Widget = @import("../Widget.zig"); const ed = @import("../editor.zig"); const tui = @import("../tui.zig"); -parent: nc.Plane, -plane: nc.Plane, +plane: Plane, matches: usize = 0, cursels: usize = 0, selection: ?ed.Selection = null, @@ -18,18 +18,17 @@ rendered: [:0]const u8 = "", const Self = @This(); -pub fn create(a: Allocator, parent: nc.Plane) !Widget { +pub fn create(a: Allocator, parent: Plane) !Widget { const self: *Self = try a.create(Self); self.* = try init(parent); return Widget.to(self); } -fn init(parent: nc.Plane) !Self { - var n = try nc.Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent); +fn init(parent: Plane) !Self { + var n = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent); errdefer n.deinit(); return .{ - .parent = parent, .plane = n, }; } @@ -46,7 +45,7 @@ pub fn layout(self: *Self) Widget.Layout { pub fn render(self: *Self, theme: *const Widget.Theme) bool { const frame = tracy.initZone(@src(), .{ .name = @typeName(@This()) ++ " render" }); defer frame.deinit(); - tui.set_base_style(&self.plane, " ", theme.statusbar); + self.plane.set_base_style(" ", theme.statusbar); self.plane.erase(); self.plane.home(); _ = self.plane.putstr(self.rendered) catch {}; diff --git a/src/tui/status/statusbar.zig b/src/tui/status/statusbar.zig index 3e3e372..d19ea1c 100644 --- a/src/tui/status/statusbar.zig +++ b/src/tui/status/statusbar.zig @@ -1,13 +1,9 @@ const std = @import("std"); -const nc = @import("notcurses"); const Widget = @import("../Widget.zig"); const WidgetList = @import("../WidgetList.zig"); const tui = @import("../tui.zig"); -parent: nc.Plane, -plane: nc.Plane, - const Self = @This(); pub fn create(a: std.mem.Allocator, parent: Widget) !Widget { diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 6fb085e..ac2175a 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -1,14 +1,14 @@ const std = @import("std"); -const nc = @import("notcurses"); const tp = @import("thespian"); const log = @import("log"); const config = @import("config"); const project_manager = @import("project_manager"); const build_options = @import("build_options"); const root = @import("root"); - const tracy = @import("tracy"); +pub const renderer = @import("renderer"); + const command = @import("command.zig"); const WidgetStack = @import("WidgetStack.zig"); const Widget = @import("Widget.zig"); @@ -25,7 +25,7 @@ const Mutex = std.Thread.Mutex; const maxInt = std.math.maxInt; a: Allocator, -nc: nc.Context, +rdr: renderer, config: config, frame_time: usize, // in microseconds frame_clock: tp.metronome, @@ -43,32 +43,17 @@ mini_mode: ?MiniModeState = null, hover_focus: ?*Widget = null, commands: Commands = undefined, logger: log.Logger, -drag: bool = false, -drag_event: nc.Input = nc.input(), drag_source: ?*Widget = null, theme: Widget.Theme, -escape_state: EscapeState = .none, -escape_initial: ?nc.Input = null, -escape_code: ArrayList(u8), -bracketed_paste: bool = false, -bracketed_paste_buffer: ArrayList(u8), idle_frame_count: usize = 0, unrendered_input_events_count: usize = 0, -unflushed_events_count: usize = 0, init_timer: ?tp.timeout, sigwinch_signal: ?tp.signal = null, no_sleep: bool = false, -mods: ModState = .{}, final_exit: []const u8 = "normal", const idle_frames = 1; -const ModState = struct { - ctrl: bool = false, - shift: bool = false, - alt: bool = false, -}; - const init_delay = 1; // ms const Self = @This(); @@ -91,20 +76,8 @@ fn start(args: StartArgs) tp.result { } fn init(a: Allocator) !*Self { - var opts = nc.Context.Options{ - .termtype = null, - .loglevel = @intFromEnum(nc.LogLevel.silent), - .margin_t = 0, - .margin_r = 0, - .margin_b = 0, - .margin_l = 0, - .flags = nc.Context.option.SUPPRESS_BANNERS | nc.Context.option.INHIBIT_SETLOCALE | nc.Context.option.NO_WINCH_SIGHANDLER, - }; - if (tp.env.get().is("no-alternate")) - opts.flags |= nc.Context.option.NO_ALTERNATE_SCREEN; - const nc_ = try nc.Context.core_init(&opts, null); - nc_.mice_enable(nc.mice.ALL_EVENTS) catch {}; - try nc_.linesigs_disable(); + var self = try a.create(Self); + const ctx = try renderer.init(a, self, tp.env.get().is("no-alternate")); var conf_buf: ?[]const u8 = null; var conf = root.read_config(a, &conf_buf); @@ -120,18 +93,17 @@ fn init(a: Allocator) !*Self { const frame_time = std.time.us_per_s / conf.frame_rate; const frame_clock = try tp.metronome.init(frame_time); - const fd_stdin = try tp.file_descriptor.init("stdin", nc_.inputready_fd()); + const fd_stdin = try tp.file_descriptor.init("stdin", ctx.input_fd()); // const fd_stdin = try tp.file_descriptor.init("stdin", std.os.STDIN_FILENO); - const n = nc_.stdplane(); + const n = ctx.stdplane(); try frame_clock.start(); try fd_stdin.wait_read(); - var self = try a.create(Self); self.* = .{ .a = a, .config = conf, - .nc = nc_, + .rdr = ctx, .frame_time = frame_time, .frame_clock = frame_clock, .frame_clock_running = true, @@ -142,23 +114,23 @@ fn init(a: Allocator) !*Self { .input_mode = null, .input_listeners = EventHandler.List.init(a), .logger = log.logger("tui"), - .escape_code = ArrayList(u8).init(a), - .bracketed_paste_buffer = ArrayList(u8).init(a), .init_timer = try tp.timeout.init_ms(init_delay, tp.message.fmt(.{"init"})), .theme = theme, .no_sleep = tp.env.get().is("no-sleep"), }; instance_ = self; defer instance_ = null; + self.rdr.handler_ctx = self; + self.rdr.dispatch_input = dispatch_input; + self.rdr.dispatch_mouse = dispatch_mouse; + self.rdr.dispatch_mouse_drag = dispatch_mouse_drag; + self.rdr.dispatch_event = dispatch_event; try self.commands.init(self); errdefer self.deinit(); try self.listen_sigwinch(); self.mainview = try mainview.create(a, n); - try self.initUI(); - try nc_.render(); + try ctx.render(); try self.save_config(); - // self.request_mouse_cursor_support_detect(); - self.bracketed_paste_enable(); if (tp.env.get().is("restore-session")) { command.executeName("restore_session", .{}) catch |e| self.logger.err("restore_session", e); self.logger.print("session restored", .{}); @@ -166,24 +138,11 @@ fn init(a: Allocator) !*Self { return self; } -pub fn initUI(self: *Self) !void { - const n = self.nc.stdplane(); - var channels: u64 = 0; - try nc.channels_set_fg_rgb(&channels, 0x88aa00); - try nc.channels_set_bg_rgb(&channels, 0x000088); - try nc.channels_set_bg_alpha(&channels, nc.ALPHA_TRANSPARENT); - _ = try n.set_base(" ", 0, channels); - try n.set_fg_rgb(0x40f040); - try n.set_fg_rgb(0x00dddd); -} - fn init_delayed(self: *Self) tp.result { if (self.input_mode) |_| {} else return cmds.enter_mode(self, command.Context.fmt(.{self.config.input_mode})); } fn deinit(self: *Self) void { - self.bracketed_paste_buffer.deinit(); - self.escape_code.deinit(); if (self.input_mode) |*m| m.deinit(); self.commands.deinit(); self.fd_stdin.deinit(); @@ -194,7 +153,7 @@ fn deinit(self: *Self) void { self.frame_clock.stop() catch {}; if (self.sigwinch_signal) |sig| sig.deinit(); self.frame_clock.deinit(); - self.nc.stop(); + self.rdr.stop(); self.logger.deinit(); self.a.destroy(self); } @@ -211,7 +170,7 @@ fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result { defer instance_ = null; errdefer self.deinit(); errdefer self.fd_stdin.cancel() catch {}; - errdefer self.nc.leave_alternate_screen(); + errdefer self.rdr.leave_alternate_screen(); self.receive_safe(from, m) catch |e| { if (std.mem.eql(u8, "normal", tp.error_text())) return e; @@ -253,8 +212,8 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) tp.result { if (try m.match(.{"sigwinch"})) { try self.listen_sigwinch(); - self.nc.refresh() catch |e| return self.logger.err("refresh", e); - self.mainview.resize(Widget.Box.from(self.nc.stdplane())); + self.rdr.refresh() catch |e| return self.logger.err("refresh", e); + self.mainview.resize(Widget.Box.from(self.rdr.stdplane())); need_render(); return; } @@ -265,7 +224,7 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) tp.result { return; } - if (self.dispatch_input(m) catch |e| b: { + if (self.dispatch_input_fd(m) catch |e| b: { self.logger.err("input dispatch", e); break :b true; }) @@ -341,14 +300,14 @@ fn render(self: *Self, current_time: i64) void { const more = ret: { const frame = tracy.initZone(@src(), .{ .name = "tui render" }); defer frame.deinit(); - self.nc.stdplane().erase(); + self.rdr.stdplane().erase(); break :ret self.mainview.render(&self.theme); }; { const frame = tracy.initZone(@src(), .{ .name = "notcurses render" }); defer frame.deinit(); - self.nc.render() catch |e| self.logger.err("render", e); + self.rdr.render() catch |e| self.logger.err("render", e); } self.idle_frame_count = if (self.unrendered_input_events_count > 0) @@ -370,14 +329,23 @@ fn render(self: *Self, current_time: i64) void { } } -fn dispatch_input(self: *Self, m: tp.message) error{Exit}!bool { +fn dispatch_flush_input_event(self: *Self) tp.result { + var buf: [32]u8 = undefined; + if (self.input_mode) |mode| + try mode.handler.send(tp.self_pid(), tp.message.fmtbuf(&buf, .{"F"}) catch |e| return tp.exit_error(e)); +} + +fn dispatch_input_fd(self: *Self, m: tp.message) error{Exit}!bool { const frame = tracy.initZone(@src(), .{ .name = "tui input" }); defer frame.deinit(); var err: i64 = 0; var err_msg: []u8 = ""; if (try m.match(.{ "fd", "stdin", "read_ready" })) { self.fd_stdin.wait_read() catch |e| return tp.exit_error(e); - try self.dispatch_notcurses(); + self.rdr.process_input() catch |e| return tp.exit_error(e); + try self.dispatch_flush_input_event(); + if (self.unrendered_input_events_count > 0 and !self.frame_clock_running) + need_render(); return true; // consume message } if (try m.match(.{ "fd", "stdin", "read_error", tp.extract(&err), tp.extract(&err_msg) })) { @@ -386,199 +354,50 @@ fn dispatch_input(self: *Self, m: tp.message) error{Exit}!bool { return false; } -fn dispatch_notcurses(self: *Self) tp.result { - var input_buffer: [256]nc.Input = undefined; - - while (true) { - const nivec = self.nc.getvec_nblock(&input_buffer) catch |e| return tp.exit_error(e); - if (nivec.len == 0) - break; - for (nivec) |*ni| { - if (ni.id == 27 or self.escape_state != .none) { - try self.handle_escape(ni); - continue; - } - self.dispatch_input_event(ni) catch |e| - self.logger.err("input dispatch", e); - } - } - if (self.escape_state == .init) - try self.handle_escape_short(); - if (self.unflushed_events_count > 0) - _ = try self.dispatch_flush_input_event(); - if (self.unrendered_input_events_count > 0 and !self.frame_clock_running) - need_render(); -} - -fn dispatch_input_event(self: *Self, ni: *nc.Input) tp.result { - const keypress: u32 = ni.id; - var buf: [256]u8 = undefined; +fn dispatch_input(ctx: *anyopaque, cbor_msg: []const u8) void { + const self: *Self = @ptrCast(@alignCast(ctx)); + const m: tp.message = .{ .buf = cbor_msg }; + const from = tp.self_pid(); self.unrendered_input_events_count += 1; - ni.modifiers &= nc.mod.CTRL | nc.mod.SHIFT | nc.mod.ALT | nc.mod.SUPER | nc.mod.META | nc.mod.HYPER; - if (keypress == nc.key.RESIZE) return; - try self.sync_mod_state(keypress, ni.modifiers); - if (keypress == nc.key.MOTION) { - if (ni.y == 0 and ni.x == 0 and ni.ypx == -1 and ni.xpx == -1) return; - self.dispatch_mouse(ni.y, ni.x, tp.self_pid(), tp.message.fmtbuf(&buf, .{ - "M", - ni.x, - ni.y, - ni.xpx, - ni.ypx, - }) catch |e| return tp.exit_error(e)); - } else if (keypress > nc.key.MOTION and keypress <= nc.key.BUTTON11) { - if (ni.y == 0 and ni.x == 0 and ni.ypx == -1 and ni.xpx == -1) return; - if (try self.detect_drag(ni)) return; - self.dispatch_mouse(ni.y, ni.x, tp.self_pid(), tp.message.fmtbuf(&buf, .{ - "B", - ni.evtype, - keypress, - nc.key_string(ni), - ni.x, - ni.y, - ni.xpx, - ni.ypx, - }) catch |e| return tp.exit_error(e)); - } else { - self.unflushed_events_count += 1; - self.send_input(tp.self_pid(), tp.message.fmtbuf(&buf, .{ - "I", - normalized_evtype(ni.evtype), - keypress, - if (@hasField(nc.Input, "eff_text")) ni.eff_text[0] else keypress, - nc.key_string(ni), - ni.modifiers, - }) catch |e| return tp.exit_error(e)); - } -} - -fn dispatch_flush_input_event(self: *Self) error{Exit}!bool { - var buf: [32]u8 = undefined; - self.unflushed_events_count = 0; + tp.trace(tp.channel.input, m); + self.input_listeners.send(from, m) catch {}; + if (self.keyboard_focus) |w| + if (w.send(from, m) catch |e| ret: { + self.logger.err("focus", e); + break :ret false; + }) + return; if (self.input_mode) |mode| - try mode.handler.send(tp.self_pid(), tp.message.fmtbuf(&buf, .{"F"}) catch |e| return tp.exit_error(e)); - return false; + mode.handler.send(from, m) catch |e| self.logger.err("input handler", e); } -fn detect_drag(self: *Self, ni: *nc.Input) error{Exit}!bool { - return switch (ni.id) { - nc.key.BUTTON1...nc.key.BUTTON3, nc.key.BUTTON6...nc.key.BUTTON9 => if (self.drag) self.detect_drag_end(ni) else self.detect_drag_begin(ni), - else => false, - }; +fn dispatch_mouse(ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void { + const self: *Self = @ptrCast(@alignCast(ctx)); + const m: tp.message = .{ .buf = cbor_msg }; + const from = tp.self_pid(); + self.unrendered_input_events_count += 1; + self.send_mouse(y, x, from, m) catch |e| self.logger.err("dispatch mouse", e); } -fn detect_drag_begin(self: *Self, ni: *nc.Input) error{Exit}!bool { - if (ni.evtype == nc.event_type.PRESS and self.drag_event.id == ni.id) { - self.drag_source = self.find_coord_widget(@intCast(self.drag_event.y), @intCast(self.drag_event.x)); - self.drag_event = ni.*; - self.drag = true; - var buf: [256]u8 = undefined; - _ = try self.send_mouse_drag(ni.y, ni.x, tp.self_pid(), tp.message.fmtbuf(&buf, .{ - "D", - nc.event_type.PRESS, - ni.id, - nc.key_string(ni), - ni.x, - ni.y, - ni.xpx, - ni.ypx, - }) catch |e| return tp.exit_error(e)); - return true; - } - if (ni.evtype == nc.event_type.PRESS) - self.drag_event = ni.* - else - self.drag_event = nc.input(); - return false; -} - -fn detect_drag_end(self: *Self, ni: *nc.Input) error{Exit}!bool { - var buf: [256]u8 = undefined; - if (ni.id == self.drag_event.id and ni.evtype != nc.event_type.PRESS) { - _ = try self.send_mouse_drag(ni.y, ni.x, tp.self_pid(), tp.message.fmtbuf(&buf, .{ - "D", - nc.event_type.RELEASE, - ni.id, - nc.key_string(ni), - ni.x, - ni.y, - ni.xpx, - ni.ypx, - }) catch |e| return tp.exit_error(e)); - self.drag = false; - self.drag_event = nc.input(); +fn dispatch_mouse_drag(ctx: *anyopaque, y: c_int, x: c_int, dragging: bool, cbor_msg: []const u8) void { + const self: *Self = @ptrCast(@alignCast(ctx)); + const m: tp.message = .{ .buf = cbor_msg }; + const from = tp.self_pid(); + self.unrendered_input_events_count += 1; + if (dragging) { + if (self.drag_source == null) self.drag_source = self.find_coord_widget(@intCast(y), @intCast(x)); + } else { self.drag_source = null; - return true; } - _ = try self.send_mouse_drag(ni.y, ni.x, tp.self_pid(), tp.message.fmtbuf(&buf, .{ - "D", - ni.evtype, - ni.id, - nc.key_string(ni), - ni.x, - ni.y, - ni.xpx, - ni.ypx, - }) catch |e| return tp.exit_error(e)); - return true; + self.send_mouse_drag(y, x, from, m) catch |e| self.logger.err("dispatch mouse", e); } -fn normalized_evtype(evtype: c_uint) c_uint { - return if (evtype == nc.event_type.UNKNOWN) @as(c_uint, @intCast(nc.event_type.PRESS)) else evtype; -} - -const EscapeState = enum { none, init, OSC, st, CSI }; - -fn handle_escape(self: *Self, ni: *nc.Input) tp.result { - switch (self.escape_state) { - .none => switch (ni.id) { - '\x1B' => { - self.escape_state = .init; - self.escape_initial = ni.*; - }, - else => unreachable, - }, - .init => switch (ni.id) { - ']' => self.escape_state = .OSC, - '[' => self.escape_state = .CSI, - else => { - try self.handle_escape_short(); - _ = try self.dispatch_input_event(ni); - }, - }, - .OSC => switch (ni.id) { - '\x1B' => self.escape_state = .st, - '\\' => try self.handle_OSC_escape_code(), - ' '...'\\' - 1, '\\' + 1...127 => { - const p = self.escape_code.addOne() catch |e| return tp.exit_error(e); - p.* = @intCast(ni.id); - }, - else => try self.handle_OSC_escape_code(), - }, - .st => switch (ni.id) { - '\\' => try self.handle_OSC_escape_code(), - else => try self.handle_OSC_escape_code(), - }, - .CSI => switch (ni.id) { - '0'...'9', ';', ' ', '-', '?' => { - const p = self.escape_code.addOne() catch |e| return tp.exit_error(e); - p.* = @intCast(ni.id); - }, - else => { - const p = self.escape_code.addOne() catch |e| return tp.exit_error(e); - p.* = @intCast(ni.id); - try self.handle_CSI_escape_code(); - }, - }, - } -} - -fn handle_escape_short(self: *Self) tp.result { - self.escape_code.clearAndFree(); - self.escape_state = .none; - defer self.escape_initial = null; - if (self.escape_initial) |*ni| - _ = try self.dispatch_input_event(ni); +fn dispatch_event(ctx: *anyopaque, cbor_msg: []const u8) void { + const self: *Self = @ptrCast(@alignCast(ctx)); + const m: tp.message = .{ .buf = cbor_msg }; + self.unrendered_input_events_count += 1; + self.dispatch_flush_input_event() catch |e| self.logger.err("dispatch event flush", e); + tp.self_pid().send_raw(m) catch |e| self.logger.err("dispatch event", e); } fn find_coord_widget(self: *Self, y: usize, x: usize) ?*Widget { @@ -626,11 +445,6 @@ fn send_widgets(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool { self.mainview.send(from, m); } -fn dispatch_mouse(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.message) void { - self.send_mouse(y, x, from, m) catch |e| - self.logger.err("dispatch mouse", e); -} - fn send_mouse(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.message) tp.result { tp.trace(tp.channel.input, m); _ = self.input_listeners.send(from, m) catch {}; @@ -656,12 +470,12 @@ fn send_mouse(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.message) } } -fn send_mouse_drag(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.message) error{Exit}!bool { +fn send_mouse_drag(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.message) tp.result { tp.trace(tp.channel.input, m); _ = self.input_listeners.send(from, m) catch {}; if (self.keyboard_focus) |w| { _ = try w.send(from, m); - return false; + return; } else if (self.find_coord_widget(@intCast(y), @intCast(x))) |w| { if (if (self.hover_focus) |h| h != w else true) { var buf: [256]u8 = undefined; @@ -679,70 +493,13 @@ fn send_mouse_drag(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.mess } self.hover_focus = null; } - return if (self.drag_source) |w| - w.send(from, m) - else - false; -} - -fn send_input(self: *Self, from: tp.pid_ref, m: tp.message) void { - tp.trace(tp.channel.input, m); - if (self.bracketed_paste and self.handle_bracketed_paste_input(m) catch |e| { - self.bracketed_paste_buffer.clearAndFree(); - self.bracketed_paste = false; - return self.logger.err("bracketed paste input handler", e); - }) { - return; - } - self.input_listeners.send(from, m) catch {}; - if (self.keyboard_focus) |w| - if (w.send(from, m) catch |e| ret: { - self.logger.err("focus", e); - break :ret false; - }) - return; - if (self.input_mode) |mode| - mode.handler.send(from, m) catch |e| self.logger.err("input handler", e); + if (self.drag_source) |w| _ = try w.send(from, m); } pub fn save_config(self: *const Self) !void { try root.write_config(self.config, self.a); } -fn sync_mod_state(self: *Self, keypress: u32, modifiers: u32) tp.result { - if (keypress == nc.key.LCTRL or keypress == nc.key.RCTRL or keypress == nc.key.LALT or keypress == nc.key.RALT or - keypress == nc.key.LSHIFT or keypress == nc.key.RSHIFT or keypress == nc.key.LSUPER or keypress == nc.key.RSUPER) return; - if (nc.isCtrl(modifiers) and !self.mods.ctrl) - try self.send_key(nc.event_type.PRESS, nc.key.LCTRL, "lctrl", modifiers); - if (!nc.isCtrl(modifiers) and self.mods.ctrl) - try self.send_key(nc.event_type.RELEASE, nc.key.LCTRL, "lctrl", modifiers); - if (nc.isAlt(modifiers) and !self.mods.alt) - try self.send_key(nc.event_type.PRESS, nc.key.LALT, "lalt", modifiers); - if (!nc.isAlt(modifiers) and self.mods.alt) - try self.send_key(nc.event_type.RELEASE, nc.key.LALT, "lalt", modifiers); - if (nc.isShift(modifiers) and !self.mods.shift) - try self.send_key(nc.event_type.PRESS, nc.key.LSHIFT, "lshift", modifiers); - if (!nc.isShift(modifiers) and self.mods.shift) - try self.send_key(nc.event_type.RELEASE, nc.key.LSHIFT, "lshift", modifiers); - self.mods = .{ - .ctrl = nc.isCtrl(modifiers), - .alt = nc.isAlt(modifiers), - .shift = nc.isShift(modifiers), - }; -} - -fn send_key(self: *Self, event_type: c_int, keypress: u32, key_string: []const u8, modifiers: u32) tp.result { - var buf: [256]u8 = undefined; - self.send_input(tp.self_pid(), tp.message.fmtbuf(&buf, .{ - "I", - event_type, - keypress, - keypress, - key_string, - modifiers, - }) catch |e| return tp.exit_error(e)); -} - const cmds = struct { pub const Target = Self; const Ctx = command.Context; @@ -757,13 +514,13 @@ const cmds = struct { var buf: [256]u8 = undefined; var buf_parent: [256]u8 = undefined; var z: i32 = 0; - var n = self.nc.stdplane(); + var n = self.rdr.stdplane(); while (n.below()) |n_| : (n = n_) { z -= 1; l.print("{d} {s} {s}", .{ z, n_.name(&buf), n_.parent().name(&buf_parent) }); } z = 0; - n = self.nc.stdplane(); + n = self.rdr.stdplane(); while (n.above()) |n_| : (n = n_) { z += 1; l.print("{d} {s} {s}", .{ z, n_.name(&buf), n_.parent().name(&buf_parent) }); @@ -925,238 +682,6 @@ pub fn current() *Self { return if (instance_) |p| p else @panic("tui call out of context"); } -const OSC = "\x1B]"; // Operating System Command -const ST = "\x1B\\"; // String Terminator -const BEL = "\x07"; -const OSC0_title = OSC ++ "0;"; -const OSC52_clipboard = OSC ++ "52;c;"; -const OSC52_clipboard_paste = OSC ++ "52;p;"; -const OSC22_cursor = OSC ++ "22;"; -const OSC22_cursor_reply = OSC ++ "22:"; - -pub fn set_terminal_title(text: []const u8) void { - var writer = std.io.getStdOut().writer(); - var buf: [std.posix.PATH_MAX]u8 = undefined; - const term_cmd = std.fmt.bufPrint(&buf, OSC0_title ++ "{s}" ++ BEL, .{text}) catch return; - _ = writer.write(term_cmd) catch return; -} - -pub fn copy_to_system_clipboard(self: *const Self, text: []const u8) void { - self.copy_to_system_clipboard_with_errors(text) catch |e| self.logger.err("copy_to_system_clipboard", e); -} - -pub fn copy_to_system_clipboard_with_errors(self: *const Self, text: []const u8) !void { - var writer = std.io.getStdOut().writer(); - const encoder = std.base64.standard.Encoder; - const size = OSC52_clipboard.len + encoder.calcSize(text.len) + ST.len; - const buf = try self.a.alloc(u8, size); - defer self.a.free(buf); - @memcpy(buf[0..OSC52_clipboard.len], OSC52_clipboard); - const b64 = encoder.encode(buf[OSC52_clipboard.len..], text); - @memcpy(buf[OSC52_clipboard.len + b64.len ..], ST); - _ = try writer.write(buf); -} - -pub fn write_stdout(self: *const Self, bytes: []const u8) void { - _ = std.io.getStdOut().writer().write(bytes) catch |e| self.logger.err("stdout", e); -} - -pub fn request_system_clipboard(self: *const Self) void { - self.write_stdout(OSC52_clipboard ++ "?" ++ ST); -} - -pub fn request_mouse_cursor_text(self: *const Self, push_or_pop: bool) void { - if (push_or_pop) self.mouse_cursor_push("text") else self.mouse_cursor_pop(); -} - -pub fn request_mouse_cursor_pointer(self: *const Self, push_or_pop: bool) void { - if (push_or_pop) self.mouse_cursor_push("pointer") else self.mouse_cursor_pop(); -} - -pub fn request_mouse_cursor_default(self: *const Self, push_or_pop: bool) void { - if (push_or_pop) self.mouse_cursor_push("default") else self.mouse_cursor_pop(); -} - -fn mouse_cursor_push(self: *const Self, comptime name: []const u8) void { - self.write_stdout(OSC22_cursor ++ name ++ ST); -} - -fn mouse_cursor_pop(self: *const Self) void { - self.write_stdout(OSC22_cursor ++ "default" ++ ST); -} - -fn match_code(self: *const Self, match: []const u8, skip: usize) bool { - const code = self.escape_code.items; - if (!(code.len >= match.len - skip)) return false; - const code_prefix = code[0 .. match.len - skip]; - return std.mem.eql(u8, match[skip..], code_prefix); -} - -fn handle_OSC_escape_code(self: *Self) tp.result { - self.escape_state = .none; - self.escape_initial = null; - defer self.escape_code.clearAndFree(); - const code = self.escape_code.items; - if (self.match_code(OSC52_clipboard, OSC.len)) - return self.handle_system_clipboard(code[OSC52_clipboard.len - OSC.len ..]); - if (self.match_code(OSC52_clipboard_paste, OSC.len)) - return self.handle_system_clipboard(code[OSC52_clipboard_paste.len - OSC.len ..]); - if (self.match_code(OSC22_cursor_reply, OSC.len)) - return self.handle_mouse_cursor(code[OSC22_cursor_reply.len - OSC.len ..]); - self.logger.print("ignored escape code: OSC {s}", .{std.fmt.fmtSliceEscapeLower(code)}); -} - -fn handle_system_clipboard(self: *Self, base64: []const u8) tp.result { - const decoder = std.base64.standard.Decoder; - // try self.logger.print("clipboard: b64 {s}", .{base64}); - const text = self.a.alloc( - u8, - decoder.calcSizeForSlice(base64) catch |e| return tp.exit_error(e), - ) catch |e| return tp.exit_error(e); - decoder.decode(text, base64) catch |e| return tp.exit_error(e); - // try self.logger.print("clipboard: txt {s}", .{std.fmt.fmtSliceEscapeLower(text)}); - return tp.self_pid().send(.{ "system_clipboard", text }); -} - -fn handle_mouse_cursor(self: *Self, text: []const u8) tp.result { - self.logger.print("mouse cursor report: {s}", .{text}); -} - -const CSI = "\x1B["; // Control Sequence Introducer -const CSI_bracketed_paste_enable = CSI ++ "?2004h"; -const CSI_bracketed_paste_disable = CSI ++ "?2004h"; -const CIS_bracketed_paste_begin = CSI ++ "200~"; -const CIS_bracketed_paste_end = CSI ++ "201~"; - -fn handle_CSI_escape_code(self: *Self) tp.result { - self.escape_state = .none; - self.escape_initial = null; - defer self.escape_code.clearAndFree(); - const code = self.escape_code.items; - if (self.match_code(CIS_bracketed_paste_begin, CSI.len)) - return self.handle_bracketed_paste_begin(); - if (self.match_code(CIS_bracketed_paste_end, CSI.len)) - return self.handle_bracketed_paste_end(); - self.logger.print("ignored escape code: CSI {s}", .{std.fmt.fmtSliceEscapeLower(code)}); -} - -fn handle_bracketed_paste_begin(self: *Self) tp.result { - _ = try self.dispatch_flush_input_event(); - self.bracketed_paste_buffer.clearAndFree(); - self.bracketed_paste = true; -} - -fn handle_bracketed_paste_input(self: *Self, m: tp.message) !bool { - var keypress: u32 = undefined; - var egc: u32 = undefined; - if (try m.match(.{ "I", tp.number, tp.extract(&keypress), tp.extract(&egc), tp.string, 0 })) { - switch (keypress) { - nc.key.ENTER => try self.bracketed_paste_buffer.appendSlice("\n"), - else => if (!nc.key.synthesized_p(keypress)) { - var buf: [6]u8 = undefined; - const bytes = try nc.ucs32_to_utf8(&[_]u32{egc}, &buf); - try self.bracketed_paste_buffer.appendSlice(buf[0..bytes]); - } else { - try self.handle_bracketed_paste_end(); - return false; - }, - } - return true; - } - return false; -} - -fn handle_bracketed_paste_end(self: *Self) tp.result { - defer self.bracketed_paste_buffer.clearAndFree(); - if (!self.bracketed_paste) return; - self.bracketed_paste = false; - return tp.self_pid().send(.{ "system_clipboard", self.bracketed_paste_buffer.items }); -} - -fn bracketed_paste_enable(self: *const Self) void { - self.write_stdout(CSI_bracketed_paste_enable); -} - -fn bracketed_paste_disable(self: *const Self) void { - self.write_stdout(CSI_bracketed_paste_disable); -} - -pub inline fn fg_channels_from_style(channels: *u64, style: Widget.Theme.Style) void { - if (style.fg) |fg| { - nc.channels_set_fg_rgb(channels, fg) catch {}; - nc.channels_set_fg_alpha(channels, nc.ALPHA_OPAQUE) catch {}; - } -} - -pub inline fn bg_channels_from_style(channels: *u64, style: Widget.Theme.Style) void { - if (style.bg) |bg| { - nc.channels_set_bg_rgb(channels, bg) catch {}; - nc.channels_set_bg_alpha(channels, nc.ALPHA_OPAQUE) catch {}; - } -} - -pub inline fn channels_from_style(channels: *u64, style: Widget.Theme.Style) void { - fg_channels_from_style(channels, style); - bg_channels_from_style(channels, style); -} - -pub inline fn set_cell_style(cell: *nc.Cell, style: Widget.Theme.Style) void { - channels_from_style(&cell.channels, style); - if (style.fs) |fs| switch (fs) { - .normal => nc.cell_set_styles(cell, nc.style.none), - .bold => nc.cell_set_styles(cell, nc.style.bold), - .italic => nc.cell_set_styles(cell, nc.style.italic), - .underline => nc.cell_set_styles(cell, nc.style.underline), - .undercurl => nc.cell_set_styles(cell, nc.style.undercurl), - .strikethrough => nc.cell_set_styles(cell, nc.style.struck), - }; -} - -pub inline fn set_cell_style_fg(cell: *nc.Cell, style: Widget.Theme.Style) void { - fg_channels_from_style(&cell.channels, style); -} - -pub inline fn set_cell_style_bg(cell: *nc.Cell, style: Widget.Theme.Style) void { - bg_channels_from_style(&cell.channels, style); -} - -pub inline fn set_base_style(plane: *const nc.Plane, egc: [*c]const u8, style: Widget.Theme.Style) void { - var channels: u64 = 0; - channels_from_style(&channels, style); - 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 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); - 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), - .undercurl => plane.set_styles(nc.style.undercurl), - .strikethrough => plane.set_styles(nc.style.struck), - }; -} - pub fn get_mode() []const u8 { return if (current().input_mode) |m| m.name else "INI"; }