mod: fix rendering resizing; layout placement of child elements for vertical and horizontal directions
Work in progress for separator configuration of border properties
This commit is contained in:
@@ -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 },
|
||||
}));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user