add(sizing): grow configuration
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m14s

Currently the 'grid' and 'mixed' examples are not working yet.
This commit is contained in:
2025-03-05 22:53:28 +01:00
parent 9ec335cad8
commit e3551fa624
6 changed files with 121 additions and 52 deletions

View File

@@ -352,12 +352,15 @@ pub const Layout = packed struct {
};
const gap: u16 = (this.gap + 1) / 2;
log.debug("origin: {any}, size: {any}", .{ origin, size });
for (0..children.len - 1) |idx| {
const child = children[idx];
const anchor = switch (this.direction) {
.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),
.horizontal => (@as(usize, child.origin.y) * @as(usize, size.x)) + @as(usize, child.origin.x) + @as(usize, child.size.x) + gap,
.vertical => ((@as(usize, child.origin.y) + @as(usize, child.size.y) + gap) * @as(usize, size.x)) + @as(usize, child.origin.x),
};
log.debug("child.origin: {any}, child.size: {any}", .{ child.origin, child.size });
log.debug("anchor: {d}", .{anchor});
switch (this.direction) {
.horizontal => for (0..child.size.y) |row| {
@@ -553,6 +556,17 @@ pub const Layout = packed struct {
}
};
/// Sizing options which should be used by the `Container`
pub const Size = packed struct {
dim: Point = .{},
grow: enum(u2) {
both,
fixed,
vertical,
horizontal,
} = .both,
};
pub fn Container(comptime Event: type) type {
if (!isTaggedUnion(Event)) @compileError("Provided user event `Event` for `Container(comptime Event: type)`");
@@ -572,10 +586,7 @@ pub fn Container(comptime Event: type) type {
border: Border = .{},
rectangle: Rectangle = .{},
layout: Layout = .{},
/// size which should be used by the `Container`
size: Point = .{},
/// should size grow to the available space?
grow: bool = true,
size: Size = .{},
};
pub fn init(
@@ -609,7 +620,7 @@ pub fn Container(comptime Event: type) type {
this.origin = origin;
this.element.reposition(origin);
var offset: Point = Point.add(origin, .{
var offset = origin.add(.{
.x = layout.padding.left,
.y = layout.padding.top,
});
@@ -633,7 +644,8 @@ pub fn Container(comptime Event: type) type {
}
}
pub fn fit_resize(this: *@This()) Point {
/// Resize all fit sized `Containers` to the necessary size required by its child elements.
fn fit_resize(this: *@This()) Point {
// NOTE this is supposed to be a simple and easy to understand algorithm, there are currently no optimizations done
const layout = this.properties.layout;
var size: Point = switch (layout.direction) {
@@ -647,8 +659,10 @@ pub fn Container(comptime Event: type) type {
};
const sides = this.properties.border.sides;
if (sides.right) size.x -|= 1;
if (sides.bottom) size.y -|= 1;
if (sides.left) size.x += 1;
if (sides.right) size.x += 1;
if (sides.top) size.y += 1;
if (sides.bottom) size.y += 1;
if (layout.separator.enabled) switch (layout.direction) {
.horizontal => size.x += @as(u16, @truncate(this.elements.items.len -| 1)),
@@ -670,44 +684,55 @@ pub fn Container(comptime Event: type) type {
}
// assign currently calculated size
this.size = Point.max(size, this.properties.size);
this.size = switch (this.properties.size.grow) {
.both => Point.max(size, this.properties.size.dim),
.fixed => this.properties.size.dim,
.horizontal => .{
.x = @max(size.x, this.properties.size.dim.x),
.y = size.y,
},
.vertical => .{
.x = size.x,
.y = @max(size.y, this.properties.size.dim.y),
},
};
return this.size;
}
// growable implicitly requires the root `Container` to have a set a size property to the size of the available terminal screen
fn grow_resize(this: *@This()) void {
fn grow_resize(this: *@This(), max_size: Point) void {
const layout = this.properties.layout;
var remainder = switch (layout.direction) {
.horizontal => this.size.x -| (layout.padding.left + layout.padding.right),
.vertical => this.size.y -| (layout.padding.top + layout.padding.bottom),
.horizontal => max_size.x -| (layout.padding.left + layout.padding.right),
.vertical => max_size.y -| (layout.padding.top + layout.padding.bottom),
};
remainder -|= layout.gap * @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) {
.horizontal => this.size.y -| (layout.padding.top + layout.padding.bottom),
.vertical => this.size.x -| (layout.padding.left + layout.padding.right),
.horizontal => max_size.y -| (layout.padding.top + layout.padding.bottom),
.vertical => max_size.x -| (layout.padding.left + layout.padding.right),
};
const sides = this.properties.border.sides;
switch (layout.direction) {
.horizontal => {
if (sides.bottom) {
if (sides.top) {
available -|= 1;
remainder -|= 1;
}
if (sides.top) {
if (sides.bottom) {
available -|= 1;
remainder -|= 1;
}
},
.vertical => {
if (sides.right) {
if (sides.left) {
available -|= 1;
remainder -|= 1;
}
if (sides.left) {
if (sides.right) {
available -|= 1;
remainder -|= 1;
}
@@ -724,18 +749,23 @@ pub fn Container(comptime Event: type) type {
var growable_children: usize = 0;
var first_growable_child: *@This() = undefined;
for (this.elements.items) |*child| {
if (child.properties.grow) {
if (child.properties.size.grow != .fixed) {
if (growable_children == 0) first_growable_child = child;
growable_children += 1;
switch (layout.direction) {
.horizontal => child.size.y = available,
.vertical => child.size.x = available,
.horizontal => if (child.properties.size.grow != .vertical) {
child.size.y = available;
},
.vertical => if (child.properties.size.grow != .horizontal) {
child.size.x = available;
},
}
}
}
while (growable_children > 0 and remainder > 0) {
var count: usize = this.elements.items.len;
while (growable_children > 0 and remainder > 0 and count > 0) : (count -|= 1) {
var smallest_size = switch (layout.direction) {
.horizontal => first_growable_child.size.x,
.vertical => first_growable_child.size.y,
@@ -744,7 +774,7 @@ pub fn Container(comptime Event: type) type {
var size_to_correct = remainder;
for (this.elements.items) |child| {
if (!child.properties.grow) continue;
if (child.properties.size.grow == .fixed) continue;
const size = switch (layout.direction) {
.horizontal => child.size.x,
@@ -771,18 +801,28 @@ pub fn Container(comptime Event: type) type {
.horizontal => child.size.x,
.vertical => child.size.y,
};
if (child.properties.grow and child_size == smallest_size) {
if (child.properties.size.grow != .fixed and child_size == smallest_size) {
switch (layout.direction) {
.horizontal => child.size.x += size_to_correct,
.vertical => child.size.y += size_to_correct,
.horizontal => if (child.properties.size.grow != .vertical) {
child.size.x += size_to_correct;
},
.vertical => if (child.properties.size.grow != .horizontal) {
child.size.y += size_to_correct;
},
}
if (overflow > 0) {
switch (layout.direction) {
.horizontal => child.size.x += 1,
.vertical => child.size.y += 1,
.horizontal => if (child.properties.size.grow != .vertical) {
child.size.x += 1;
overflow -|= 1;
remainder -|= 1;
},
.vertical => if (child.properties.size.grow != .horizontal) {
child.size.y += 1;
overflow -|= 1;
remainder -|= 1;
},
}
overflow -|= 1;
remainder -|= 1;
}
remainder -|= size_to_correct;
}
@@ -790,16 +830,28 @@ pub fn Container(comptime Event: type) type {
}
this.element.resize(this.size);
for (this.elements.items) |*child| child.grow_resize();
for (this.elements.items) |*child| child.grow_resize(this.size);
}
pub fn resize(this: *@This(), origin: Point, size: Point) void {
// NOTE assume that this function is only called for the root `Container`
this.size = size; // total screen size;
const fit_size = this.fit_resize();
this.size = size; // total screen size
_ = fit_size;
this.grow_resize();
log.debug("fit_size: {any}; size: {any}", .{ fit_size, size });
// if (fit_size.y > size.y or fit_size.x > size.x) @panic("error: cannot render in available space");
switch (this.properties.size.grow) {
.both => this.size = Point.max(size, fit_size),
.fixed => {},
.horizontal => this.size = .{
.x = @max(size.x, fit_size.x),
.y = size.y,
},
.vertical => this.size = .{
.x = size.x,
.y = @max(size.y, fit_size.y),
},
}
log.debug("this.size: {any}", .{this.size});
this.grow_resize(this.size);
this.reposition(origin);
}
@@ -822,6 +874,7 @@ pub fn Container(comptime Event: type) type {
@memset(cells, .{});
errdefer this.allocator.free(cells);
log.debug("contents: this.size: {any}", .{this.size});
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);