feat: add libvaxis renderer

This commit is contained in:
CJ van den Berg 2024-05-01 22:03:33 +02:00
parent b15fa47f30
commit 1cd3cb17ce
32 changed files with 1559 additions and 516 deletions

View file

@ -77,15 +77,19 @@ pub const Plane = struct {
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 abs_yx_to_rel(self: Plane, y: c_int, x: c_int) struct { c_int, c_int } {
var y_, var x_ = .{ y, x };
self.plane.abs_yx_to_rel(&y_, &x_);
return .{ 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 rel_yx_to_abs(self: Plane, y: c_int, x: c_int) struct { c_int, c_int } {
var y_, var x_ = .{ y, x };
self.plane.rel_yx_to_abs(&y_, &x_);
return .{ y_, x_ };
}
pub fn move_bottom(self: Plane) void {
pub fn hide(self: Plane) void {
self.plane.move_bottom();
}
@ -244,4 +248,30 @@ pub const Plane = struct {
.strikethrough => plane.plane.set_styles(nc.style.struck),
};
}
pub fn egc_length(_: Plane, 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(plane: Plane, 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 = plane.egc_length(chunk, &cols, abs_col);
colcount += @intCast(cols);
abs_col += @intCast(cols);
if (chunk.len < bytes) break;
chunk = chunk[bytes..];
}
return colcount;
}
};

View file

@ -1,29 +0,0 @@
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;

View file

@ -1,6 +1,7 @@
const nc = @import("notcurses");
pub const key = nc.key;
pub const key_type = u32;
pub const modifier = nc.mod;
pub const event_type = nc.event_type;

View file

@ -4,7 +4,6 @@ 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;
@ -18,7 +17,7 @@ const key = input.key;
const event_type = input.event_type;
const Self = @This();
const log_name = "ncrender";
pub const log_name = "notcurses";
a: std.mem.Allocator,
ctx: nc.Context,
@ -79,6 +78,8 @@ pub fn deinit(self: *Self) void {
self.bracketed_paste_buffer.deinit();
}
pub fn run(_: *Self) !void {}
pub fn render(self: Self) !void {
return self.ctx.render();
}
@ -103,11 +104,25 @@ pub fn leave_alternate_screen(self: Self) void {
return self.ctx.leave_alternate_screen();
}
pub fn process_input(self: *Self) !void {
const InputError = error{
OutOfMemory,
InvalidCharacter,
NoSpaceLeft,
CborIntegerTooLarge,
CborIntegerTooSmall,
CborInvalidType,
CborTooShort,
Ucs32toUtf8Error,
InvalidPadding,
ReadInputError,
WouldBlock,
};
pub fn process_input(self: *Self) InputError!void {
var input_buffer: [256]nc.Input = undefined;
while (true) {
const nivec = try self.ctx.getvec_nblock(&input_buffer);
const nivec = self.ctx.getvec_nblock(&input_buffer) catch return error.ReadInputError;
if (nivec.len == 0)
break;
for (nivec) |*ni| {
@ -191,7 +206,7 @@ fn handle_bracketed_paste_input(self: *Self, cbor_msg: []const u8) !bool {
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);
const bytes = try ucs32_to_utf8(&[_]u32{egc_}, &buf);
try self.bracketed_paste_buffer.appendSlice(buf[0..bytes]);
} else {
try self.handle_bracketed_paste_end();
@ -506,3 +521,5 @@ pub fn cursor_enable(self: Self, y: c_int, x: c_int) !void {
pub fn cursor_disable(self: Self) void {
self.ctx.cursor_disable() catch {};
}
pub const ucs32_to_utf8 = nc.ucs32_to_utf8;

View file

@ -0,0 +1,51 @@
const vaxis = @import("vaxis");
const Style = @import("theme").Style;
const Cell = @This();
cell: vaxis.Cell = .{},
pub inline fn set_style(self: *Cell, style_: Style) void {
if (style_.fg) |fg| self.cell.style.fg = vaxis.Cell.Color.rgbFromUint(fg);
if (style_.bg) |bg| self.cell.style.bg = vaxis.Cell.Color.rgbFromUint(bg);
if (style_.fs) |fs| {
self.cell.style.ul = .default;
self.cell.style.ul_style = .off;
self.cell.style.bold = false;
self.cell.style.dim = false;
self.cell.style.italic = false;
self.cell.style.blink = false;
self.cell.style.reverse = false;
self.cell.style.invisible = false;
self.cell.style.strikethrough = false;
switch (fs) {
.normal => {},
.bold => self.cell.style.bold = true,
.italic => self.cell.style.italic = false,
.underline => self.cell.style.ul_style = .single,
.undercurl => self.cell.style.ul_style = .curly,
.strikethrough => self.cell.style.strikethrough = true,
}
}
}
pub inline fn set_style_fg(self: *Cell, style_: Style) void {
if (style_.fg) |fg| self.cell.style.fg = vaxis.Cell.Color.rgbFromUint(fg);
}
pub inline fn set_style_bg(self: *Cell, style_: Style) void {
if (style_.bg) |bg| self.cell.style.bg = vaxis.Cell.Color.rgbFromUint(bg);
}
pub inline fn set_fg_rgb(self: *Cell, arg_rgb: c_uint) !void {
self.cell.style.fg = vaxis.Cell.Color.rgbFromUint(@intCast(arg_rgb));
}
pub inline fn set_bg_rgb(self: *Cell, arg_rgb: c_uint) !void {
self.cell.style.bg = vaxis.Cell.Color.rgbFromUint(@intCast(arg_rgb));
}
pub fn columns(self: *const Cell) usize {
// return if (self.cell.char.width == 0) self.window.gwidth(self.cell.char.grapheme) else self.cell.char.width; // FIXME?
return self.cell.char.width;
}

View file

@ -0,0 +1,346 @@
const std = @import("std");
const Style = @import("theme").Style;
const StyleBits = @import("style.zig").StyleBits;
const Cell = @import("Cell.zig");
const vaxis = @import("vaxis");
const Plane = @This();
window: vaxis.Window,
row: i32 = 0,
col: i32 = 0,
name_buf: [128]u8,
name_len: usize,
cache: GraphemeCache = .{},
style: vaxis.Cell.Style = .{},
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 {
const opts = .{
.x_off = nopts.x,
.y_off = nopts.y,
.width = .{ .limit = nopts.cols },
.height = .{ .limit = nopts.rows },
.border = .{},
};
var plane: Plane = .{
.window = parent_.window.child(opts),
.name_buf = undefined,
.name_len = std.mem.span(nopts.name).len,
};
@memcpy(plane.name_buf[0..plane.name_len], nopts.name);
return plane;
}
pub fn deinit(_: *Plane) void {}
pub fn name(self: Plane, buf: []u8) []const u8 {
@memcpy(buf[0..self.name_len], self.name_buf[0..self.name_len]);
return buf[0..self.name_len];
}
pub fn above(_: Plane) ?Plane {
return null;
}
pub fn below(_: Plane) ?Plane {
return null;
}
pub fn erase(self: Plane) void {
self.window.clear();
}
pub inline fn abs_y(self: Plane) c_int {
return @intCast(self.window.y_off);
}
pub inline fn abs_x(self: Plane) c_int {
return @intCast(self.window.x_off);
}
pub inline fn dim_y(self: Plane) c_uint {
return @intCast(self.window.height);
}
pub inline fn dim_x(self: Plane) c_uint {
return @intCast(self.window.width);
}
pub fn abs_yx_to_rel(self: Plane, y: c_int, x: c_int) struct { c_int, c_int } {
return .{ y - self.abs_y(), x - self.abs_x() };
}
pub fn rel_yx_to_abs(self: Plane, y: c_int, x: c_int) struct { c_int, c_int } {
return .{ self.abs_y() + y, self.abs_x() + x };
}
pub fn hide(_: Plane) void {}
pub fn move_yx(self: *Plane, y: c_int, x: c_int) !void {
self.window.y_off = @intCast(y);
self.window.x_off = @intCast(x);
}
pub fn resize_simple(self: *Plane, ylen: c_uint, xlen: c_uint) !void {
self.window.height = @intCast(ylen);
self.window.width = @intCast(xlen);
}
pub fn home(self: *Plane) void {
self.row = 0;
self.col = 0;
}
pub fn print(self: *Plane, comptime fmt: anytype, args: anytype) !usize {
var buf: [fmt.len + 4096]u8 = undefined;
const text = try std.fmt.bufPrint(&buf, fmt, args);
return self.putstr(text);
}
pub fn print_aligned_right(self: *Plane, y: c_int, comptime fmt: anytype, args: anytype) !usize {
var buf: [fmt.len + 4096]u8 = undefined;
const width = self.window.width;
const text = try std.fmt.bufPrint(&buf, fmt, args);
const text_width = self.egc_chunk_width(text, 0);
self.row = @intCast(y);
self.col = @intCast(if (text_width >= width) 0 else width - text_width);
return self.putstr(text);
}
pub fn print_aligned_center(self: *Plane, y: c_int, comptime fmt: anytype, args: anytype) !usize {
var buf: [fmt.len + 4096]u8 = undefined;
const width = self.window.width;
const text = try std.fmt.bufPrint(&buf, fmt, args);
const text_width = self.egc_chunk_width(text, 0);
self.row = @intCast(y);
self.col = @intCast(if (text_width >= width) 0 else (width - text_width) / 2);
return self.putstr(text);
}
pub fn putstr(self: *Plane, text: []const u8) !usize {
var result: usize = 0;
const width = self.window.width;
var iter = self.window.screen.unicode.graphemeIterator(text);
while (iter.next()) |grapheme| {
if (self.col >= width) {
self.row += 1;
self.col = 0;
}
const s = grapheme.bytes(text);
if (std.mem.eql(u8, s, "\n")) {
self.row += 1;
self.col = 0;
result += 1;
continue;
}
const w = self.window.gwidth(s);
if (w == 0) continue;
self.window.writeCell(@intCast(self.col), @intCast(self.row), .{
.char = .{
.grapheme = self.cache.put(s),
.width = w,
},
.style = self.style,
});
self.col += @intCast(w);
result += 1;
}
return result;
}
pub fn putc(self: *Plane, cell: *const Cell) !usize {
return self.putc_yx(@intCast(self.row), @intCast(self.col), cell);
}
pub fn putc_yx(self: *Plane, y: c_int, x: c_int, cell: *const Cell) !usize {
try self.cursor_move_yx(y, x);
const w = if (cell.cell.char.width == 0) self.window.gwidth(cell.cell.char.grapheme) else cell.cell.char.width;
if (w == 0) return w;
self.window.writeCell(@intCast(self.col), @intCast(self.row), cell.cell);
self.col += @intCast(w);
if (self.col >= self.window.width) {
self.row += 1;
self.col = 0;
}
return w;
}
pub fn cursor_yx(self: Plane, y: *c_uint, x: *c_uint) void {
y.* = @intCast(self.row);
x.* = @intCast(self.col);
}
pub fn cursor_y(self: Plane) c_uint {
return @intCast(self.row);
}
pub fn cursor_x(self: Plane) c_uint {
return @intCast(self.col);
}
pub fn cursor_move_yx(self: *Plane, y: c_int, x: c_int) !void {
if (self.window.height == 0 or self.window.width == 0) return;
if (self.window.height <= y or self.window.width <= x) return;
if (y >= 0)
self.row = @intCast(y);
if (x >= 0)
self.col = @intCast(x);
}
pub fn cursor_move_rel(self: *Plane, y: c_int, x: c_int) !void {
if (self.window.height == 0 or self.window.width == 0) return error.OutOfBounds;
const new_y: isize = @as(c_int, @intCast(self.row)) + y;
const new_x: isize = @as(c_int, @intCast(self.col)) + x;
if (new_y < 0 or new_x < 0) return error.OutOfBounds;
if (self.window.height <= new_y or self.window.width <= new_x) return error.OutOfBounds;
self.row = @intCast(new_y);
self.col = @intCast(new_x);
}
pub fn cell_init(self: Plane) Cell {
return .{ .cell = .{ .style = self.style } };
}
pub fn cell_load(self: *Plane, cell: *Cell, gcluster: [:0]const u8) !usize {
cell.* = .{ .cell = .{ .style = self.style } };
var cols: c_int = 0;
const bytes = self.egc_length(gcluster, &cols, 0);
cell.cell.char.grapheme = self.cache.put(gcluster[0..bytes]);
cell.cell.char.width = @intCast(cols);
return bytes;
}
pub fn at_cursor_cell(self: Plane, cell: *Cell) !usize {
cell.* = .{};
if (self.window.readCell(@intCast(self.col), @intCast(self.row))) |cell_| cell.cell = cell_;
return cell.cell.char.grapheme.len;
}
pub fn set_styles(self: Plane, stylebits: StyleBits) void {
_ = self;
_ = stylebits;
// FIXME
}
pub fn on_styles(self: Plane, stylebits: StyleBits) void {
_ = self;
_ = stylebits;
// FIXME
}
pub fn off_styles(self: Plane, stylebits: StyleBits) void {
_ = self;
_ = stylebits;
// FIXME
}
pub fn set_fg_rgb(self: *Plane, channel: u32) !void {
self.style.fg = vaxis.Cell.Color.rgbFromUint(@intCast(channel));
}
pub fn set_bg_rgb(self: *Plane, channel: u32) !void {
self.style.bg = vaxis.Cell.Color.rgbFromUint(@intCast(channel));
}
pub fn set_fg_palindex(self: *Plane, idx: c_uint) !void {
self.style.fg = .{ .index = @intCast(idx) };
}
pub fn set_bg_palindex(self: *Plane, idx: c_uint) !void {
self.style.bg = .{ .index = @intCast(idx) };
}
pub fn set_channels(self: Plane, channels_: u64) void {
_ = self;
_ = channels_;
// FIXME
}
pub inline fn set_base_style(self: *const Plane, egc_: [*c]const u8, style_: Style) void {
_ = self;
_ = egc_;
_ = style_;
// FIXME
}
pub fn set_base_style_transparent(self: Plane, egc_: [*:0]const u8, style_: Style) void {
_ = self;
_ = egc_;
_ = style_;
// FIXME
}
pub fn set_base_style_bg_transparent(self: Plane, egc_: [*:0]const u8, style_: Style) void {
_ = self;
_ = egc_;
_ = style_;
// FIXME
}
pub inline fn set_style(self: *Plane, style_: Style) void {
if (style_.fg) |color| self.style.fg = vaxis.Cell.Color.rgbFromUint(@intCast(color));
if (style_.bg) |color| self.style.bg = vaxis.Cell.Color.rgbFromUint(@intCast(color));
// if (style_.fs) |fontstyle| ... FIXME
}
pub inline fn set_style_bg_transparent(self: *Plane, style_: Style) void {
if (style_.fg) |color| self.style.fg = vaxis.Cell.Color.rgbFromUint(@intCast(color));
self.style.bg = .default;
}
pub fn egc_length(self: *const Plane, egcs: []const u8, colcount: *c_int, abs_col: usize) usize {
if (egcs[0] == '\t') {
colcount.* = @intCast(8 - abs_col % 8);
return 1;
}
var iter = self.window.screen.unicode.graphemeIterator(egcs);
const grapheme = iter.next() orelse {
colcount.* = 1;
return 1;
};
const s = grapheme.bytes(egcs);
const w = self.window.gwidth(s);
colcount.* = @intCast(w);
return s.len;
}
pub fn egc_chunk_width(self: *const Plane, 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 = self.egc_length(chunk, &cols, abs_col);
colcount += @intCast(cols);
abs_col += @intCast(cols);
if (chunk.len < bytes) break;
chunk = chunk[bytes..];
}
return colcount;
}
const GraphemeCache = struct {
buf: [1024 * 16]u8 = undefined,
idx: usize = 0,
pub fn put(self: *GraphemeCache, bytes: []const u8) []u8 {
if (self.idx + bytes.len > self.buf.len) self.idx = 0;
defer self.idx += bytes.len;
@memcpy(self.buf[self.idx .. self.idx + bytes.len], bytes);
return self.buf[self.idx .. self.idx + bytes.len];
}
};

View file

@ -0,0 +1,267 @@
const Key = @import("vaxis").Key;
const Mouse = @import("vaxis").Mouse;
pub const key = struct {
pub const ENTER: key_type = Key.enter;
pub const TAB: key_type = Key.tab;
pub const ESC: key_type = Key.escape;
pub const SPACE: key_type = Key.space;
pub const BACKSPACE: key_type = Key.backspace;
pub const INS: key_type = Key.insert;
pub const DEL: key_type = Key.delete;
pub const LEFT: key_type = Key.left;
pub const RIGHT: key_type = Key.right;
pub const UP: key_type = Key.up;
pub const DOWN: key_type = Key.down;
pub const PGDOWN: key_type = Key.page_down;
pub const PGUP: key_type = Key.page_up;
pub const HOME: key_type = Key.home;
pub const END: key_type = Key.end;
pub const CAPS_LOCK: key_type = Key.caps_lock;
pub const SCROLL_LOCK: key_type = Key.scroll_lock;
pub const NUM_LOCK: key_type = Key.num_lock;
pub const PRINT_SCREEN: key_type = Key.print_screen;
pub const PAUSE: key_type = Key.pause;
pub const MENU: key_type = Key.menu;
pub const F01: key_type = Key.f1;
pub const F02: key_type = Key.f2;
pub const F03: key_type = Key.f3;
pub const F04: key_type = Key.f4;
pub const F05: key_type = Key.f5;
pub const F06: key_type = Key.f6;
pub const F07: key_type = Key.f7;
pub const F08: key_type = Key.f8;
pub const F09: key_type = Key.f9;
pub const F10: key_type = Key.f10;
pub const F11: key_type = Key.f11;
pub const F12: key_type = Key.f12;
pub const F13: key_type = Key.f13;
pub const F14: key_type = Key.f14;
pub const F15: key_type = Key.f15;
pub const F16: key_type = Key.f16;
pub const F17: key_type = Key.f17;
pub const F18: key_type = Key.f18;
pub const F19: key_type = Key.f19;
pub const F20: key_type = Key.f20;
pub const F21: key_type = Key.f21;
pub const F22: key_type = Key.f22;
pub const F23: key_type = Key.f23;
pub const F24: key_type = Key.f24;
pub const F25: key_type = Key.f25;
pub const F26: key_type = Key.f26;
pub const F27: key_type = Key.f27;
pub const F28: key_type = Key.f28;
pub const F29: key_type = Key.f29;
pub const F30: key_type = Key.f30;
pub const F31: key_type = Key.f31;
pub const F32: key_type = Key.f32;
pub const F33: key_type = Key.f33;
pub const F34: key_type = Key.f34;
pub const F35: key_type = Key.f35;
pub const F58: key_type = Key.iso_level_5_shift + 1; // FIXME bogus
pub const MEDIA_PLAY: key_type = Key.media_play;
pub const MEDIA_PAUSE: key_type = Key.media_pause;
pub const MEDIA_PPAUSE: key_type = Key.media_play_pause;
pub const MEDIA_REV: key_type = Key.media_reverse;
pub const MEDIA_STOP: key_type = Key.media_stop;
pub const MEDIA_FF: key_type = Key.media_fast_forward;
pub const MEDIA_REWIND: key_type = Key.media_rewind;
pub const MEDIA_NEXT: key_type = Key.media_track_next;
pub const MEDIA_PREV: key_type = Key.media_track_previous;
pub const MEDIA_RECORD: key_type = Key.media_record;
pub const MEDIA_LVOL: key_type = Key.lower_volume;
pub const MEDIA_RVOL: key_type = Key.raise_volume;
pub const MEDIA_MUTE: key_type = Key.mute_volume;
pub const LSHIFT: key_type = Key.left_shift;
pub const LCTRL: key_type = Key.left_control;
pub const LALT: key_type = Key.left_alt;
pub const LSUPER: key_type = Key.left_super;
pub const LHYPER: key_type = Key.left_hyper;
pub const LMETA: key_type = Key.left_meta;
pub const RSHIFT: key_type = Key.right_shift;
pub const RCTRL: key_type = Key.right_control;
pub const RALT: key_type = Key.right_alt;
pub const RSUPER: key_type = Key.right_super;
pub const RHYPER: key_type = Key.right_hyper;
pub const RMETA: key_type = Key.right_meta;
pub const L3SHIFT: key_type = Key.iso_level_3_shift;
pub const L5SHIFT: key_type = Key.iso_level_5_shift;
pub const MOTION: key_type = @intCast(@intFromEnum(Mouse.Button.none));
pub const BUTTON1: key_type = @intCast(@intFromEnum(Mouse.Button.left));
pub const BUTTON2: key_type = @intCast(@intFromEnum(Mouse.Button.middle));
pub const BUTTON3: key_type = @intCast(@intFromEnum(Mouse.Button.right));
pub const BUTTON4: key_type = @intCast(@intFromEnum(Mouse.Button.wheel_up));
pub const BUTTON5: key_type = @intCast(@intFromEnum(Mouse.Button.wheel_down));
// pub const BUTTON6: key_type = @intCast(@intFromEnum(Mouse.Button.button_6));
// pub const BUTTON7: key_type = @intCast(@intFromEnum(Mouse.Button.button_7));
pub const BUTTON8: key_type = @intCast(@intFromEnum(Mouse.Button.button_8));
pub const BUTTON9: key_type = @intCast(@intFromEnum(Mouse.Button.button_9));
pub const BUTTON10: key_type = @intCast(@intFromEnum(Mouse.Button.button_10));
pub const BUTTON11: key_type = @intCast(@intFromEnum(Mouse.Button.button_11));
// pub const SIGNAL: key_type = Key.SIGNAL;
// pub const EOF: key_type = Key.EOF;
// pub const SCROLL_UP: key_type = Key.SCROLL_UP;
// pub const SCROLL_DOWN: key_type = Key.SCROLL_DOWN;
/// Is this uint32_t a synthesized event?
pub fn synthesized_p(w: u32) bool {
return switch (w) {
Key.up...Key.iso_level_5_shift => true,
Key.enter => true,
Key.tab => true,
Key.escape => true,
Key.space => true,
Key.backspace => true,
else => false,
};
}
};
pub const key_type = u21;
pub const modifier = struct {
pub const SHIFT: modifier_type = 1;
pub const ALT: modifier_type = 2;
pub const CTRL: modifier_type = 4;
pub const SUPER: modifier_type = 8;
pub const HYPER: modifier_type = 16;
pub const META: modifier_type = 32;
pub const CAPSLOCK: modifier_type = 64;
pub const NUMLOCK: modifier_type = 128;
};
pub const modifier_type = u32;
pub const event_type = struct {
pub const PRESS: usize = 1;
pub const REPEAT: usize = 2;
pub const RELEASE: usize = 3;
};
pub const utils = struct {
pub fn isSuper(modifiers: u32) bool {
return modifiers & modifier.SUPER != 0;
}
pub fn isCtrl(modifiers: u32) bool {
return modifiers & modifier.CTRL != 0;
}
pub fn isShift(modifiers: u32) bool {
return modifiers & modifier.SHIFT != 0;
}
pub fn isAlt(modifiers: u32) bool {
return modifiers & modifier.ALT != 0;
}
pub fn key_id_string(k: u32) []const u8 {
return switch (k) {
key.ENTER => "ENTER",
key.TAB => "TAB",
key.ESC => "ESC",
key.SPACE => "SPACE",
key.BACKSPACE => "BACKSPACE",
key.INS => "INS",
key.DEL => "DEL",
key.LEFT => "LEFT",
key.RIGHT => "RIGHT",
key.UP => "UP",
key.DOWN => "DOWN",
key.PGDOWN => "PGDOWN",
key.PGUP => "PGUP",
key.HOME => "HOME",
key.END => "END",
key.CAPS_LOCK => "CAPS_LOCK",
key.SCROLL_LOCK => "SCROLL_LOCK",
key.NUM_LOCK => "NUM_LOCK",
key.PRINT_SCREEN => "PRINT_SCREEN",
key.PAUSE => "PAUSE",
key.MENU => "MENU",
key.F01 => "F01",
key.F02 => "F02",
key.F03 => "F03",
key.F04 => "F04",
key.F05 => "F05",
key.F06 => "F06",
key.F07 => "F07",
key.F08 => "F08",
key.F09 => "F09",
key.F10 => "F10",
key.F11 => "F11",
key.F12 => "F12",
key.F13 => "F13",
key.F14 => "F14",
key.F15 => "F15",
key.F16 => "F16",
key.F17 => "F17",
key.F18 => "F18",
key.F19 => "F19",
key.F20 => "F20",
key.F21 => "F21",
key.F22 => "F22",
key.F23 => "F23",
key.F24 => "F24",
key.F25 => "F25",
key.F26 => "F26",
key.F27 => "F27",
key.F28 => "F28",
key.F29 => "F29",
key.F30 => "F30",
key.F31 => "F31",
key.F32 => "F32",
key.F33 => "F33",
key.F34 => "F34",
key.F35 => "F35",
key.MEDIA_PLAY => "MEDIA_PLAY",
key.MEDIA_PAUSE => "MEDIA_PAUSE",
key.MEDIA_PPAUSE => "MEDIA_PPAUSE",
key.MEDIA_REV => "MEDIA_REV",
key.MEDIA_STOP => "MEDIA_STOP",
key.MEDIA_FF => "MEDIA_FF",
key.MEDIA_REWIND => "MEDIA_REWIND",
key.MEDIA_NEXT => "MEDIA_NEXT",
key.MEDIA_PREV => "MEDIA_PREV",
key.MEDIA_RECORD => "MEDIA_RECORD",
key.MEDIA_LVOL => "MEDIA_LVOL",
key.MEDIA_RVOL => "MEDIA_RVOL",
key.MEDIA_MUTE => "MEDIA_MUTE",
key.LSHIFT => "LSHIFT",
key.LCTRL => "LCTRL",
key.LALT => "LALT",
key.LSUPER => "LSUPER",
key.LHYPER => "LHYPER",
key.LMETA => "LMETA",
key.RSHIFT => "RSHIFT",
key.RCTRL => "RCTRL",
key.RALT => "RALT",
key.RSUPER => "RSUPER",
key.RHYPER => "RHYPER",
key.RMETA => "RMETA",
key.L3SHIFT => "L3SHIFT",
key.L5SHIFT => "L5SHIFT",
else => "",
};
}
pub fn button_id_string(k: u32) []const u8 {
return switch (k) {
key.MOTION => "MOTION",
key.BUTTON1 => "BUTTON1",
key.BUTTON2 => "BUTTON2",
key.BUTTON3 => "BUTTON3",
key.BUTTON4 => "BUTTON4",
key.BUTTON5 => "BUTTON5",
// key.BUTTON6 => "BUTTON6",
// key.BUTTON7 => "BUTTON7",
key.BUTTON8 => "BUTTON8",
key.BUTTON9 => "BUTTON9",
key.BUTTON10 => "BUTTON10",
key.BUTTON11 => "BUTTON11",
else => "",
};
}
};

View file

@ -0,0 +1,312 @@
const std = @import("std");
const cbor = @import("cbor");
const log = @import("log");
const Style = @import("theme").Style;
const vaxis = @import("vaxis");
pub const input = @import("input.zig");
pub const Plane = @import("Plane.zig");
pub const Cell = @import("Cell.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();
pub const log_name = "vaxis";
a: std.mem.Allocator,
vx: vaxis.Vaxis,
no_alternate: bool,
event_buffer: std.ArrayList(u8),
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 ModState = struct { ctrl: bool = false, shift: bool = false, alt: bool = false };
const Event = union(enum) {
key_press: vaxis.Key,
winsize: vaxis.Winsize,
focus_in,
};
pub fn init(a: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool) !Self {
return .{
.a = a,
.vx = try vaxis.init(a, .{}),
.no_alternate = no_alternate,
.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.vx.screen.tty.write(vaxis.ctlseqs.show_cursor);
self.vx.screen.tty.flush();
self.vx.deinit(self.a);
self.bracketed_paste_buffer.deinit();
self.event_buffer.deinit();
}
pub fn run(self: *Self) !void {
if (self.vx.tty == null) self.vx.tty = try vaxis.Tty.init();
if (!self.no_alternate) try self.vx.enterAltScreen();
try self.vx.queryTerminal();
const ws = try vaxis.Tty.getWinsize(self.input_fd());
try self.vx.resize(self.a, ws);
self.vx.queueRefresh();
try self.vx.setMouseMode(true);
}
pub fn render(self: *Self) !void {
self.vx.queueRefresh(); // FIXME: why do differential updates not work?
return self.vx.render();
}
pub fn refresh(self: *Self) !void {
const ws = try vaxis.Tty.getWinsize(self.input_fd());
try self.vx.resize(self.a, ws);
self.vx.queueRefresh();
}
pub fn stop(self: *Self) void {
_ = self;
}
pub fn stdplane(self: *Self) Plane {
const name = "root";
var plane: Plane = .{
.window = self.vx.window(),
.name_buf = undefined,
.name_len = name.len,
};
@memcpy(plane.name_buf[0..name.len], name);
return plane;
}
pub fn input_fd(self: Self) i32 {
return self.vx.tty.?.fd;
}
pub fn leave_alternate_screen(self: *Self) void {
self.vx.exitAltScreen() catch {};
}
pub fn process_input(self: *Self) !void {
var parser: vaxis.Parser = .{
.grapheme_data = &self.vx.screen.unicode.grapheme_data,
};
var buf: [1024]u8 = undefined;
var start: usize = 0;
const n = std.posix.read(self.input_fd(), &buf) catch |e| switch (e) {
error.WouldBlock => return,
else => return e,
};
while (start < n) {
const result = try parser.parse(buf[start..n]);
start += result.n;
const event = result.event orelse continue;
switch (event) {
.key_press => |key_| {
const cbor_msg = try self.fmtmsg(.{
"I",
event_type.PRESS,
key_.codepoint,
key_.shifted_codepoint orelse key_.codepoint,
key_.text orelse input.utils.key_id_string(key_.codepoint),
@as(u8, @bitCast(key_.mods)),
});
if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg);
},
.key_release => |*key_| {
const cbor_msg = try self.fmtmsg(.{
"I",
event_type.RELEASE,
key_.codepoint,
key_.shifted_codepoint orelse key_.codepoint,
key_.text orelse input.utils.key_id_string(key_.codepoint),
@as(u8, @bitCast(key_.mods)),
});
if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg);
},
.mouse => |mouse| {
if (self.dispatch_mouse) |f| switch (mouse.type) {
.motion => f(self.handler_ctx, @intCast(mouse.row), @intCast(mouse.col), try self.fmtmsg(.{
"M",
mouse.col,
mouse.row,
0,
0,
})),
.press => f(self.handler_ctx, @intCast(mouse.row), @intCast(mouse.col), try self.fmtmsg(.{
"B",
event_type.PRESS,
@intFromEnum(mouse.button),
input.utils.button_id_string(@intFromEnum(mouse.button)),
mouse.col,
mouse.row,
0,
0,
})),
.release => f(self.handler_ctx, @intCast(mouse.row), @intCast(mouse.col), try self.fmtmsg(.{
"B",
event_type.RELEASE,
@intFromEnum(mouse.button),
input.utils.button_id_string(@intFromEnum(mouse.button)),
mouse.col,
mouse.row,
0,
0,
})),
.drag => f(self.handler_ctx, @intCast(mouse.row), @intCast(mouse.col), try self.fmtmsg(.{
"D",
event_type.PRESS,
@intFromEnum(mouse.button),
input.utils.button_id_string(@intFromEnum(mouse.button)),
mouse.col,
mouse.row,
0,
0,
})),
};
},
.focus_in => {
// FIXME
},
.focus_out => {
// FIXME
},
.paste_start => {
self.bracketed_paste = true;
self.bracketed_paste_buffer.clearRetainingCapacity();
},
.paste_end => {
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 }));
},
.cap_unicode => {
self.vx.caps.unicode = .unicode;
self.vx.screen.width_method = .unicode;
},
.cap_da1 => {
std.Thread.Futex.wake(&self.vx.query_futex, 10);
},
.cap_kitty_keyboard => {
self.vx.caps.kitty_keyboard = true;
},
.cap_kitty_graphics => {
if (!self.vx.caps.kitty_graphics) {
self.vx.caps.kitty_graphics = true;
}
},
.cap_rgb => {
self.vx.caps.rgb = true;
},
}
}
}
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;
}
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:";
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~";
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 {
self.vx.screen.cursor_vis = true;
self.vx.screen.cursor_row = @intCast(y);
self.vx.screen.cursor_col = @intCast(x);
}
pub fn cursor_disable(self: *Self) void {
self.vx.screen.cursor_vis = false;
}
pub fn ucs32_to_utf8(ucs32: []const u32, utf8: []u8) !usize {
return @intCast(try std.unicode.utf8Encode(@intCast(ucs32[0]), utf8));
}

View file

@ -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 = .{};
};