feat(render): implement direct rendering
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 38s
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 38s
This commit is contained in:
@@ -78,8 +78,34 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
|||||||
.resize => |size| {
|
.resize => |size| {
|
||||||
this.size = size;
|
this.size = size;
|
||||||
// adjust size according to the containing elements
|
// adjust size according to the containing elements
|
||||||
|
log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
|
||||||
|
size.anchor.col,
|
||||||
|
size.anchor.row,
|
||||||
|
size.cols,
|
||||||
|
size.rows,
|
||||||
|
});
|
||||||
|
const len: u16 = @truncate(this.elements.items.len);
|
||||||
|
const element_cols = @divTrunc(size.cols, len);
|
||||||
|
var overflow = size.cols % len;
|
||||||
|
var offset: u16 = 0;
|
||||||
|
// adjust size according to the containing elements
|
||||||
for (this.elements.items) |*element| {
|
for (this.elements.items) |*element| {
|
||||||
const sub_event = event;
|
var cols = element_cols;
|
||||||
|
if (overflow > 0) {
|
||||||
|
overflow -|= 1;
|
||||||
|
cols += 1;
|
||||||
|
}
|
||||||
|
const sub_event: Event = .{
|
||||||
|
.resize = .{
|
||||||
|
.anchor = .{
|
||||||
|
.col = size.anchor.col + offset,
|
||||||
|
.row = size.anchor.row,
|
||||||
|
},
|
||||||
|
.cols = cols,
|
||||||
|
.rows = size.rows,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
offset += cols;
|
||||||
switch (element.*) {
|
switch (element.*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
const events = try layout.handle(sub_event);
|
const events = try layout.handle(sub_event);
|
||||||
@@ -113,17 +139,13 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(this: *@This(), renderer: Renderer) !void {
|
pub fn render(this: *@This(), renderer: Renderer) !void {
|
||||||
// TODO: concat contents accordingly to create a horizontal stack
|
|
||||||
for (this.elements.items) |*element| {
|
for (this.elements.items) |*element| {
|
||||||
switch (element.*) {
|
switch (element.*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
try layout.render(renderer);
|
try layout.render(renderer);
|
||||||
},
|
},
|
||||||
.widget => |*widget| {
|
.widget => |*widget| {
|
||||||
// TODO: clear per widget if necessary (i.e. can I query that?)
|
try widget.render(renderer);
|
||||||
// TODO: render using `renderer`
|
|
||||||
const content = try widget.content();
|
|
||||||
_ = try terminal.write(content);
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
|||||||
const Events = std.ArrayList(Event);
|
const Events = std.ArrayList(Event);
|
||||||
return struct {
|
return struct {
|
||||||
// TODO: current focused `Element`?
|
// TODO: current focused `Element`?
|
||||||
// FIX: this should not be 'hardcoded' but dynamically be calculated and updated (i.e. through the event system)
|
|
||||||
anchor: terminal.Position = .{ .col = 1, .row = 1 },
|
|
||||||
size: terminal.Size = undefined,
|
size: terminal.Size = undefined,
|
||||||
element_rows: u16 = undefined,
|
|
||||||
elements: Elements = undefined,
|
elements: Elements = undefined,
|
||||||
events: Events = undefined,
|
events: Events = undefined,
|
||||||
|
|
||||||
@@ -87,12 +84,12 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
|||||||
size.rows,
|
size.rows,
|
||||||
});
|
});
|
||||||
const len: u16 = @truncate(this.elements.items.len);
|
const len: u16 = @truncate(this.elements.items.len);
|
||||||
this.element_rows = @divTrunc(size.rows, len);
|
const element_rows = @divTrunc(size.rows, len);
|
||||||
var overflow = this.size.rows % len;
|
var overflow = size.rows % len;
|
||||||
var offset: u16 = 0;
|
var offset: u16 = 0;
|
||||||
// adjust size according to the containing elements
|
// adjust size according to the containing elements
|
||||||
for (this.elements.items) |*element| {
|
for (this.elements.items) |*element| {
|
||||||
var rows = this.element_rows;
|
var rows = element_rows;
|
||||||
if (overflow > 0) {
|
if (overflow > 0) {
|
||||||
overflow -|= 1;
|
overflow -|= 1;
|
||||||
rows += 1;
|
rows += 1;
|
||||||
@@ -141,7 +138,6 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(this: *@This(), renderer: Renderer) !void {
|
pub fn render(this: *@This(), renderer: Renderer) !void {
|
||||||
// FIX: renderer should clear only what is going to change! (i.e. the 'active' widget / layout)
|
|
||||||
for (this.elements.items) |*element| {
|
for (this.elements.items) |*element| {
|
||||||
switch (element.*) {
|
switch (element.*) {
|
||||||
.layout => |*layout| {
|
.layout => |*layout| {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const zterm = @import("zterm");
|
|||||||
|
|
||||||
const App = zterm.App(
|
const App = zterm.App(
|
||||||
union(enum) {},
|
union(enum) {},
|
||||||
zterm.Renderer.Plain,
|
zterm.Renderer.Direct,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
const Key = zterm.Key;
|
const Key = zterm.Key;
|
||||||
@@ -31,7 +31,7 @@ pub fn main() !void {
|
|||||||
// -> size hint how much should it use?
|
// -> size hint how much should it use?
|
||||||
|
|
||||||
var layout = Layout.createFrom(layout: {
|
var layout = Layout.createFrom(layout: {
|
||||||
var vstack = Layout.VStack.init(allocator, .{
|
var vstack = Layout.HStack.init(allocator, .{
|
||||||
Widget.createFrom(blk: {
|
Widget.createFrom(blk: {
|
||||||
const file = try std.fs.cwd().openFile("./src/app.zig", .{});
|
const file = try std.fs.cwd().openFile("./src/app.zig", .{});
|
||||||
defer file.close();
|
defer file.close();
|
||||||
@@ -59,6 +59,7 @@ pub fn main() !void {
|
|||||||
// App.Event loop
|
// App.Event loop
|
||||||
while (true) {
|
while (true) {
|
||||||
const event = app.nextEvent();
|
const event = app.nextEvent();
|
||||||
|
log.debug("received event: {s}", .{@tagName(event)});
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.quit => break,
|
.quit => break,
|
||||||
|
|||||||
@@ -6,16 +6,17 @@ const Contents = std.ArrayList(u8);
|
|||||||
const Position = terminal.Position;
|
const Position = terminal.Position;
|
||||||
const Size = terminal.Size;
|
const Size = terminal.Size;
|
||||||
|
|
||||||
pub fn Buffered(comptime fullscreen: bool) type {
|
pub fn Buffered(comptime _: bool) type {
|
||||||
return struct {
|
return struct {
|
||||||
refresh: bool = false,
|
refresh: bool = false,
|
||||||
size: terminal.Size = undefined,
|
size: terminal.Size = undefined,
|
||||||
screen: Contents = undefined,
|
next_frame: Contents = undefined,
|
||||||
|
frame: Contents = undefined,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) @This() {
|
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||||
return .{
|
return .{
|
||||||
.fullscreen = fullscreen,
|
.next_frame = Contents.init(allocator),
|
||||||
.screen = Contents.init(allocator),
|
.frame = Contents.init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ pub fn Buffered(comptime fullscreen: bool) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Plain(comptime _: bool) type {
|
pub fn Direct(comptime _: bool) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub fn clear(this: @This(), size: Size) !void {
|
pub fn clear(this: @This(), size: Size) !void {
|
||||||
_ = this;
|
_ = this;
|
||||||
@@ -52,12 +53,52 @@ pub fn Plain(comptime _: bool) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(this: @This(), anchor: Position, size: Size, contents: *Contents) !void {
|
pub fn render(this: @This(), size: Size, contents: []u8) !void {
|
||||||
_ = this;
|
_ = this;
|
||||||
_ = size;
|
try terminal.setCursorPosition(size.anchor);
|
||||||
// FIXME: this should respect the given `size`
|
var row: u16 = 0;
|
||||||
try terminal.setCursorPosition(anchor);
|
var idx: usize = 0;
|
||||||
_ = try terminal.write(contents.items);
|
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_
|
||||||
|
row += 1;
|
||||||
|
if (row >= size.rows) {
|
||||||
|
return; // we are done
|
||||||
|
}
|
||||||
|
try terminal.setCursorPosition(.{
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
try terminal.setCursorPosition(.{
|
||||||
|
.col = size.anchor.col,
|
||||||
|
.row = size.anchor.row + row,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
skip_next_line = true;
|
||||||
|
idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (idx < contents.len) {
|
||||||
|
_ = try terminal.write(contents[idx..]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,20 +78,21 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
|
|||||||
pub fn render(this: *@This(), renderer: Renderer) !void {
|
pub fn render(this: *@This(), renderer: Renderer) !void {
|
||||||
try renderer.clear(this.size);
|
try renderer.clear(this.size);
|
||||||
try terminal.setCursorPosition(this.size.anchor);
|
try terminal.setCursorPosition(this.size.anchor);
|
||||||
// TODO: render `this.contents`
|
|
||||||
if (this.size.rows >= this.line_index.items.len) {
|
if (this.size.rows >= this.line_index.items.len) {
|
||||||
_ = try terminal.write(this.contents.items);
|
log.debug("render: {s}", .{this.contents.items});
|
||||||
|
try renderer.render(this.size, this.contents.items);
|
||||||
} else {
|
} else {
|
||||||
// more rows than we can display
|
// more rows than we can display
|
||||||
const i = this.line_index.items[this.line];
|
const i = this.line_index.items[this.line];
|
||||||
const e = this.size.rows + this.line;
|
const e = this.size.rows + this.line;
|
||||||
if (e > this.line_index.items.len) {
|
if (e > this.line_index.items.len) {
|
||||||
_ = try terminal.write(this.contents.items[i..]);
|
log.debug("render: {s}", .{this.contents.items[i..]});
|
||||||
|
try renderer.render(this.size, this.contents.items[i..]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// last line should not end with the last character (likely a newline character)
|
const x = this.line_index.items[e];
|
||||||
// FIX: what about files which do not end with a newline?
|
log.debug("render: {s}", .{this.contents.items[i..x]});
|
||||||
const x = this.line_index.items[e] - 1;
|
try renderer.render(this.size, this.contents.items[i..x]);
|
||||||
_ = try terminal.write(this.contents.items[i..x]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user