WIP: add Container type with corresponding Properties configuration

The configuration of the `Container` types is very much inspired by
[clay](https://github.com/nicbarker/clay).
This commit is contained in:
2025-02-01 01:05:56 +01:00
parent bdbe05c996
commit 1293cb065d
9 changed files with 361 additions and 142 deletions

View File

@@ -10,109 +10,111 @@
const std = @import("std");
const terminal = @import("terminal.zig");
const Cell = terminal.Cell;
const Position = terminal.Position;
const Size = terminal.Size;
const Cell = @import("cell.zig");
const Size = @import("size.zig");
const Position = Size.Position;
/// Double-buffered intermediate rendering pipeline
pub fn Buffered(comptime fullscreen: bool) type {
pub const Buffered = struct {
const log = std.log.scoped(.renderer_buffered);
// _ = log;
_ = fullscreen;
return struct {
allocator: std.mem.Allocator,
created: bool,
size: Size,
screen: []Cell,
virtual_screen: []Cell,
allocator: std.mem.Allocator,
created: bool,
size: Size,
screen: []Cell,
virtual_screen: []Cell,
pub fn init(allocator: std.mem.Allocator) @This() {
return .{
.allocator = allocator,
.created = false,
.size = undefined,
.screen = undefined,
.virtual_screen = undefined,
};
pub fn init(allocator: std.mem.Allocator) @This() {
return .{
.allocator = allocator,
.created = false,
.size = undefined,
.screen = undefined,
.virtual_screen = undefined,
};
}
pub fn deinit(this: *@This()) void {
if (this.created) {
this.allocator.free(this.screen);
this.allocator.free(this.virtual_screen);
}
}
pub fn deinit(this: *@This()) void {
if (this.created) {
this.allocator.free(this.screen);
this.allocator.free(this.virtual_screen);
pub fn resize(this: *@This(), size: Size) !void {
this.size = size;
if (!this.created) {
this.screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
@memset(this.screen, Cell{});
this.virtual_screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
@memset(this.virtual_screen, Cell{});
this.created = true;
return;
}
if (this.allocator.resize(this.screen, size.cols * size.rows)) {
@panic("render.zig: Could not resize `screen` buffer");
}
if (this.allocator.resize(this.virtual_screen, size.cols * size.rows)) {
@panic("render.zig: Could not resize `virtual screen` buffer.");
}
}
pub fn clear(this: *@This(), size: Size) void {
log.debug("renderer::clear", .{});
var vs = this.virtual_screen;
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
for (0..size.rows) |row| {
for (0..size.cols) |col| {
vs[anchor + (row * this.size.cols) + col].reset();
}
}
}
pub fn resize(this: *@This(), size: Size) void {
this.size = size;
if (!this.created) {
this.screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
this.virtual_screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
this.created = true;
return;
}
if (this.allocator.resize(this.screen, size.cols * size.rows)) {
@panic("render.zig: Could not resize `screen` buffer");
}
if (this.allocator.resize(this.virtual_screen, size.cols * size.rows)) {
@panic("render.zig: Could not resize `virtual screen` buffer.");
/// Render provided cells at size (anchor and dimension) into the *virtual screen*.
pub fn render(this: *@This(), comptime T: type, container: *T) void {
const cells: []const Cell = container.contents();
const size: Size = container.size;
log.debug("renderer:render: cells: {any} @ {any}", .{ cells, size });
if (cells.len == 0) return;
var idx: usize = 0;
var vs = this.virtual_screen;
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
for (0..size.rows) |row| {
for (0..size.cols) |col| {
const cell = cells[idx];
idx += 1;
vs[anchor + (row * this.size.cols) + col].style = cell.style;
vs[anchor + (row * this.size.cols) + col].rune = cell.rune;
if (cells.len == idx) return;
}
}
}
pub fn clear(this: *@This(), size: Size) !void {
log.debug("renderer::clear", .{});
var vs = this.virtual_screen;
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
for (0..size.rows) |row| {
for (0..size.cols) |col| {
vs[anchor + (row * this.size.cols) + col].reset();
}
/// Write *virtual screen* to alternate screen (should be called once and last during each render loop iteration in the main loop).
pub fn flush(this: *@This()) !void {
log.debug("renderer::flush", .{});
const writer = terminal.writer();
const s = this.screen;
const vs = this.virtual_screen;
for (0..this.size.rows) |row| {
for (0..this.size.cols) |col| {
const idx = (row * this.size.cols) + col;
const cs = s[idx];
const cvs = vs[idx];
if (cs.eql(cvs))
continue;
// render differences found in virtual screen
// TODO: improve the writing speed (many unnecessary writes (i.e. the style for every character..))
try terminal.setCursorPosition(.{ .row = @truncate(row), .col = @truncate(col) });
try cvs.value(writer);
// update screen to be the virtual screen for the next frame
s[idx] = vs[idx];
}
}
/// Render provided cells at size (anchor and dimension) into the *virtual screen*.
pub fn render(this: *@This(), size: Size, cells: []const Cell) !void {
log.debug("renderer:render: cells: {any}", .{cells});
std.debug.assert(cells.len > 0);
var idx: usize = 0;
var vs = this.virtual_screen;
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
for (0..size.rows) |row| {
for (0..size.cols) |col| {
const cell = cells[idx];
idx += 1;
vs[anchor + (row * this.size.cols) + col].style = cell.style;
vs[anchor + (row * this.size.cols) + col].rune = cell.rune;
if (cells.len == idx) return;
}
}
}
/// Write *virtual screen* to alternate screen (should be called once and last during each render loop iteration in the main loop).
pub fn flush(this: *@This()) !void {
log.debug("renderer::flush", .{});
const writer = terminal.writer();
const s = this.screen;
const vs = this.virtual_screen;
for (0..this.size.rows) |row| {
for (0..this.size.cols) |col| {
const idx = (row * this.size.cols) + col;
const cs = s[idx];
const cvs = vs[idx];
if (cs.eql(cvs))
continue;
// render differences found in virtual screen
// TODO: improve the writing speed (many unnecessary writes (i.e. the style for every character..))
try terminal.setCursorPosition(.{ .row = @truncate(row), .col = @truncate(col) });
try cvs.value(writer);
// update screen to be the virtual screen for the next frame
s[idx] = vs[idx];
}
}
}
};
}
}
};