ref(event): split Size into two Points (one for the size and one for the anchor / origin)
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 39s

This commit is contained in:
2025-03-04 00:04:56 +01:00
parent 91ac6241f4
commit 591b990087
23 changed files with 477 additions and 459 deletions

View File

@@ -4,7 +4,7 @@ const isTaggedUnion = @import("event.zig").isTaggedUnion;
const Cell = @import("cell.zig");
const Color = @import("color.zig").Color;
const Size = @import("size.zig").Size;
const Point = @import("point.zig").Point;
const Style = @import("style.zig");
const Error = @import("error.zig").Error;
@@ -38,8 +38,8 @@ pub const Border = packed struct {
pub const vertical: @This() = .{ .top = true, .bottom = true };
} = .{},
pub fn contents(this: @This(), cells: []Cell, size: Size) void {
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
pub fn contents(this: @This(), cells: []Cell, size: Point) void {
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
const frame = switch (this.corners) {
.rounded => Border.rounded_border,
@@ -49,14 +49,14 @@ pub const Border = packed struct {
// render top and bottom border
if (this.sides.top or this.sides.bottom) {
for (0..size.cols) |col| {
const last_row = @as(usize, size.rows - 1) * @as(usize, size.cols);
for (0..size.x) |col| {
const last_row = @as(usize, size.y - 1) * @as(usize, size.x);
if (this.sides.left and col == 0) {
// top left corner
if (this.sides.top) cells[col].cp = frame[0];
// bottom left corner
if (this.sides.bottom) cells[last_row + col].cp = frame[4];
} else if (this.sides.right and col == size.cols - 1) {
} else if (this.sides.right and col == size.x - 1) {
// top right corner
if (this.sides.top) cells[col].cp = frame[2];
// bottom left corner
@@ -75,17 +75,17 @@ pub const Border = packed struct {
if (this.sides.left or this.sides.right) {
var start: usize = 0;
if (this.sides.top) start = 1;
var end = size.rows;
var end = size.y;
if (this.sides.bottom) end -= 1;
for (start..end) |row| {
const idx = (row * size.cols);
const idx = (row * size.x);
if (this.sides.left) {
cells[idx].cp = frame[3]; // left
cells[idx].style.fg = this.color;
}
if (this.sides.right) {
cells[idx + size.cols - 1].cp = frame[3]; // right
cells[idx + size.cols - 1].style.fg = this.color;
cells[idx + size.x - 1].cp = frame[3]; // right
cells[idx + size.x - 1].style.fg = this.color;
}
}
}
@@ -104,8 +104,8 @@ pub const Border = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/border.all.zon"));
}
@@ -122,8 +122,8 @@ pub const Border = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/border.vertical.zon"));
}
@@ -140,8 +140,8 @@ pub const Border = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/border.horizontal.zon"));
}
};
@@ -153,13 +153,13 @@ pub const Rectangle = packed struct {
/// children accordingly without removing the coloring of the `Rectangle`
fill: Color = .default,
// NOTE caller owns `Cells` slice and ensures that `cells.len == size.cols * size.rows`
pub fn contents(this: @This(), cells: []Cell, size: Size) void {
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
// NOTE caller owns `Cells` slice and ensures that `cells.len == size.x * size.y`
pub fn contents(this: @This(), cells: []Cell, size: Point) void {
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
for (0..size.rows) |row| {
for (0..size.cols) |col| {
cells[(row * size.cols) + col].style.bg = this.fill;
for (0..size.y) |row| {
for (0..size.x) |col| {
cells[(row * size.x) + col].style.bg = this.fill;
}
}
}
@@ -178,8 +178,8 @@ pub const Rectangle = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/rectangle_with_parent_fill_without_padding.zon"));
}
@@ -200,8 +200,8 @@ pub const Rectangle = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/rectangle_with_parent_padding.zon"));
}
@@ -228,8 +228,8 @@ pub const Rectangle = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/rectangle_with_padding.zon"));
}
@@ -259,8 +259,8 @@ pub const Rectangle = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/rectangle_with_gap.zon"));
}
@@ -291,8 +291,8 @@ pub const Rectangle = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/rectangle_with_separator.zon"));
}
};
@@ -341,8 +341,8 @@ pub const Layout = packed struct {
} = .line,
} = .{},
pub fn contents(this: @This(), comptime C: type, cells: []Cell, size: Size, children: []const C) void {
std.debug.assert(cells.len == @as(usize, size.cols) * @as(usize, size.rows));
pub fn contents(this: @This(), comptime C: type, cells: []Cell, origin: Point, size: Point, children: []const C) void {
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
if (this.separator.enabled and children.len > 1) {
const line_cps: [2]u21 = switch (this.separator.line) {
@@ -355,16 +355,16 @@ pub const Layout = packed struct {
for (0..children.len - 1) |idx| {
const child = children[idx];
const anchor = switch (this.direction) {
.horizontal => ((@as(usize, child.size.anchor.row) -| @as(usize, size.anchor.row)) * @as(usize, size.cols)) + @as(usize, child.size.anchor.col) + @as(usize, child.size.cols) + gap -| @as(usize, size.anchor.col),
.vertical => ((@as(usize, child.size.anchor.row) + @as(usize, child.size.rows) + gap -| @as(usize, size.anchor.row)) * @as(usize, size.cols)) + @as(usize, child.size.anchor.col) -| @as(usize, size.anchor.col),
.horizontal => ((@as(usize, child.origin.y) -| @as(usize, origin.y)) * @as(usize, size.x)) + @as(usize, child.origin.x) + @as(usize, child.size.x) + gap -| @as(usize, origin.x),
.vertical => ((@as(usize, child.origin.y) + @as(usize, child.size.y) + gap -| @as(usize, origin.y)) * @as(usize, size.x)) + @as(usize, child.origin.x) -| @as(usize, origin.x),
};
switch (this.direction) {
.horizontal => for (0..child.size.rows) |row| {
cells[anchor + row * size.cols].cp = line_cps[0];
cells[anchor + row * size.cols].style.fg = this.separator.color;
.horizontal => for (0..child.size.y) |row| {
cells[anchor + row * size.x].cp = line_cps[0];
cells[anchor + row * size.x].style.fg = this.separator.color;
},
.vertical => for (0..child.size.cols) |col| {
.vertical => for (0..child.size.x) |col| {
cells[anchor + col].cp = line_cps[1];
cells[anchor + col].style.fg = this.separator.color;
},
@@ -389,8 +389,8 @@ pub const Layout = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/separator_no_gaps.zon"));
}
@@ -411,8 +411,8 @@ pub const Layout = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/separator_no_gaps_with_padding.zon"));
}
@@ -435,8 +435,8 @@ pub const Layout = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/separator_2x_no_gaps.zon"));
}
@@ -462,8 +462,8 @@ pub const Layout = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/separator_2x_no_gaps_with_border.zon"));
}
@@ -490,8 +490,8 @@ pub const Layout = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/separator_2x_no_gaps_with_padding.zon"));
}
@@ -518,8 +518,8 @@ pub const Layout = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/separator_2x_with_gaps_with_border.zon"));
}
@@ -547,8 +547,8 @@ pub const Layout = packed struct {
defer container.deinit();
try testing.expectContainerScreen(.{
.rows = 20,
.cols = 30,
.y = 20,
.x = 30,
}, &container, @import("test/container/separator_2x_with_gaps_with_border_with_padding.zon"));
}
};
@@ -559,7 +559,8 @@ pub fn Container(comptime Event: type) type {
const Element = @import("element.zig").Element(Event);
return struct {
allocator: std.mem.Allocator,
size: Size,
origin: Point,
size: Point,
properties: Properties,
element: Element,
elements: std.ArrayList(@This()),
@@ -571,7 +572,7 @@ pub fn Container(comptime Event: type) type {
border: Border = .{},
rectangle: Rectangle = .{},
layout: Layout = .{},
fixed_size: Size = .{},
fixed_size: Point = .{},
};
pub fn init(
@@ -581,6 +582,7 @@ pub fn Container(comptime Event: type) type {
) !@This() {
return .{
.allocator = allocator,
.origin = .{},
.size = .{},
.properties = properties,
.element = element,
@@ -599,18 +601,18 @@ pub fn Container(comptime Event: type) type {
try this.elements.append(element);
}
pub fn position(this: *@This(), pos: Point) !void {
log.debug("pos: .{{ .x = {d}, .y = {d} }}", .{ pos.x, pos.y });
this.origin = pos;
}
pub fn handle(this: *@This(), event: Event) !void {
switch (event) {
.resize => |size| resize: {
log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
size.anchor.col,
size.anchor.row,
size.cols,
size.rows,
});
.size => |size| resize: {
log.debug("Event .size: {{ .x = {d}, .y = {d} }}", .{ size.x, size.y });
this.size = size;
if (this.properties.fixed_size.cols > 0 and size.cols < this.properties.fixed_size.cols) return Error.TooSmall;
if (this.properties.fixed_size.rows > 0 and size.rows < this.properties.fixed_size.rows) return Error.TooSmall;
if (this.properties.fixed_size.x > 0 and size.x < this.properties.fixed_size.x) return Error.TooSmall;
if (this.properties.fixed_size.y > 0 and size.y < this.properties.fixed_size.y) return Error.TooSmall;
try this.element.handle(event);
@@ -618,13 +620,13 @@ pub fn Container(comptime Event: type) type {
const layout = this.properties.layout;
var fixed_size_elements: u16 = 0;
var fixed_size: Size = .{};
var fixed_size: Point = .{};
for (this.elements.items) |element| {
switch (layout.direction) {
.horizontal => if (element.properties.fixed_size.cols > 0) {
.horizontal => if (element.properties.fixed_size.x > 0) {
fixed_size_elements += 1;
},
.vertical => if (element.properties.fixed_size.rows > 0) {
.vertical => if (element.properties.fixed_size.y > 0) {
fixed_size_elements += 1;
},
}
@@ -632,8 +634,8 @@ pub fn Container(comptime Event: type) type {
}
// check if the available screen is large enough
switch (layout.direction) {
.horizontal => if (fixed_size.cols > size.cols) return Error.TooSmall,
.vertical => if (fixed_size.rows > size.rows) return Error.TooSmall,
.horizontal => if (fixed_size.x > size.x) return Error.TooSmall,
.vertical => if (fixed_size.y > size.y) return Error.TooSmall,
}
const sides = this.properties.border.sides;
const padding = layout.padding;
@@ -641,28 +643,28 @@ pub fn Container(comptime Event: type) type {
if (layout.separator.enabled) gap += 1;
const len: u16 = @truncate(this.elements.items.len);
const element_cols = blk: {
var cols = size.cols - fixed_size.cols - gap * (len - 1);
if (sides.left) cols -= 1;
if (sides.right) cols -= 1;
cols -= padding.left + padding.right;
const element_x = blk: {
var x = size.x - fixed_size.x - gap * (len - 1);
if (sides.left) x -= 1;
if (sides.right) x -= 1;
x -= padding.left + padding.right;
if (fixed_size_elements == len) break :blk 0;
if (fixed_size_elements == 0) {
break :blk @divTrunc(cols, len);
break :blk @divTrunc(x, len);
} else {
break :blk @divTrunc(cols, len - fixed_size_elements);
break :blk @divTrunc(x, len - fixed_size_elements);
}
};
const element_rows = blk: {
var rows = size.rows - fixed_size.rows - gap * (len - 1);
if (sides.top) rows -= 1;
if (sides.bottom) rows -= 1;
rows -= padding.top + padding.bottom;
const element_y = blk: {
var y = size.y - fixed_size.y - gap * (len - 1);
if (sides.top) y -= 1;
if (sides.bottom) y -= 1;
y -= padding.top + padding.bottom;
if (fixed_size_elements == len) break :blk 0;
if (fixed_size_elements == 0) {
break :blk @divTrunc(rows, len);
break :blk @divTrunc(y, len);
} else {
break :blk @divTrunc(rows, len - fixed_size_elements);
break :blk @divTrunc(y, len - fixed_size_elements);
}
};
var offset: u16 = switch (layout.direction) {
@@ -671,99 +673,102 @@ pub fn Container(comptime Event: type) type {
};
var overflow = switch (layout.direction) {
.horizontal => blk: {
var cols = size.cols - fixed_size.cols - gap * (len - 1);
if (sides.left) cols -= 1;
if (sides.right) cols -= 1;
cols -= padding.left + padding.right;
var x = size.x - fixed_size.x - gap * (len - 1);
if (sides.left) x -= 1;
if (sides.right) x -= 1;
x -= padding.left + padding.right;
if (fixed_size_elements == len) break :blk 0;
if (fixed_size_elements == 0) {
break :blk cols - element_cols * len;
break :blk x - element_x * len;
} else {
break :blk cols - element_cols * (len - fixed_size_elements);
break :blk x - element_x * (len - fixed_size_elements);
}
},
.vertical => blk: {
var rows = size.rows - fixed_size.rows - gap * (len - 1);
if (sides.top) rows -= 1;
if (sides.bottom) rows -= 1;
rows -= padding.top + padding.bottom;
var y = size.y - fixed_size.y - gap * (len - 1);
if (sides.top) y -= 1;
if (sides.bottom) y -= 1;
y -= padding.top + padding.bottom;
if (fixed_size_elements == len) break :blk 0;
if (fixed_size_elements == 0) {
break :blk rows - element_rows * len;
break :blk y - element_y * len;
} else {
break :blk rows - element_rows * (len - fixed_size_elements);
break :blk y - element_y * (len - fixed_size_elements);
}
},
};
for (this.elements.items) |*element| {
var element_size: Size = undefined;
var element_size: Point = undefined;
var element_origin: Point = undefined;
switch (layout.direction) {
.horizontal => {
// TODO this should not always be the max size property!
var cols = blk: {
if (element.properties.fixed_size.cols > 0) break :blk element.properties.fixed_size.cols;
break :blk element_cols;
var x = blk: {
if (element.properties.fixed_size.x > 0) break :blk element.properties.fixed_size.x;
break :blk element_x;
};
if (overflow > 0) {
overflow -|= 1;
cols += 1;
x += 1;
}
element_origin = .{
.x = this.origin.x + offset,
.y = this.origin.y,
};
element_size = .{
.anchor = .{
.col = this.size.anchor.col + offset,
.row = this.size.anchor.row,
},
.cols = cols,
.rows = size.rows,
.x = x,
.y = size.y,
};
// border
if (sides.top) element_size.rows -= 1;
if (sides.bottom) element_size.rows -= 1;
if (sides.top) element_size.y -= 1;
if (sides.bottom) element_size.y -= 1;
// padding
element_size.anchor.row += padding.top;
element_size.rows -= padding.top + padding.bottom;
element_origin.y += padding.top;
element_size.y -= padding.top + padding.bottom;
// gap
offset += gap;
offset += cols;
offset += x;
},
.vertical => {
var rows = blk: {
if (element.properties.fixed_size.rows > 0) break :blk element.properties.fixed_size.rows;
break :blk element_rows;
var y = blk: {
if (element.properties.fixed_size.y > 0) break :blk element.properties.fixed_size.y;
break :blk element_y;
};
if (overflow > 0) {
overflow -|= 1;
rows += 1;
y += 1;
}
element_origin = .{
.x = this.origin.x,
.y = this.origin.y + offset,
};
element_size = .{
.anchor = .{
.col = this.size.anchor.col,
.row = this.size.anchor.row + offset,
},
.cols = size.cols,
.rows = rows,
.x = size.x,
.y = y,
};
// border
if (sides.left) element_size.cols -= 1;
if (sides.right) element_size.cols -= 1;
if (sides.left) element_size.x -= 1;
if (sides.right) element_size.x -= 1;
// padding
element_size.anchor.col += padding.left;
element_size.cols -= padding.left + padding.right;
element_origin.x += padding.left;
element_size.x -= padding.left + padding.right;
// gap
offset += gap;
offset += rows;
offset += y;
},
}
// border resizing
if (sides.top) element_size.anchor.row += 1;
if (sides.left) element_size.anchor.col += 1;
if (sides.top) element_origin.y += 1;
if (sides.left) element_origin.x += 1;
try element.handle(.{ .resize = element_size });
// TODO tell the element its origin
try element.position(element_origin);
try element.handle(.{ .size = element_size });
}
},
.mouse => |mouse| if (mouse.in(this.size)) {
.mouse => |mouse| if (mouse.in(this.origin, this.size)) {
try this.element.handle(event);
for (this.elements.items) |*element| try element.handle(event);
},
@@ -775,15 +780,15 @@ pub fn Container(comptime Event: type) type {
}
pub fn contents(this: *const @This()) ![]const Cell {
const cells = try this.allocator.alloc(Cell, @as(usize, this.size.cols) * @as(usize, this.size.rows));
const cells = try this.allocator.alloc(Cell, @as(usize, this.size.x) * @as(usize, this.size.y));
@memset(cells, .{});
errdefer this.allocator.free(cells);
this.properties.layout.contents(@This(), cells, this.size, this.elements.items);
this.properties.layout.contents(@This(), cells, this.origin, this.size, this.elements.items);
this.properties.border.contents(cells, this.size);
this.properties.rectangle.contents(cells, this.size);
try this.element.content(cells, this.size);
try this.element.content(cells, this.origin, this.size);
return cells;
}