feat(renderer): render cells instead of raw u8 array contents
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 39s

This commit is contained in:
2024-11-12 19:11:19 +01:00
parent 510bf7d885
commit 07e4819ecd
6 changed files with 122 additions and 38 deletions

View File

@@ -8,7 +8,7 @@ const terminal = @import("../terminal.zig");
const isTaggedUnion = @import("../event.zig").isTaggedUnion; const isTaggedUnion = @import("../event.zig").isTaggedUnion;
const Error = @import("../event.zig").Error; const Error = @import("../event.zig").Error;
const Key = terminal.Key; const Key = terminal.Key;
const Style = terminal.Style; const Style = terminal.Cell.Style;
const log = std.log.scoped(.layout_framing); const log = std.log.scoped(.layout_framing);

View File

@@ -14,6 +14,7 @@ const std = @import("std");
const terminal = @import("terminal.zig"); const terminal = @import("terminal.zig");
const Contents = std.ArrayList(u8); // TODO: this may contain more than just a single character! (i.e. styled) const Contents = std.ArrayList(u8); // TODO: this may contain more than just a single character! (i.e. styled)
const Cells = []const terminal.Cell;
const Position = terminal.Position; const Position = terminal.Position;
const Size = terminal.Size; const Size = terminal.Size;
@@ -136,16 +137,18 @@ pub fn Direct(comptime _: bool) type {
} }
} }
pub fn render(this: *@This(), size: Size, contents: []u8) !void { pub fn render(this: *@This(), size: Size, cells: Cells) !void {
_ = this; _ = this;
try terminal.setCursorPosition(size.anchor); try terminal.setCursorPosition(size.anchor);
var row: u16 = 0; var row: u16 = 0;
var idx: usize = 0; var remaining_cols = size.cols;
var skip_next_line = false; const writer = terminal.writer();
for (contents, 0..) |item, i| { for (cells) |cell| {
if (item == '\n') { // do not print newlines var idx: usize = 0;
if (!skip_next_line) { print_cell: while (true) {
_ = try terminal.write(contents[idx..i]); // does not include '\n' at position _i_ const cell_len = cell.len(idx);
if (cell_len > remaining_cols) {
const result = try cell.writeUpToNewline(writer, idx, idx + remaining_cols);
row += 1; row += 1;
if (row >= size.rows) { if (row >= size.rows) {
return; // we are done return; // we are done
@@ -154,34 +157,46 @@ pub fn Direct(comptime _: bool) type {
.col = size.anchor.col, .col = size.anchor.col,
.row = size.anchor.row + row, .row = size.anchor.row + row,
}); });
} remaining_cols = size.cols;
skip_next_line = false; idx = result.idx;
idx = i + 1; // skip over '\n' if (result.newline) {
continue; idx += 1; // skip over newline
} } else {
if (i - idx == size.cols) { // there is still content to the newline (which will not be printed)
// FIXME: this will introduce another additional line which is not accounted for and will cut off these lines in the end from rendering for (idx..cell.content.len) |i| {
// -> *current solution*: cut of the line and skip that remaining content (not sure if that should be done) if (cell.content[i] == '\n') {
// - however the widget has actually knowledge about the size and could change the reported contents which fit to the size or simply don't care and leave it to the renderer? idx = i + 1;
// flush line continue :print_cell;
if (!skip_next_line) { }
_ = try terminal.write(contents[idx..i]); }
row += 1; break; // go to next cell (as we went to the end of the cell and do not print on the next line)
if (row >= size.rows) { }
return; // we are done } else {
// print rest of cell
const result = try cell.writeUpToNewline(writer, idx, idx + cell_len);
if (result.newline) {
row += 1;
if (row >= size.rows) {
return; // we are done
}
try terminal.setCursorPosition(.{
.col = size.anchor.col,
.row = size.anchor.row + row,
});
remaining_cols = size.cols;
idx = result.idx + 1; // skip over newline
} else {
remaining_cols -= @truncate(cell_len - idx);
idx = 0;
break; // go to next cell
} }
try terminal.setCursorPosition(.{
.col = size.anchor.col,
.row = size.anchor.row + row,
});
} }
skip_next_line = true; // written all cell contents
idx = i; if (idx >= cell.content.len) {
break; // go to next cell
}
} }
} }
if (idx < contents.len) {
_ = try terminal.write(contents[idx..]);
}
} }
pub fn flush(this: @This()) !void { pub fn flush(this: @This()) !void {

View File

@@ -2,7 +2,7 @@ const std = @import("std");
pub const Key = @import("terminal/Key.zig"); pub const Key = @import("terminal/Key.zig");
pub const Size = @import("terminal/Size.zig"); pub const Size = @import("terminal/Size.zig");
pub const Position = @import("terminal/Position.zig"); pub const Position = @import("terminal/Position.zig");
pub const Style = @import("terminal/Style.zig"); pub const Cell = @import("terminal/Cell.zig");
pub const code_point = @import("code_point"); pub const code_point = @import("code_point");
const log = std.log.scoped(.terminal); const log = std.log.scoped(.terminal);
@@ -229,5 +229,5 @@ fn getReportMode(ps: u8) ReportMode {
} }
test { test {
_ = Style; _ = Cell;
} }

63
src/terminal/Cell.zig Normal file
View File

@@ -0,0 +1,63 @@
const std = @import("std");
pub const Style = @import("Style.zig");
style: Style = .{},
content: []u8 = undefined,
pub const Result = struct {
idx: usize,
newline: bool,
};
pub fn len(this: @This(), start: usize) usize {
std.debug.assert(this.content.len > start);
return this.content[start..].len;
}
pub fn write(this: @This(), writer: anytype, start: usize, end: usize) !void {
std.debug.assert(this.content.len > start);
std.debug.assert(this.content.len >= end);
std.debug.assert(start < end);
try this.style.value(writer, this.content[start..end]);
}
pub fn writeUpToNewline(this: @This(), writer: anytype, start: usize, end: usize) !Result {
std.debug.assert(this.content.len > start);
std.debug.assert(this.content.len >= end);
std.debug.assert(start < end);
for (start..end) |i| {
if (this.content[i] == '\n') {
if (start < i) {
// this is just an empty line with a newline
try this.value(writer, start, i);
}
return .{
.idx = i,
.newline = true,
};
}
}
try this.write(writer, start, end);
return .{
.idx = end,
.newline = false,
};
}
pub fn value(this: @This(), writer: anytype, start: usize, end: usize) !void {
std.debug.assert(start < this.content.len);
std.debug.assert(this.content.len >= end);
std.debug.assert(start < end);
try this.style.value(writer, this.content[start..end]);
}
// not really supported
pub fn format(this: @This(), writer: anytype, comptime fmt: []const u8, args: anytype) !void {
try this.style.format(writer, fmt, args); // NOTE: args should contain this.content[start..end] or this.content
}
test {
_ = Style;
}

View File

@@ -78,17 +78,23 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
pub fn render(this: *@This(), renderer: *Renderer) !void { pub fn render(this: *@This(), renderer: *Renderer) !void {
try renderer.clear(this.size); try renderer.clear(this.size);
if (this.size.rows >= this.line_index.items.len) { if (this.size.rows >= this.line_index.items.len) {
try renderer.render(this.size, this.contents.items); try renderer.render(this.size, &[_]terminal.Cell{
.{ .content = this.contents.items, .style = .{ .dim = true, .fg = .{ .index = 8 } } },
});
} else { } else {
// more rows than we can display // more rows than we can display
const i = this.line_index.items[this.line]; const i = this.line_index.items[this.line];
const e = this.size.rows + this.line; const e = this.size.rows + this.line;
if (e > this.line_index.items.len) { if (e > this.line_index.items.len) {
try renderer.render(this.size, this.contents.items[i..]); try renderer.render(this.size, &[_]terminal.Cell{
.{ .content = this.contents.items[i..], .style = .{ .dim = true, .fg = .{ .index = 7 } } },
});
return; return;
} }
const x = this.line_index.items[e]; const x = this.line_index.items[e];
try renderer.render(this.size, this.contents.items[i..x]); try renderer.render(this.size, &[_]terminal.Cell{
.{ .content = this.contents.items[i..x], .style = .{ .dim = true, .fg = .{ .index = 9 } } },
});
} }
} }
}; };

View File

@@ -8,7 +8,7 @@ pub const Renderer = @import("render.zig");
pub const Key = terminal.Key; pub const Key = terminal.Key;
pub const Position = terminal.Position; pub const Position = terminal.Position;
pub const Size = terminal.Size; pub const Size = terminal.Size;
pub const Style = terminal.Style; pub const Cell = terminal.Cell;
test { test {
_ = @import("terminal.zig"); _ = @import("terminal.zig");