From 8586a055081c4b26c26eef3ca48f736f25a8739c Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Thu, 6 Feb 2025 20:10:22 +0100 Subject: [PATCH] mod: fix rendering resizing; layout placement of child elements for vertical and horizontal directions Work in progress for separator configuration of border properties --- examples/container.zig | 13 ++++- src/container.zig | 117 ++++++++++++++++++++++++++++++++++------- src/render.zig | 44 ++++++++-------- 3 files changed, 131 insertions(+), 43 deletions(-) diff --git a/examples/container.zig b/examples/container.zig index 8cc75d4..728f744 100644 --- a/examples/container.zig +++ b/examples/container.zig @@ -26,13 +26,22 @@ pub fn main() !void { .border = .{ .color = .green, .corners = .rounded, - .separator = .{ .enabled = true }, + .separator = .{ .enabled = false }, }, - .layout = .{ .gap = 40 }, + .layout = .{ .gap = 10, .direction = .horizontal }, }); try container.append(try App.Container.init(allocator, .{ .border = .{ .color = .light_green, .corners = .squared }, })); + try container.append(try App.Container.init(allocator, .{ + .border = .{ .color = .light_green, .corners = .squared }, + })); + try container.append(try App.Container.init(allocator, .{ + .border = .{ .color = .light_green, .corners = .squared }, + })); + try container.append(try App.Container.init(allocator, .{ + .border = .{ .color = .light_green, .corners = .squared }, + })); try container.append(try App.Container.init(allocator, .{ .border = .{ .color = .light_red, .corners = .squared }, })); diff --git a/src/container.zig b/src/container.zig index 4f98c9f..c45fe9d 100644 --- a/src/container.zig +++ b/src/container.zig @@ -39,8 +39,7 @@ pub const Border = struct { } = .{}, // NOTE: caller owns `cells` slice and ensures that `cells.len == size.cols * size.rows` - pub fn contents(this: @This(), cells: []Cell, size: Size) void { - log.debug("Content of Border: {any}", .{this}); + pub fn contents(this: @This(), cells: []Cell, size: Size, layout: Layout, len: u16) void { std.debug.assert(cells.len == size.cols * size.rows); const frame = switch (this.corners) { @@ -48,7 +47,7 @@ pub const Border = struct { .squared => Border.squared_border, }; std.debug.assert(frame.len == 6); - // TODO: respect color configuration + // TODO: respect sides configuration // render top and bottom border for (0..size.cols) |col| { @@ -69,7 +68,6 @@ pub const Border = struct { // bottom side cells[last_row + col].cp = frame[1]; } - // TODO: fix rendering of styling? cells[col].style.fg = this.color; cells[last_row + col].style.fg = this.color; } @@ -81,6 +79,78 @@ pub const Border = struct { cells[idx + size.cols - 1].cp = frame[3]; // right cells[idx + size.cols - 1].style.fg = this.color; } + + // TODO: the separator should be rendered regardless of the gap + if (this.separator.enabled) { + // calculate where the separator would need to be + const element_cols = blk: { + var cols = size.cols - layout.gap * (len - 1); + if (this.sides.left) cols -= 1; + if (this.sides.right) cols -= 1; + break :blk @divTrunc(cols, len); + }; + const element_rows = blk: { + var rows = size.rows - layout.gap * (len - 1); + if (this.sides.top) rows -= 1; + if (this.sides.bottom) rows -= 1; + break :blk @divTrunc(rows, len); + }; + const gap = blk: { + var gap = layout.gap / 2; + if (layout.gap % 2 > 0) gap += 1; + break :blk gap; + }; + var overflow = switch (layout.direction) { + .horizontal => blk: { + var cols = size.cols - layout.gap * (len - 1); + if (this.sides.left) cols -= 1; + if (this.sides.right) cols -= 1; + break :blk cols - element_cols * len; + }, + .vertical => blk: { + var rows = size.rows - layout.gap * (len - 1); + if (this.sides.top) rows -= 1; + if (this.sides.bottom) rows -= 1; + break :blk rows - element_rows * len; + }, + }; + switch (layout.direction) { + .horizontal => { + var offset: u16 = gap; + for (0..len - 1) |_| { + var cols = element_cols; + if (overflow > 0) { + overflow -|= 1; + cols += 1; + } + offset += cols; + for (1..size.rows - 1) |row| { + // TODO: support the line options + cells[row * size.cols + offset].cp = frame[3]; + cells[row * size.cols + offset].style.fg = this.separator.color; + } + offset += layout.gap; + } + }, + .vertical => { + var offset: u16 = gap; + for (0..len - 1) |_| { + var rows = element_rows; + if (overflow > 0) { + overflow -|= 1; + rows += 1; + } + offset += rows; + for (1..size.cols - 1) |col| { + // TODO: support the line options + cells[offset * size.cols + col].cp = frame[1]; + cells[offset * size.cols + col].style.fg = this.separator.color; + } + offset += layout.gap; + } + }, + } + } } }; @@ -203,8 +273,10 @@ pub fn Container(comptime Event: type) type { if (this.elements.items.len == 0) break :resize; + const separator = this.properties.border.separator; const sides = this.properties.border.sides; - const gap = this.properties.layout.gap; + var gap = this.properties.layout.gap; + if (separator.enabled) gap += 1; // the gap will be used for the rendering of the separator line const len: u16 = @truncate(this.elements.items.len); const element_cols = blk: { var cols = size.cols - gap * (len - 1); @@ -219,12 +291,27 @@ pub fn Container(comptime Event: type) type { break :blk @divTrunc(rows, len); }; var offset: u16 = 0; + var overflow = switch (this.properties.layout.direction) { + .horizontal => blk: { + var cols = size.cols - gap * (len - 1); + if (sides.left) cols -= 1; + if (sides.right) cols -= 1; + break :blk cols - element_cols * len; + }, + .vertical => blk: { + var rows = size.rows - gap * (len - 1); + if (sides.top) rows -= 1; + if (sides.bottom) rows -= 1; + break :blk rows - element_rows * len; + }, + }; + // TODO: make sure that items cannot underflow in size! + // - make their size and position still according (even if outside of the visible space!) + // - don't render them then accordingly -> avoid index out of bounce accesses! for (this.elements.items) |*element| { var element_size: Size = undefined; switch (this.properties.layout.direction) { .horizontal => { - var overflow = size.cols - element_cols * len; - // adjust size according to the containing elements var cols = element_cols; if (overflow > 0) { overflow -|= 1; @@ -238,12 +325,12 @@ pub fn Container(comptime Event: type) type { .cols = cols, .rows = size.rows, }; + if (sides.top) element_size.rows -= 1; + if (sides.bottom) element_size.rows -= 1; offset += cols; offset += gap; }, .vertical => { - var overflow = size.rows - element_rows * len; - // adjust size according to the containing elements var rows = element_rows; if (overflow > 0) { overflow -|= 1; @@ -257,6 +344,8 @@ pub fn Container(comptime Event: type) type { .cols = size.cols, .rows = rows, }; + if (sides.left) element_size.cols -= 1; + if (sides.right) element_size.cols -= 1; offset += rows; offset += gap; }, @@ -265,17 +354,9 @@ pub fn Container(comptime Event: type) type { // border resizing if (sides.top) { element_size.anchor.row += 1; - element_size.rows -|= 1; - } - if (sides.bottom) { - element_size.rows -|= 1; } if (sides.left) { element_size.anchor.col += 1; - element_size.cols -|= 1; - } - if (sides.right) { - element_size.cols -|= 1; } // TODO: adjust size according to the layout of the `Container` @@ -302,7 +383,7 @@ pub fn Container(comptime Event: type) type { // TODO: use the size and the corresponding contents to determine what should be show in form of a `Cell` array const cells = this.allocator.alloc(Cell, this.size.cols * this.size.rows) catch @panic("Container::contents: Out of memory."); @memset(cells, .{}); // reset all cells - this.properties.border.contents(cells, this.size); + this.properties.border.contents(cells, this.size, this.properties.layout, @truncate(this.elements.items.len)); return cells; } }; diff --git a/src/render.zig b/src/render.zig index 1f3b886..8efb048 100644 --- a/src/render.zig +++ b/src/render.zig @@ -42,33 +42,33 @@ pub const Buffered = struct { } pub fn resize(this: *@This(), size: Size) !void { - this.size = size; - if (!this.created) { + log.debug("renderer::resize", .{}); + defer this.size = size; + + if (this.size.cols >= size.cols or this.size.rows >= size.rows) { + if (!this.created) { + this.screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory."); + @memset(this.screen, .{}); + this.virtual_screen = this.allocator.alloc(Cell, size.cols * size.rows) 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, size.cols * size.rows) catch @panic("render.zig: Out of memory."); - @memset(this.screen, Cell{}); + @memset(this.screen, .{}); + this.allocator.free(this.virtual_screen); 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."); + @memset(this.virtual_screen, .{}); } + try this.clear(); } - pub fn clear(this: *@This(), size: Size) void { + /// 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 { log.debug("renderer::clear", .{}); - var vs = this.virtual_screen; - const anchor = (size.anchor.row * this.size.rows) + size.anchor.col; - // TODO: use memset to effectivly reset the coresponding cells? - for (0..size.rows) |row| { - for (0..size.cols) |col| { - vs[anchor + (row * this.size.cols) + col].reset(); - } - } + try terminal.clearScreen(); + @memset(this.screen, .{}); } /// Render provided cells at size (anchor and dimension) into the *virtual screen*. @@ -76,8 +76,6 @@ pub const Buffered = struct { const size: Size = container.size; const cells: []const Cell = container.contents(); - // log.debug("renderer:render: cells: {any} @ {any}", .{ cells, size }); - if (cells.len == 0) return; var idx: usize = 0;