feat(container): negative layout padding
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Has been cancelled

Negative paddings use the current size to calculate the padding from
the opposite orientation. For a given dimension (horizontal or vertical)
if the size is `30` a padding of `5` would be equivalent to a padding
of `-25`. This enables to describe the size of the container using
the padding property of the `Layout` and gives users more freedom to
describe different layouts.
This commit is contained in:
2025-07-11 22:33:32 +02:00
parent b401b5ece8
commit 66b3a77805

View File

@@ -195,6 +195,33 @@ pub const Rectangle = packed struct {
}, &container, @import("test/container/rectangle_with_parent_padding.zon")); }, &container, @import("test/container/rectangle_with_parent_padding.zon"));
} }
test "fill color padding to show parent fill (negative padding)" {
const event = @import("event.zig");
const testing = @import("testing.zig");
var container: Container(event.SystemEvent) = try .init(std.testing.allocator, .{
.layout = .{
.padding = .{
.top = -18,
.bottom = -18,
.left = -28,
.right = -28,
},
},
.rectangle = .{ .fill = .green },
}, .{});
try container.append(try .init(std.testing.allocator, .{
.rectangle = .{ .fill = .white },
}, .{}));
try container.append(try .init(std.testing.allocator, .{}, .{}));
defer container.deinit();
try testing.expectContainerScreen(.{
.y = 20,
.x = 30,
}, &container, @import("test/container/rectangle_with_parent_padding.zon"));
}
test "fill color spacer with padding" { test "fill color spacer with padding" {
const event = @import("event.zig"); const event = @import("event.zig");
const testing = @import("testing.zig"); const testing = @import("testing.zig");
@@ -293,23 +320,23 @@ pub const Layout = packed struct {
direction: enum(u1) { horizontal, vertical } = .horizontal, direction: enum(u1) { horizontal, vertical } = .horizontal,
/// Padding outside of the child elements /// Padding outside of the child elements
padding: packed struct { padding: packed struct {
top: u16 = 0, top: i16 = 0,
bottom: u16 = 0, bottom: i16 = 0,
left: u16 = 0, left: i16 = 0,
right: u16 = 0, right: i16 = 0,
/// Create a padding with equivalent padding in all four directions. /// Create a padding with equivalent padding in all four directions.
pub fn all(padding: u16) @This() { pub fn all(padding: i16) @This() {
return .{ .top = padding, .bottom = padding, .left = padding, .right = padding }; return .{ .top = padding, .bottom = padding, .left = padding, .right = padding };
} }
/// Create a padding with equivalent padding in the left and right directions; others directions remain the default value. /// Create a padding with equivalent padding in the left and right directions; others directions remain the default value.
pub fn horizontal(padding: u16) @This() { pub fn horizontal(padding: i16) @This() {
return .{ .left = padding, .right = padding }; return .{ .left = padding, .right = padding };
} }
/// Create a padding with equivalent padding in the top and bottom directions; others directions remain the default value. /// Create a padding with equivalent padding in the top and bottom directions; others directions remain the default value.
pub fn vertical(padding: u16) @This() { pub fn vertical(padding: i16) @This() {
return .{ .top = padding, .bottom = padding }; return .{ .top = padding, .bottom = padding };
} }
} = .{}, } = .{},
@@ -326,6 +353,11 @@ pub const Layout = packed struct {
} = .line, } = .line,
} = .{}, } = .{},
/// Calculate the absolute offset for the provided `padding` if it is negative to get the absolute padding for the given `size`.
pub fn getAbsolutePadding(padding: i16, size: u16) u16 {
return if (padding >= 0) @intCast(padding) else size -| @as(u16, @intCast(-padding));
}
pub fn content(this: @This(), comptime C: type, cells: []Cell, origin: Point, size: Point, children: []const C) void { pub fn content(this: @This(), comptime C: type, cells: []Cell, origin: Point, size: Point, children: []const C) void {
assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
@@ -609,8 +641,8 @@ pub fn Container(comptime Event: type) type {
this.element.reposition(origin); this.element.reposition(origin);
var offset = origin.add(.{ var offset = origin.add(.{
.x = layout.padding.left, .x = Layout.getAbsolutePadding(layout.padding.left, this.size.x),
.y = layout.padding.top, .y = Layout.getAbsolutePadding(layout.padding.top, this.size.y),
}); });
const sides = this.properties.border.sides; const sides = this.properties.border.sides;
@@ -642,8 +674,8 @@ pub fn Container(comptime Event: type) type {
}; };
if (this.elements.items.len > 0) switch (layout.direction) { if (this.elements.items.len > 0) switch (layout.direction) {
.horizontal => size.x += layout.padding.left + layout.padding.right, .horizontal => size.x += Layout.getAbsolutePadding(layout.padding.left, this.size.x) + Layout.getAbsolutePadding(layout.padding.right, this.size.x),
.vertical => size.y += layout.padding.top + layout.padding.bottom, .vertical => size.y += Layout.getAbsolutePadding(layout.padding.top, this.size.y) + Layout.getAbsolutePadding(layout.padding.bottom, this.size.y),
}; };
const sides = this.properties.border.sides; const sides = this.properties.border.sides;
@@ -691,16 +723,16 @@ pub fn Container(comptime Event: type) type {
fn grow_resize(this: *@This(), max_size: Point) void { fn grow_resize(this: *@This(), max_size: Point) void {
const layout = this.properties.layout; const layout = this.properties.layout;
var remainder = switch (layout.direction) { var remainder = switch (layout.direction) {
.horizontal => max_size.x -| (layout.padding.left + layout.padding.right), .horizontal => max_size.x -| (Layout.getAbsolutePadding(layout.padding.left, this.size.x) + Layout.getAbsolutePadding(layout.padding.right, this.size.x)),
.vertical => max_size.y -| (layout.padding.top + layout.padding.bottom), .vertical => max_size.y -| (Layout.getAbsolutePadding(layout.padding.top, this.size.y) + Layout.getAbsolutePadding(layout.padding.bottom, this.size.y)),
}; };
remainder -|= layout.gap * @as(u16, @truncate(this.elements.items.len -| 1)); remainder -|= layout.gap * @as(u16, @truncate(this.elements.items.len -| 1));
if (layout.separator.enabled) remainder -|= @as(u16, @truncate(this.elements.items.len -| 1)); if (layout.separator.enabled) remainder -|= @as(u16, @truncate(this.elements.items.len -| 1));
var available = switch (layout.direction) { var available = switch (layout.direction) {
.horizontal => max_size.y -| (layout.padding.top + layout.padding.bottom), .horizontal => max_size.y -| (Layout.getAbsolutePadding(layout.padding.top, this.size.y) + Layout.getAbsolutePadding(layout.padding.bottom, this.size.y)),
.vertical => max_size.x -| (layout.padding.left + layout.padding.right), .vertical => max_size.x -| (Layout.getAbsolutePadding(layout.padding.left, this.size.x) + Layout.getAbsolutePadding(layout.padding.right, this.size.x)),
}; };
const sides = this.properties.border.sides; const sides = this.properties.border.sides;
@@ -836,6 +868,7 @@ pub fn Container(comptime Event: type) type {
pub fn resize(this: *@This(), size: Point) void { pub fn resize(this: *@This(), size: Point) void {
// NOTE assume that this function is only called for the root `Container` // NOTE assume that this function is only called for the root `Container`
this.size = size;
const fit_size = this.fit_resize(); const fit_size = this.fit_resize();
// if (fit_size.y > size.y or fit_size.x > size.x) @panic("error: cannot render in available space"); // if (fit_size.y > size.y or fit_size.x > size.x) @panic("error: cannot render in available space");
switch (this.properties.size.grow) { switch (this.properties.size.grow) {