All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m2s
Now the `App` contains a state which is a user-defined `struct` which is passed to the `handle` and `contents` callbacks for `Container`'s and `Element`'s. Built-in `Element`'s shall not access the `App.Model` and should therefore never cause any side-effects. User-defined events shall be used to act as *messages* to cause potential side-effects for the model. This is the reason why only the `handle` callback has a non-const pointer to the `App.Model`. The `contents` callback can only access the `App.Model` read-only to use for generating the *view* (in context of the elm architecture).
132 lines
4.5 KiB
Zig
132 lines
4.5 KiB
Zig
//! Renderer for `zterm`.
|
|
|
|
/// Double-buffered intermediate rendering pipeline
|
|
pub const Buffered = struct {
|
|
allocator: Allocator,
|
|
created: bool,
|
|
size: Point,
|
|
screen: []Cell,
|
|
virtual_screen: []Cell,
|
|
|
|
pub fn init(allocator: 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 resize(this: *@This()) !Point {
|
|
const size = terminal.getTerminalSize();
|
|
if (meta.eql(this.size, size)) return this.size;
|
|
|
|
this.size = size;
|
|
const n = @as(usize, this.size.x) * @as(usize, this.size.y);
|
|
|
|
if (!this.created) {
|
|
this.screen = this.allocator.alloc(Cell, n) catch @panic("render.zig: Out of memory.");
|
|
|
|
this.virtual_screen = this.allocator.alloc(Cell, n) catch @panic("render.zig: Out of memory.");
|
|
@memset(this.virtual_screen, .{});
|
|
|
|
this.created = true;
|
|
} else {
|
|
this.allocator.free(this.screen);
|
|
this.screen = this.allocator.alloc(Cell, n) catch @panic("render.zig: Out of memory.");
|
|
|
|
this.allocator.free(this.virtual_screen);
|
|
this.virtual_screen = this.allocator.alloc(Cell, n) catch @panic("render.zig: Out of memory.");
|
|
@memset(this.virtual_screen, .{});
|
|
}
|
|
try this.clear();
|
|
return size;
|
|
}
|
|
|
|
/// Clear the entire screen and reset the screen buffer, to force a re-draw with the next `flush` call.
|
|
pub fn clear(this: *@This()) !void {
|
|
try terminal.clearScreen();
|
|
@memset(this.screen, .{});
|
|
}
|
|
|
|
/// Render provided cells at size (anchor and dimension) into the *virtual screen*.
|
|
pub fn render(this: *@This(), comptime Container: type, container: *Container, comptime Model: type, model: *const Model) !void {
|
|
const size: Point = container.size;
|
|
const origin: Point = container.origin;
|
|
const cells: []const Cell = try container.content(model);
|
|
|
|
if (cells.len == 0) return;
|
|
|
|
var idx: usize = 0;
|
|
var vs = this.virtual_screen;
|
|
const anchor: usize = (@as(usize, origin.y) * @as(usize, this.size.x)) + @as(usize, origin.x);
|
|
|
|
blk: for (0..size.y) |row| {
|
|
for (0..size.x) |col| {
|
|
vs[anchor + (row * this.size.x) + col] = cells[idx];
|
|
idx += 1;
|
|
|
|
if (cells.len == idx) break :blk;
|
|
}
|
|
}
|
|
// free immediately
|
|
container.allocator.free(cells);
|
|
|
|
for (container.elements.items) |*element| try this.render(Container, element, Model, model);
|
|
}
|
|
|
|
/// 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 {
|
|
try terminal.hideCursor();
|
|
// TODO measure timings of rendered frames?
|
|
var cursor_position: ?Point = null;
|
|
var writer = terminal.writer();
|
|
const s = this.screen;
|
|
const vs = this.virtual_screen;
|
|
for (0..this.size.y) |row| {
|
|
for (0..this.size.x) |col| {
|
|
const idx = (row * this.size.x) + col;
|
|
const cs = s[idx];
|
|
const cvs = vs[idx];
|
|
|
|
// update the latest found cursor position
|
|
if (cvs.style.cursor) {
|
|
assert(cursor_position == null);
|
|
cursor_position = .{
|
|
.x = @truncate(col),
|
|
.y = @truncate(row),
|
|
};
|
|
}
|
|
|
|
if (cs.eql(cvs)) continue;
|
|
|
|
// render differences found in virtual screen
|
|
try terminal.setCursorPosition(.{ .y = @truncate(row), .x = @truncate(col) });
|
|
try cvs.value(&writer);
|
|
// update screen to be the virtual screen for the next frame
|
|
s[idx] = vs[idx];
|
|
}
|
|
}
|
|
if (cursor_position) |point| {
|
|
try terminal.showCursor();
|
|
try terminal.setCursorPosition(point);
|
|
}
|
|
}
|
|
};
|
|
|
|
const std = @import("std");
|
|
const meta = std.meta;
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const terminal = @import("terminal.zig");
|
|
const Cell = @import("cell.zig");
|
|
const Point = @import("point.zig").Point;
|