add(sizing): grow configuration
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m14s
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:
@@ -61,7 +61,9 @@ pub fn main() !void {
|
||||
.padding = .vertical(2),
|
||||
.direction = .vertical,
|
||||
},
|
||||
.size = .{ .y = 90 },
|
||||
.size = .{
|
||||
.dim = .{ .y = 90 },
|
||||
},
|
||||
}, .{});
|
||||
try box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .light_green },
|
||||
@@ -91,11 +93,15 @@ pub fn main() !void {
|
||||
.color = .light_blue,
|
||||
.sides = .all,
|
||||
},
|
||||
.size = .{ .x = 100 },
|
||||
.size = .{
|
||||
.dim = .{ .x = 100 },
|
||||
},
|
||||
}, .{}));
|
||||
try container.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .blue },
|
||||
.size = .{ .x = 30 },
|
||||
.size = .{
|
||||
.dim = .{ .x = 30 },
|
||||
},
|
||||
}, .{}));
|
||||
defer container.deinit(); // also de-initializes the children
|
||||
|
||||
|
||||
@@ -98,15 +98,21 @@ pub fn main() !void {
|
||||
}, .{});
|
||||
try top_box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .light_green },
|
||||
.size = .{ .y = 30 },
|
||||
.size = .{
|
||||
.dim = .{ .y = 30 },
|
||||
},
|
||||
}, .{}));
|
||||
try top_box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .light_green },
|
||||
.size = .{ .y = 5 },
|
||||
.size = .{
|
||||
.dim = .{ .y = 5 },
|
||||
},
|
||||
}, element));
|
||||
try top_box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .light_green },
|
||||
.size = .{ .y = 2 },
|
||||
.size = .{
|
||||
.dim = .{ .y = 2 },
|
||||
},
|
||||
}, .{}));
|
||||
defer top_box.deinit();
|
||||
|
||||
@@ -123,6 +129,9 @@ pub fn main() !void {
|
||||
.direction = .vertical,
|
||||
.padding = .vertical(1),
|
||||
},
|
||||
.size = .{
|
||||
.dim = .{ .y = 30 },
|
||||
},
|
||||
}, .{});
|
||||
try bottom_box.append(try App.Container.init(allocator, .{
|
||||
.rectangle = .{ .fill = .grey },
|
||||
|
||||
@@ -64,7 +64,7 @@ pub fn main() !void {
|
||||
if (comptime field.value == 0) continue; // zterm.Color.default == 0 -> skip
|
||||
try box.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = @enumFromInt(field.value) } }, .{}));
|
||||
}
|
||||
var scrollable: App.Scrollable = .init(box, .{ .x = 3 * std.meta.fields(zterm.Color).len }); // ensure enough columns to render all colors -> scrollable otherwise
|
||||
var scrollable: App.Scrollable = .{ .container = box };
|
||||
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
||||
|
||||
try app.start();
|
||||
|
||||
@@ -114,10 +114,7 @@ pub fn main() !void {
|
||||
}, text_styles.element());
|
||||
defer box.deinit();
|
||||
|
||||
var scrollable: App.Scrollable = .init(box, .{
|
||||
.x = std.meta.fields(zterm.Style.Emphasis).len * TextStyles.text.len,
|
||||
.y = (std.meta.fields(zterm.Color).len - 1) * (std.meta.fields(zterm.Color).len - 2),
|
||||
}); // ensure enough rows and/or columns to render all text styles -> scrollable otherwise
|
||||
var scrollable: App.Scrollable = .{ .container = box };
|
||||
try container.append(try App.Container.init(allocator, .{}, scrollable.element()));
|
||||
|
||||
try app.start();
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -94,8 +94,8 @@ pub fn Scrollable(Event: type) type {
|
||||
this.size = size;
|
||||
|
||||
// TODO scrollbar space - depending on configuration and only if necessary?
|
||||
this.container_size = Point.max(size, this.container.fit_resize());
|
||||
this.container.resize(.{}, this.container_size);
|
||||
this.container.resize(.{}, this.size);
|
||||
this.container_size = this.container.size;
|
||||
}
|
||||
|
||||
fn handle(ctx: *anyopaque, event: Event) !void {
|
||||
@@ -206,7 +206,9 @@ test "scrollable vertical" {
|
||||
.direction = .vertical,
|
||||
.padding = .all(1),
|
||||
},
|
||||
.size = .{ .y = size.y + 15 },
|
||||
.size = .{
|
||||
.dim = .{ .y = size.y + 15 },
|
||||
},
|
||||
}, .{});
|
||||
try box.append(try .init(allocator, .{
|
||||
.rectangle = .{ .fill = .grey },
|
||||
@@ -281,7 +283,9 @@ test "scrollable horizontal" {
|
||||
.direction = .horizontal,
|
||||
.padding = .all(1),
|
||||
},
|
||||
.size = .{ .x = size.x + 15 },
|
||||
.size = .{
|
||||
.dim = .{ .x = size.x + 15 },
|
||||
},
|
||||
}, .{});
|
||||
try box.append(try .init(allocator, .{
|
||||
.rectangle = .{ .fill = .grey },
|
||||
|
||||
Reference in New Issue
Block a user