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 Error = @import("../event.zig").Error;
const Key = terminal.Key;
const Style = terminal.Style;
const Style = terminal.Cell.Style;
const log = std.log.scoped(.layout_framing);

View File

@@ -14,6 +14,7 @@ const std = @import("std");
const terminal = @import("terminal.zig");
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 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;
try terminal.setCursorPosition(size.anchor);
var row: u16 = 0;
var idx: usize = 0;
var skip_next_line = false;
for (contents, 0..) |item, i| {
if (item == '\n') { // do not print newlines
if (!skip_next_line) {
_ = try terminal.write(contents[idx..i]); // does not include '\n' at position _i_
var remaining_cols = size.cols;
const writer = terminal.writer();
for (cells) |cell| {
var idx: usize = 0;
print_cell: while (true) {
const cell_len = cell.len(idx);
if (cell_len > remaining_cols) {
const result = try cell.writeUpToNewline(writer, idx, idx + remaining_cols);
row += 1;
if (row >= size.rows) {
return; // we are done
@@ -154,34 +157,46 @@ pub fn Direct(comptime _: bool) type {
.col = size.anchor.col,
.row = size.anchor.row + row,
});
}
skip_next_line = false;
idx = i + 1; // skip over '\n'
continue;
}
if (i - idx == size.cols) {
// FIXME: this will introduce another additional line which is not accounted for and will cut off these lines in the end from rendering
// -> *current solution*: cut of the line and skip that remaining content (not sure if that should be done)
// - 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?
// flush line
if (!skip_next_line) {
_ = try terminal.write(contents[idx..i]);
row += 1;
if (row >= size.rows) {
return; // we are done
remaining_cols = size.cols;
idx = result.idx;
if (result.newline) {
idx += 1; // skip over newline
} else {
// there is still content to the newline (which will not be printed)
for (idx..cell.content.len) |i| {
if (cell.content[i] == '\n') {
idx = i + 1;
continue :print_cell;
}
}
break; // go to next cell (as we went to the end of the cell and do not print on the next line)
}
} 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;
idx = i;
// written all cell contents
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 {

View File

@@ -2,7 +2,7 @@ const std = @import("std");
pub const Key = @import("terminal/Key.zig");
pub const Size = @import("terminal/Size.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");
const log = std.log.scoped(.terminal);
@@ -229,5 +229,5 @@ fn getReportMode(ps: u8) ReportMode {
}
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 {
try renderer.clear(this.size);
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 {
// more rows than we can display
const i = this.line_index.items[this.line];
const e = this.size.rows + this.line;
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;
}
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 Position = terminal.Position;
pub const Size = terminal.Size;
pub const Style = terminal.Style;
pub const Cell = terminal.Cell;
test {
_ = @import("terminal.zig");