diff --git a/src/layout/HStack.zig b/src/layout/HStack.zig index 2c1ea46..8d2ad5f 100644 --- a/src/layout/HStack.zig +++ b/src/layout/HStack.zig @@ -78,8 +78,34 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { .resize => |size| { this.size = size; // 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| { - 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.*) { .layout => |*layout| { 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 { - // TODO: concat contents accordingly to create a horizontal stack for (this.elements.items) |*element| { switch (element.*) { .layout => |*layout| { try layout.render(renderer); }, .widget => |*widget| { - // TODO: clear per widget if necessary (i.e. can I query that?) - // TODO: render using `renderer` - const content = try widget.content(); - _ = try terminal.write(content); + try widget.render(renderer); }, } } diff --git a/src/layout/VStack.zig b/src/layout/VStack.zig index 40d70f5..d5b1a2b 100644 --- a/src/layout/VStack.zig +++ b/src/layout/VStack.zig @@ -25,10 +25,7 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { const Events = std.ArrayList(Event); return struct { // 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, - element_rows: u16 = undefined, elements: Elements = undefined, events: Events = undefined, @@ -87,12 +84,12 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type { size.rows, }); const len: u16 = @truncate(this.elements.items.len); - this.element_rows = @divTrunc(size.rows, len); - var overflow = this.size.rows % len; + const element_rows = @divTrunc(size.rows, len); + var overflow = size.rows % len; var offset: u16 = 0; // adjust size according to the containing elements for (this.elements.items) |*element| { - var rows = this.element_rows; + var rows = element_rows; if (overflow > 0) { overflow -|= 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 { - // FIX: renderer should clear only what is going to change! (i.e. the 'active' widget / layout) for (this.elements.items) |*element| { switch (element.*) { .layout => |*layout| { diff --git a/src/main.zig b/src/main.zig index aef550e..230393e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,7 +3,7 @@ const zterm = @import("zterm"); const App = zterm.App( union(enum) {}, - zterm.Renderer.Plain, + zterm.Renderer.Direct, true, ); const Key = zterm.Key; @@ -31,7 +31,7 @@ pub fn main() !void { // -> size hint how much should it use? var layout = Layout.createFrom(layout: { - var vstack = Layout.VStack.init(allocator, .{ + var vstack = Layout.HStack.init(allocator, .{ Widget.createFrom(blk: { const file = try std.fs.cwd().openFile("./src/app.zig", .{}); defer file.close(); @@ -59,6 +59,7 @@ pub fn main() !void { // App.Event loop while (true) { const event = app.nextEvent(); + log.debug("received event: {s}", .{@tagName(event)}); switch (event) { .quit => break, diff --git a/src/render.zig b/src/render.zig index 59255d9..6b03e7c 100644 --- a/src/render.zig +++ b/src/render.zig @@ -6,16 +6,17 @@ const Contents = std.ArrayList(u8); const Position = terminal.Position; const Size = terminal.Size; -pub fn Buffered(comptime fullscreen: bool) type { +pub fn Buffered(comptime _: bool) type { return struct { refresh: bool = false, size: terminal.Size = undefined, - screen: Contents = undefined, + next_frame: Contents = undefined, + frame: Contents = undefined, pub fn init(allocator: std.mem.Allocator) @This() { return .{ - .fullscreen = fullscreen, - .screen = Contents.init(allocator), + .next_frame = 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 { pub fn clear(this: @This(), size: Size) !void { _ = 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; - _ = size; - // FIXME: this should respect the given `size` - try terminal.setCursorPosition(anchor); - _ = try terminal.write(contents.items); + try terminal.setCursorPosition(size.anchor); + var row: u16 = 0; + var idx: usize = 0; + 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..]); + } } }; } diff --git a/src/widget/RawText.zig b/src/widget/RawText.zig index 5833849..9cb51f7 100644 --- a/src/widget/RawText.zig +++ b/src/widget/RawText.zig @@ -78,20 +78,21 @@ 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); - // TODO: render `this.contents` 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 { // more rows than we can display const i = this.line_index.items[this.line]; const e = this.size.rows + this.line; 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) - // FIX: what about files which do not end with a newline? - const x = this.line_index.items[e] - 1; - _ = try terminal.write(this.contents.items[i..x]); + const x = this.line_index.items[e]; + log.debug("render: {s}", .{this.contents.items[i..x]}); + try renderer.render(this.size, this.contents.items[i..x]); } } };