It works kinda

This commit is contained in:
2026-04-17 19:13:10 -05:00
parent 3424b1c75f
commit 3c1077bbfa
3 changed files with 115 additions and 57 deletions
+63 -28
View File
@@ -15,52 +15,87 @@ const Rect = grome.matrix.Rect;
/// approach minimizes unnecessary updates by only diffing changed cells.
pub const DoubleBuffer = struct {
// display holds the existing data.
a: Matrix(Cell),
front: Matrix(Cell),
// write is where we are writing the active changes which will be commited
// on the next rendering
b: Matrix(Cell),
// Front and back hold pointers to a and b, they will switch back and forth
// based on what is needed.
front: *Matrix(Cell),
back: *Matrix(Cell),
back: Matrix(Cell),
pub fn init(gpa: Allocator, size: Rect) !DoubleBuffer {
var a = try Matrix(Cell).init(gpa, .{ .char = [_]u8{' '} ** 8, .width = 1 }, size);
const a = try Matrix(Cell).init(gpa, .{ .char = [_]u8{' '} ** 8, .width = 1 }, size);
errdefer a.deinit(gpa);
var b = try Matrix(Cell).init(gpa, .{ .char = [_]u8{' '} ** 8, .width = 1 }, size);
const b = try Matrix(Cell).init(gpa, .{ .char = [_]u8{' '} ** 8, .width = 1 }, size);
return .{
.a = a,
.b = b,
.front = &a,
.back = &b,
.front = a,
.back = b,
};
}
pub fn deinit(self: *@This(), gpa: Allocator) void {
gpa.free(self.a);
gpa.free(self.b);
self.front.deinit(gpa);
self.back.deinit(gpa);
}
// frame returns a subset of the backbuffer where `loc` is the top left point of the
// subsection, and `size` is the area or size of the subset.
pub fn frame(self: *@This(), loc: Point, size: Rect) SubMatrix(Cell) {
return .init(self.back, loc, size);
return .init(&self.back, loc, size);
}
pub fn render(self: *@This(), w: *std.Io.Writer) !usize {
std.mem.swap(*Matrix(Cell), &self.front, &self.back);
// swap the front and back. This should be called right before rendering
fn swap(self: *@This()) void {
std.mem.swap(Matrix(Cell), &self.front, &self.back);
}
var i = try w.write("\\033[H\\033[2J");
var iter = self.front.row_iterator();
while (iter.next()) |row| {
for (row) |cell| {
i += try cell.render(w);
// render outputs contents of the backbuffer to the writer. This function **does not**
// call `flush` so you should!
pub fn hard_refresh(self: *@This(), w: *std.Io.Writer) !void {
_ = try w.write("\x1b[H\x1b[2J");
var back_iter = self.back.row_iterator();
while (back_iter.next()) |back_row| {
for (back_row) |cell| {
_ = try cell.render(w);
}
i += try w.write("\n");
_ = try w.write("\n");
}
return i;
self.swap();
}
pub fn refresh(self: *@This(), w: *std.Io.Writer) !void {
_ = try w.write("\x1b[H");
var back_iter = self.back.row_iterator();
var front_iter = self.front.row_iterator();
var skip_lines: usize = 0;
while (true) {
var back_row = back_iter.next() orelse break;
const front_row = front_iter.next() orelse break;
for (0..back_row.len) |x| {
if (back_row[x].eql(&front_row[x])) {
continue;
}
// if it's a new character then write out the rest of the line and then
// continue
try w.print("\x1b[{d};{d}H", .{ skip_lines, x + 1 });
for (back_row[x..]) |*cell| {
try cell.render(w);
}
_ = try w.write("\n");
skip_lines = 0;
break;
}
skip_lines += 1;
}
_ = try w.write("\x1b[?25l");
self.swap();
}
};
@@ -70,10 +105,10 @@ pub const Cell = struct {
/// eql returns
pub fn eql(self: *const Cell, b: *const Cell) bool {
return mem.eql(u8, self.char, b.char) and self.width == b.width;
return mem.eql(u8, &self.char, &b.char) and self.width == b.width;
}
pub fn render(self: *const @This(), w: *std.Io.Writer) !usize {
return w.write(self.char[0..self.width]);
pub fn render(self: *const @This(), w: *std.Io.Writer) !void {
_ = try w.write(self.char[0..self.width]);
}
};