feat(Renderer): update interface functions to start supporting buffered rendering
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 30s
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 30s
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
//! Dynamic dispatch for layout implementations.
|
||||
//! Each layout should at last implement these functions:
|
||||
//! Each layout should at least implement these functions:
|
||||
//! - handle(this: *@This(), event: Event) anyerror!*std.ArrayList(Event) {}
|
||||
//! - render(this: *@This(), renderer: *Renderer) anyerror!void {}
|
||||
//! - deinit(this: *@This()) void {}
|
||||
|
||||
27
src/main.zig
27
src/main.zig
@@ -26,10 +26,30 @@ pub fn main() !void {
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var app: App = .{};
|
||||
var renderer: App.Renderer = .{};
|
||||
var renderer = App.Renderer.init(allocator);
|
||||
defer renderer.deinit();
|
||||
// FIX: when not running fullscreen, the application needs to screen down accordingly to display the contents
|
||||
// -> size hint how much should it use?
|
||||
|
||||
// var layout = Layout.createFrom(layout: {
|
||||
// var stack = Layout.HStack.init(allocator, .{
|
||||
// Widget.createFrom(blk: {
|
||||
// var spacer = Widget.Spacer.init();
|
||||
// break :blk &spacer;
|
||||
// }),
|
||||
// Widget.createFrom(blk: {
|
||||
// const file = try std.fs.cwd().openFile("./src/app.zig", .{});
|
||||
// defer file.close();
|
||||
// var widget = Widget.RawText.init(allocator, file);
|
||||
// break :blk &widget;
|
||||
// }),
|
||||
// Widget.createFrom(blk: {
|
||||
// var spacer = Widget.Spacer.init();
|
||||
// break :blk &spacer;
|
||||
// }),
|
||||
// });
|
||||
// break :layout &stack;
|
||||
// });
|
||||
var layout = Layout.createFrom(layout: {
|
||||
var hstack = Layout.HStack.init(allocator, .{
|
||||
Widget.createFrom(blk: {
|
||||
@@ -113,6 +133,9 @@ pub fn main() !void {
|
||||
|
||||
switch (event) {
|
||||
.quit => break,
|
||||
.resize => |size| {
|
||||
renderer.resize(size);
|
||||
},
|
||||
.key => |key| {
|
||||
// ctrl+c to quit
|
||||
if (Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) {
|
||||
@@ -135,7 +158,6 @@ pub fn main() !void {
|
||||
.err => |err| {
|
||||
log.err("Received {any} with message: {s}", .{ err.err, err.msg });
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
// NOTE: this currently re-renders the screen for every key-press -> which might be a bit of an overkill
|
||||
const events = try layout.handle(event);
|
||||
@@ -143,5 +165,6 @@ pub fn main() !void {
|
||||
app.postEvent(e);
|
||||
}
|
||||
try layout.render(&renderer);
|
||||
try renderer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
111
src/render.zig
111
src/render.zig
@@ -1,49 +1,128 @@
|
||||
//! Renderer which holds the screen to compare with the previous screen for efficient rendering.
|
||||
//! Each renderer should at least implement these functions:
|
||||
//! - init(allocator: std.mem.Allocator) @This() {}
|
||||
//! - deinit(this: *@This()) void {}
|
||||
//! - resize(this: *@This(), size: Size) void {}
|
||||
//! - clear(this: *@This(), size: Size) !void {}
|
||||
//! - render(this: *@This(), size: Size, contents: []u8) !void {}
|
||||
//! - flush(this: @This()) !void {}
|
||||
//!
|
||||
//! Each `Renderer` should be able to be used interchangable without having to
|
||||
//! change any code of any `Layout` or `Widget`. The only change should be the
|
||||
//! passed type to `zterm.App` _R_ parameter.
|
||||
const std = @import("std");
|
||||
const terminal = @import("terminal.zig");
|
||||
|
||||
const Contents = std.ArrayList(u8);
|
||||
const Contents = std.ArrayList(u8); // TODO: this may contain more than just a single character! (i.e. styled)
|
||||
const Position = terminal.Position;
|
||||
const Size = terminal.Size;
|
||||
|
||||
pub fn Buffered(comptime _: bool) type {
|
||||
const log = std.log.scoped(.renderer_buffered);
|
||||
return struct {
|
||||
refresh: bool = false,
|
||||
size: terminal.Size = undefined,
|
||||
next_frame: Contents = undefined,
|
||||
frame: Contents = undefined,
|
||||
|
||||
const EMPTY: u8 = 0;
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||
_ = log;
|
||||
return .{
|
||||
.next_frame = Contents.init(allocator),
|
||||
.frame = Contents.init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(this: *@This()) void {
|
||||
this.screen.deinit();
|
||||
this.frame.deinit();
|
||||
this.* = undefined;
|
||||
}
|
||||
|
||||
pub fn resize(this: *@This(), size: Size) void {
|
||||
// TODO: are there size changes which impact the corresponding rendered content?
|
||||
// -> can I even be sure nothing needs to be re-rendered?
|
||||
std.debug.assert(size.anchor.col == 1 and size.anchor.row == 1);
|
||||
this.size = size;
|
||||
this.refresh = true;
|
||||
this.frame.resize(size.cols * size.rows) catch @panic("OOM");
|
||||
}
|
||||
|
||||
pub fn render(this: *@This(), content: *Contents) !void {
|
||||
// TODO: put the corresponding screen to the terminal
|
||||
// -> determine diff between screen and new content and only update the corresponding characters of the terminal
|
||||
_ = this;
|
||||
_ = content;
|
||||
@panic("Not yet implemented.");
|
||||
pub fn clear(this: *@This(), size: Size) error{}!void {
|
||||
for (size.anchor.row..size.anchor.row + size.rows) |row| {
|
||||
const y = this.size.cols * (row - 1);
|
||||
for (size.anchor.col..size.anchor.col + size.cols) |col| {
|
||||
// log.debug("clearing index[{d}]/[{d}] at .{{ .col = {d}, .row = {d} }}", .{ y + col - 1, this.size.cols * this.size.rows, col, row });
|
||||
std.debug.assert(y + col - 1 < this.frame.items.len);
|
||||
this.frame.items[y + col - 1] = EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(this: *@This(), size: Size, contents: []u8) !void {
|
||||
var c_idx: usize = 0;
|
||||
row: for (size.anchor.row..size.anchor.row + size.rows) |row| {
|
||||
var fill_empty = false;
|
||||
const y = this.size.cols * (row - 1);
|
||||
for (size.anchor.col..size.anchor.col + size.cols) |col| {
|
||||
std.debug.assert(y + col - 1 < this.frame.items.len);
|
||||
const idx = y + col - 1;
|
||||
const item = contents[c_idx];
|
||||
if (item == '\n') { // do not print newlines
|
||||
// reached end of line
|
||||
// fill rest of col's of this row with EMPTY
|
||||
fill_empty = true;
|
||||
}
|
||||
if (fill_empty) {
|
||||
this.frame.items[idx] = EMPTY;
|
||||
} else {
|
||||
this.frame.items[idx] = item;
|
||||
c_idx += 1;
|
||||
}
|
||||
}
|
||||
// printed row
|
||||
// are there still items for this row (even though there is no space left?)
|
||||
if (!fill_empty) {
|
||||
// find end of line and update c_idx accordingly
|
||||
for (c_idx..contents.len) |c| {
|
||||
if (contents[c] == '\n') {
|
||||
c_idx = c + 1;
|
||||
continue :row;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c_idx += 1; // skip over '\n'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(this: @This()) !void {
|
||||
try terminal.clearScreen();
|
||||
for (this.size.anchor.row..this.size.anchor.row + this.size.rows) |row| {
|
||||
const y = this.size.cols * (row - 1);
|
||||
for (1..this.size.cols) |col| {
|
||||
std.debug.assert(y + col - 1 < this.frame.items.len);
|
||||
const item = this.frame.items[y + col - 1];
|
||||
if (item == EMPTY) {
|
||||
continue;
|
||||
}
|
||||
const pos: Position = .{ .col = @truncate(col), .row = @truncate(row) };
|
||||
try terminal.setCursorPosition(pos);
|
||||
_ = try terminal.write(&.{item});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Direct(comptime _: bool) type {
|
||||
const log = std.log.scoped(.renderer_direct);
|
||||
return struct {
|
||||
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||
_ = allocator;
|
||||
_ = log;
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn deinit(this: *@This()) void {
|
||||
this.* = undefined;
|
||||
}
|
||||
|
||||
pub fn resize(this: *@This(), size: Size) void {
|
||||
_ = this;
|
||||
_ = size;
|
||||
@@ -104,5 +183,9 @@ pub fn Direct(comptime _: bool) type {
|
||||
_ = try terminal.write(contents[idx..]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(this: @This()) !void {
|
||||
_ = this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Dynamic dispatch for widget implementations.
|
||||
//! Each widget should at last implement these functions:
|
||||
//! Each widget should at least implement these functions:
|
||||
//! - handle(this: *@This(), event: Event) ?Event {}
|
||||
//! - render(this: *@This(), renderer: *Renderer) !void {}
|
||||
//! - deinit(this: *@This()) void {}
|
||||
|
||||
@@ -77,7 +77,6 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
|
||||
|
||||
pub fn render(this: *@This(), renderer: *Renderer) !void {
|
||||
try renderer.clear(this.size);
|
||||
try terminal.setCursorPosition(this.size.anchor);
|
||||
if (this.size.rows >= this.line_index.items.len) {
|
||||
try renderer.render(this.size, this.contents.items);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user