render: complete the render loop
This loop matches the go version of Vaxis to a tee. :chefs-kiss: Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
parent
13e9dadbb1
commit
de91cbe2d0
4 changed files with 219 additions and 3 deletions
|
@ -22,12 +22,18 @@ pub fn main() !void {
|
||||||
|
|
||||||
try vx.enterAltScreen();
|
try vx.enterAltScreen();
|
||||||
|
|
||||||
|
var color_idx: u8 = 0;
|
||||||
const msg = "Hello, world!";
|
const msg = "Hello, world!";
|
||||||
outer: while (true) {
|
outer: while (true) {
|
||||||
const event = vx.nextEvent();
|
const event = vx.nextEvent();
|
||||||
log.debug("event: {}\r\n", .{event});
|
log.debug("event: {}\r\n", .{event});
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.key_press => |key| {
|
.key_press => |key| {
|
||||||
|
if (color_idx == 255) {
|
||||||
|
color_idx = 0;
|
||||||
|
} else {
|
||||||
|
color_idx += 1;
|
||||||
|
}
|
||||||
if (key.codepoint == 'c' and key.mods.ctrl) {
|
if (key.codepoint == 'c' and key.mods.ctrl) {
|
||||||
break :outer;
|
break :outer;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +48,10 @@ pub fn main() !void {
|
||||||
win.clear();
|
win.clear();
|
||||||
const child = win.initChild(win.width / 2 - msg.len / 2, win.height / 2, .expand, .expand);
|
const child = win.initChild(win.width / 2 - msg.len / 2, win.height / 2, .expand, .expand);
|
||||||
for (msg, 0..) |_, i| {
|
for (msg, 0..) |_, i| {
|
||||||
const cell: Cell = .{ .char = .{ .grapheme = msg[i .. i + 1] } };
|
const cell: Cell = .{
|
||||||
|
.char = .{ .grapheme = msg[i .. i + 1] },
|
||||||
|
.style = .{ .fg = .{ .index = color_idx } },
|
||||||
|
};
|
||||||
child.writeCell(i, 0, cell);
|
child.writeCell(i, 0, cell);
|
||||||
}
|
}
|
||||||
try vx.render();
|
try vx.render();
|
||||||
|
|
|
@ -24,6 +24,14 @@ pub const Style = struct {
|
||||||
ul_style: Underline = .off,
|
ul_style: Underline = .off,
|
||||||
url: ?[]const u8 = null,
|
url: ?[]const u8 = null,
|
||||||
url_params: ?[]const u8 = null,
|
url_params: ?[]const u8 = null,
|
||||||
|
|
||||||
|
bold: bool = false,
|
||||||
|
dim: bool = false,
|
||||||
|
italic: bool = false,
|
||||||
|
blink: bool = false,
|
||||||
|
reverse: bool = false,
|
||||||
|
invisible: bool = false,
|
||||||
|
strikethrough: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Color = union(enum) {
|
pub const Color = union(enum) {
|
||||||
|
|
|
@ -11,7 +11,52 @@ pub const csi_u_pop = "\x1b[<u";
|
||||||
// Cursor
|
// Cursor
|
||||||
pub const home = "\x1b[H";
|
pub const home = "\x1b[H";
|
||||||
pub const cup = "\x1b[{d};{d}H";
|
pub const cup = "\x1b[{d};{d}H";
|
||||||
|
pub const hide_cursor = "\x1b[?25l";
|
||||||
|
pub const show_cursor = "\x1b[?25h";
|
||||||
|
|
||||||
// alt screen
|
// alt screen
|
||||||
pub const smcup = "\x1b[?1049h";
|
pub const smcup = "\x1b[?1049h";
|
||||||
pub const rmcup = "\x1b[?1049l";
|
pub const rmcup = "\x1b[?1049l";
|
||||||
|
|
||||||
|
// colors
|
||||||
|
pub const fg_base = "\x1b[3{d}m";
|
||||||
|
pub const fg_bright = "\x1b[9{d}m";
|
||||||
|
pub const bg_base = "\x1b[4{d}m";
|
||||||
|
pub const bg_bright = "\x1b[10{d}m";
|
||||||
|
|
||||||
|
pub const fg_reset = "\x1b[39m";
|
||||||
|
pub const bg_reset = "\x1b[49m";
|
||||||
|
pub const ul_reset = "\x1b[59m";
|
||||||
|
pub const fg_indexed = "\x1b[38;5;{d}m";
|
||||||
|
pub const bg_indexed = "\x1b[48:5:{d}m";
|
||||||
|
pub const ul_indexed = "\x1b[58:5:{d}m";
|
||||||
|
pub const fg_rgb = "\x1b[38:2:{d}:{d}:{d}m";
|
||||||
|
pub const bg_rgb = "\x1b[48:2:{d}:{d}:{d}m";
|
||||||
|
pub const ul_rgb = "\x1b[58:2:{d}:{d}:{d}m";
|
||||||
|
|
||||||
|
// Underlines
|
||||||
|
pub const ul_off = "\x1b[24m"; // NOTE: this could be \x1b[4:0m but is not as widely supported
|
||||||
|
pub const ul_single = "\x1b[4m";
|
||||||
|
pub const ul_double = "\x1b[4:2m";
|
||||||
|
pub const ul_curly = "\x1b[4:3m";
|
||||||
|
pub const ul_dotted = "\x1b[4:4m";
|
||||||
|
pub const ul_dashed = "\x1b[4:5m";
|
||||||
|
|
||||||
|
// Attributes
|
||||||
|
pub const bold_set = "\x1b[1m";
|
||||||
|
pub const dim_set = "\x1b[2m";
|
||||||
|
pub const italic_set = "\x1b[3m";
|
||||||
|
pub const blink_set = "\x1b[5m";
|
||||||
|
pub const reverse_set = "\x1b[7m";
|
||||||
|
pub const invisible_set = "\x1b[8m";
|
||||||
|
pub const strikethrough_set = "\x1b[9m";
|
||||||
|
pub const bold_dim_reset = "\x1b[22m";
|
||||||
|
pub const italic_reset = "\x1b[23m";
|
||||||
|
pub const blink_reset = "\x1b[25m";
|
||||||
|
pub const reverse_reset = "\x1b[27m";
|
||||||
|
pub const invisible_reset = "\x1b[28m";
|
||||||
|
pub const strikethrough_reset = "\x1b[29m";
|
||||||
|
|
||||||
|
// OSC sequences
|
||||||
|
pub const osc8 = "\x1b]8;{s};{s}\x1b\\";
|
||||||
|
pub const osc8_clear = "\x1b]8;;\x1b\\";
|
||||||
|
|
158
src/vaxis.zig
158
src/vaxis.zig
|
@ -8,6 +8,7 @@ const Key = @import("Key.zig");
|
||||||
const Screen = @import("Screen.zig");
|
const Screen = @import("Screen.zig");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
const Options = @import("Options.zig");
|
const Options = @import("Options.zig");
|
||||||
|
const Style = @import("cell.zig").Style;
|
||||||
|
|
||||||
/// Vaxis is the entrypoint for a Vaxis application. The provided type T should
|
/// Vaxis is the entrypoint for a Vaxis application. The provided type T should
|
||||||
/// be a tagged union which contains all of the events the application will
|
/// be a tagged union which contains all of the events the application will
|
||||||
|
@ -135,18 +136,30 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
self.alt_screen = false;
|
self.alt_screen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// draws the screen to the terminal
|
||||||
pub fn render(self: *Self) !void {
|
pub fn render(self: *Self) !void {
|
||||||
var tty = self.tty orelse return;
|
var tty = self.tty orelse return;
|
||||||
|
|
||||||
// TODO: optimize writes
|
// TODO: optimize writes
|
||||||
|
|
||||||
// Send the cursor to 0,0
|
// Send the cursor to 0,0
|
||||||
|
// TODO: this needs to move after we optimize writes. We only do
|
||||||
|
// this if we have an update to make. We also need to hide cursor
|
||||||
|
// and then reshow it if needed
|
||||||
|
_ = try tty.write(ctlseqs.hide_cursor);
|
||||||
_ = try tty.write(ctlseqs.home);
|
_ = try tty.write(ctlseqs.home);
|
||||||
|
|
||||||
|
// initialize some variables
|
||||||
var reposition: bool = false;
|
var reposition: bool = false;
|
||||||
var row: usize = 0;
|
var row: usize = 0;
|
||||||
var col: usize = 0;
|
var col: usize = 0;
|
||||||
for (self.screen.buf, 0..) |cell, i| {
|
var cursor: Style = .{};
|
||||||
col += 1;
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < self.screen.buf.len) : (i += 1) {
|
||||||
|
const cell = self.screen.buf[i];
|
||||||
|
defer col += 1;
|
||||||
|
defer cursor = cell.style;
|
||||||
if (col == self.screen.width) {
|
if (col == self.screen.width) {
|
||||||
row += 1;
|
row += 1;
|
||||||
col = 0;
|
col = 0;
|
||||||
|
@ -155,13 +168,154 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
// anything
|
// anything
|
||||||
if (std.meta.eql(cell, self.screen_last.buf[i])) {
|
if (std.meta.eql(cell, self.screen_last.buf[i])) {
|
||||||
reposition = true;
|
reposition = true;
|
||||||
|
// Close any osc8 sequence we might be in before
|
||||||
|
// repositioning
|
||||||
|
if (cursor.url) |_| {
|
||||||
|
_ = try tty.write(ctlseqs.osc8_clear);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Set this cell in the last frame
|
// Set this cell in the last frame
|
||||||
self.screen_last.buf[i] = cell;
|
self.screen_last.buf[i] = cell;
|
||||||
|
|
||||||
|
// reposition the cursor, if needed
|
||||||
if (reposition) {
|
if (reposition) {
|
||||||
try std.fmt.format(tty.writer(), ctlseqs.cup, .{ row + 1, col + 1 });
|
try std.fmt.format(tty.writer(), ctlseqs.cup, .{ row + 1, col + 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// something is different, so let's loop throuugh everything and
|
||||||
|
// find out what
|
||||||
|
|
||||||
|
// foreground
|
||||||
|
if (!std.meta.eql(cursor.fg, cell.style.fg)) {
|
||||||
|
switch (cell.style.fg) {
|
||||||
|
.default => _ = try tty.write(ctlseqs.fg_reset),
|
||||||
|
.index => |idx| {
|
||||||
|
switch (idx) {
|
||||||
|
0...7 => try std.fmt.format(tty.writer(), ctlseqs.fg_base, .{idx}),
|
||||||
|
8...15 => try std.fmt.format(tty.writer(), ctlseqs.fg_bright, .{idx}),
|
||||||
|
else => try std.fmt.format(tty.writer(), ctlseqs.fg_indexed, .{idx}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.rgb => |rgb| {
|
||||||
|
try std.fmt.format(tty.writer(), ctlseqs.fg_rgb, .{ rgb[0], rgb[1], rgb[2] });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// background
|
||||||
|
if (!std.meta.eql(cursor.bg, cell.style.bg)) {
|
||||||
|
switch (cell.style.bg) {
|
||||||
|
.default => _ = try tty.write(ctlseqs.bg_reset),
|
||||||
|
.index => |idx| {
|
||||||
|
switch (idx) {
|
||||||
|
0...7 => try std.fmt.format(tty.writer(), ctlseqs.bg_base, .{idx}),
|
||||||
|
8...15 => try std.fmt.format(tty.writer(), ctlseqs.bg_bright, .{idx}),
|
||||||
|
else => try std.fmt.format(tty.writer(), ctlseqs.bg_indexed, .{idx}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.rgb => |rgb| {
|
||||||
|
try std.fmt.format(tty.writer(), ctlseqs.bg_rgb, .{ rgb[0], rgb[1], rgb[2] });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// underline color
|
||||||
|
if (!std.meta.eql(cursor.ul, cell.style.ul)) {
|
||||||
|
switch (cell.style.bg) {
|
||||||
|
.default => _ = try tty.write(ctlseqs.ul_reset),
|
||||||
|
.index => |idx| {
|
||||||
|
try std.fmt.format(tty.writer(), ctlseqs.ul_indexed, .{idx});
|
||||||
|
},
|
||||||
|
.rgb => |rgb| {
|
||||||
|
try std.fmt.format(tty.writer(), ctlseqs.ul_rgb, .{ rgb[0], rgb[1], rgb[2] });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// underline style
|
||||||
|
if (!std.meta.eql(cursor.ul_style, cell.style.ul_style)) {
|
||||||
|
const seq = switch (cell.style.ul_style) {
|
||||||
|
.off => ctlseqs.ul_off,
|
||||||
|
.single => ctlseqs.ul_single,
|
||||||
|
.double => ctlseqs.ul_double,
|
||||||
|
.curly => ctlseqs.ul_curly,
|
||||||
|
.dotted => ctlseqs.ul_dotted,
|
||||||
|
.dashed => ctlseqs.ul_dashed,
|
||||||
|
};
|
||||||
|
_ = try tty.write(seq);
|
||||||
|
}
|
||||||
|
// bold
|
||||||
|
if (cursor.bold != cell.style.bold) {
|
||||||
|
const seq = switch (cell.style.bold) {
|
||||||
|
true => ctlseqs.bold_set,
|
||||||
|
false => ctlseqs.bold_dim_reset,
|
||||||
|
};
|
||||||
|
_ = try tty.write(seq);
|
||||||
|
if (cell.style.dim) {
|
||||||
|
_ = try tty.write(ctlseqs.dim_set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dim
|
||||||
|
if (cursor.dim != cell.style.dim) {
|
||||||
|
const seq = switch (cell.style.dim) {
|
||||||
|
true => ctlseqs.dim_set,
|
||||||
|
false => ctlseqs.bold_dim_reset,
|
||||||
|
};
|
||||||
|
_ = try tty.write(seq);
|
||||||
|
if (cell.style.bold) {
|
||||||
|
_ = try tty.write(ctlseqs.bold_set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dim
|
||||||
|
if (cursor.italic != cell.style.italic) {
|
||||||
|
const seq = switch (cell.style.italic) {
|
||||||
|
true => ctlseqs.italic_set,
|
||||||
|
false => ctlseqs.italic_reset,
|
||||||
|
};
|
||||||
|
_ = try tty.write(seq);
|
||||||
|
}
|
||||||
|
// dim
|
||||||
|
if (cursor.blink != cell.style.blink) {
|
||||||
|
const seq = switch (cell.style.blink) {
|
||||||
|
true => ctlseqs.blink_set,
|
||||||
|
false => ctlseqs.blink_reset,
|
||||||
|
};
|
||||||
|
_ = try tty.write(seq);
|
||||||
|
}
|
||||||
|
// reverse
|
||||||
|
if (cursor.reverse != cell.style.reverse) {
|
||||||
|
const seq = switch (cell.style.reverse) {
|
||||||
|
true => ctlseqs.reverse_set,
|
||||||
|
false => ctlseqs.reverse_reset,
|
||||||
|
};
|
||||||
|
_ = try tty.write(seq);
|
||||||
|
}
|
||||||
|
// invisible
|
||||||
|
if (cursor.invisible != cell.style.invisible) {
|
||||||
|
const seq = switch (cell.style.invisible) {
|
||||||
|
true => ctlseqs.invisible_set,
|
||||||
|
false => ctlseqs.invisible_reset,
|
||||||
|
};
|
||||||
|
_ = try tty.write(seq);
|
||||||
|
}
|
||||||
|
// strikethrough
|
||||||
|
if (cursor.strikethrough != cell.style.strikethrough) {
|
||||||
|
const seq = switch (cell.style.strikethrough) {
|
||||||
|
true => ctlseqs.strikethrough_set,
|
||||||
|
false => ctlseqs.strikethrough_reset,
|
||||||
|
};
|
||||||
|
_ = try tty.write(seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// url
|
||||||
|
if (!std.meta.eql(cursor.url, cell.style.url)) {
|
||||||
|
const url = cell.style.url orelse "";
|
||||||
|
var ps = cell.style.url_params orelse "";
|
||||||
|
if (url.len == 0) {
|
||||||
|
// Empty out the params no matter what if we don't have
|
||||||
|
// a url
|
||||||
|
ps = "";
|
||||||
|
}
|
||||||
|
try std.fmt.format(tty.writer(), ctlseqs.osc8, .{ ps, url });
|
||||||
|
}
|
||||||
_ = try tty.write(cell.char.grapheme);
|
_ = try tty.write(cell.char.grapheme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue