feat(container): introduce fixed_size property for fixed sizing of Containers
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 42s

Moved min_size property from `Container` to the `Scrollable` element,
where it is only used anyway.
This commit is contained in:
2025-03-01 11:56:14 +01:00
parent 35ebe31008
commit 8a7ce78aaf
3 changed files with 79 additions and 38 deletions

View File

@@ -51,11 +51,14 @@ pub fn main() !void {
// - some sort of chat? -> write messages and have them displayed in a scrollable array at the right hand side? // - some sort of chat? -> write messages and have them displayed in a scrollable array at the right hand side?
// - on the left some buttons? // - on the left some buttons?
var box = try App.Container.init(allocator, .{ var box = try App.Container.init(allocator, .{
.border = .{
.color = .blue,
.sides = .all,
},
.layout = .{ .layout = .{
.gap = 1, .gap = 1,
.padding = .vertical(2), .padding = .vertical(2),
}, },
.min_size = .{ .cols = 50 },
}, .{}); }, .{});
try box.append(try App.Container.init(allocator, .{ try box.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .light_green }, .rectangle = .{ .fill = .light_green },
@@ -70,6 +73,7 @@ pub fn main() !void {
var scrollable: App.Scrollable = .{ var scrollable: App.Scrollable = .{
.container = box, .container = box,
.min_size = .{ .cols = 60 },
}; };
var container = try App.Container.init(allocator, .{ var container = try App.Container.init(allocator, .{
@@ -87,9 +91,11 @@ pub fn main() !void {
.color = .light_blue, .color = .light_blue,
.sides = .all, .sides = .all,
}, },
.fixed_size = .{ .cols = 200 },
}, .{})); }, .{}));
try container.append(try App.Container.init(allocator, .{ try container.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .blue }, .rectangle = .{ .fill = .blue },
.fixed_size = .{ .cols = 30 },
}, .{})); }, .{}));
defer container.deinit(); // also de-initializes the children defer container.deinit(); // also de-initializes the children

View File

@@ -562,7 +562,7 @@ pub fn Container(comptime Event: type) type {
border: Border = .{}, border: Border = .{},
rectangle: Rectangle = .{}, rectangle: Rectangle = .{},
layout: Layout = .{}, layout: Layout = .{},
min_size: Size = .{}, fixed_size: Size = .{},
}; };
pub fn init( pub fn init(
@@ -590,24 +590,6 @@ pub fn Container(comptime Event: type) type {
try this.elements.append(element); try this.elements.append(element);
} }
pub fn minSize(this: @This()) Size {
var size: Size = .{};
const len: u16 = @truncate(this.elements.items.len);
if (len > 0) {
for (this.elements.items) |element| {
size = size.add(element.minSize());
}
var gap = this.properties.layout.gap;
if (this.properties.layout.separator.enabled) gap += 1;
switch (this.properties.layout.direction) {
.horizontal => size.cols += gap * (len - 1),
.vertical => size.rows += gap * (len - 1),
}
}
return size.max(this.properties.min_size);
}
pub fn handle(this: *@This(), event: Event) !void { pub fn handle(this: *@This(), event: Event) !void {
switch (event) { switch (event) {
.resize => |size| resize: { .resize => |size| resize: {
@@ -618,11 +600,32 @@ pub fn Container(comptime Event: type) type {
size.rows, size.rows,
}); });
this.size = size; 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;
try this.element.handle(event); try this.element.handle(event);
if (this.elements.items.len == 0) break :resize; if (this.elements.items.len == 0) break :resize;
const layout = this.properties.layout; const layout = this.properties.layout;
var fixed_size_elements: u16 = 0;
var fixed_size: Size = .{};
for (this.elements.items) |element| {
switch (layout.direction) {
.horizontal => if (element.properties.fixed_size.cols > 0) {
fixed_size_elements += 1;
},
.vertical => if (element.properties.fixed_size.rows > 0) {
fixed_size_elements += 1;
},
}
fixed_size = fixed_size.add(element.properties.fixed_size);
}
// 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,
}
const sides = this.properties.border.sides; const sides = this.properties.border.sides;
const padding = layout.padding; const padding = layout.padding;
var gap = layout.gap; var gap = layout.gap;
@@ -630,18 +633,28 @@ pub fn Container(comptime Event: type) type {
const len: u16 = @truncate(this.elements.items.len); const len: u16 = @truncate(this.elements.items.len);
const element_cols = blk: { const element_cols = blk: {
var cols = size.cols - gap * (len - 1); var cols = size.cols - fixed_size.cols - gap * (len - 1);
if (sides.left) cols -= 1; if (sides.left) cols -= 1;
if (sides.right) cols -= 1; if (sides.right) cols -= 1;
cols -= padding.left + padding.right; cols -= padding.left + padding.right;
break :blk @divTrunc(cols, len); if (fixed_size_elements == len) break :blk 0;
if (fixed_size_elements == 0) {
break :blk @divTrunc(cols, len);
} else {
break :blk @divTrunc(cols, len - fixed_size_elements);
}
}; };
const element_rows = blk: { const element_rows = blk: {
var rows = size.rows - gap * (len - 1); var rows = size.rows - fixed_size.rows - gap * (len - 1);
if (sides.top) rows -= 1; if (sides.top) rows -= 1;
if (sides.bottom) rows -= 1; if (sides.bottom) rows -= 1;
rows -= padding.top + padding.bottom; rows -= padding.top + padding.bottom;
break :blk @divTrunc(rows, len); if (fixed_size_elements == len) break :blk 0;
if (fixed_size_elements == 0) {
break :blk @divTrunc(rows, len);
} else {
break :blk @divTrunc(rows, len - fixed_size_elements);
}
}; };
var offset: u16 = switch (layout.direction) { var offset: u16 = switch (layout.direction) {
.horizontal => padding.left, .horizontal => padding.left,
@@ -649,18 +662,28 @@ pub fn Container(comptime Event: type) type {
}; };
var overflow = switch (layout.direction) { var overflow = switch (layout.direction) {
.horizontal => blk: { .horizontal => blk: {
var cols = size.cols - gap * (len - 1); var cols = size.cols - fixed_size.cols - gap * (len - 1);
if (sides.left) cols -= 1; if (sides.left) cols -= 1;
if (sides.right) cols -= 1; if (sides.right) cols -= 1;
cols -= padding.left + padding.right; cols -= padding.left + padding.right;
break :blk cols - element_cols * len; if (fixed_size_elements == len) break :blk 0;
if (fixed_size_elements == 0) {
break :blk cols - element_cols * len;
} else {
break :blk cols - element_cols * (len - fixed_size_elements);
}
}, },
.vertical => blk: { .vertical => blk: {
var rows = size.rows - gap * (len - 1); var rows = size.rows - fixed_size.rows - gap * (len - 1);
if (sides.top) rows -= 1; if (sides.top) rows -= 1;
if (sides.bottom) rows -= 1; if (sides.bottom) rows -= 1;
rows -= padding.top + padding.bottom; rows -= padding.top + padding.bottom;
break :blk rows - element_rows * len; if (fixed_size_elements == len) break :blk 0;
if (fixed_size_elements == 0) {
break :blk rows - element_rows * len;
} else {
break :blk rows - element_rows * (len - fixed_size_elements);
}
}, },
}; };
@@ -668,7 +691,11 @@ pub fn Container(comptime Event: type) type {
var element_size: Size = undefined; var element_size: Size = undefined;
switch (layout.direction) { switch (layout.direction) {
.horizontal => { .horizontal => {
var cols = element_cols; // 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;
};
if (overflow > 0) { if (overflow > 0) {
overflow -|= 1; overflow -|= 1;
cols += 1; cols += 1;
@@ -692,7 +719,10 @@ pub fn Container(comptime Event: type) type {
offset += cols; offset += cols;
}, },
.vertical => { .vertical => {
var rows = element_rows; var rows = blk: {
if (element.properties.fixed_size.rows > 0) break :blk element.properties.fixed_size.rows;
break :blk element_rows;
};
if (overflow > 0) { if (overflow > 0) {
overflow -|= 1; overflow -|= 1;
rows += 1; rows += 1;

View File

@@ -58,12 +58,16 @@ pub fn Scrollable(Event: type) type {
/// `Size` of the actual contents where the anchor and the size is /// `Size` of the actual contents where the anchor and the size is
/// representing the size and location on screen. /// representing the size and location on screen.
size: Size = .{}, size: Size = .{},
/// Minimal `Size` of the scrollable `Container` to be used. If the
/// actual scrollable container's size is larger it will be used instead
/// (no scrolling will be necessary for that screen size).
min_size: Size = .{},
/// `Size` of the `Container` content that is scrollable and mapped to /// `Size` of the `Container` content that is scrollable and mapped to
/// the *size* of the `Scrollable` `Element`. /// the *size* of the `Scrollable` `Element`.
container_size: Size = .{}, container_size: Size = .{},
/// Anchor of the viewport of the scrollable `Container`. /// Anchor of the viewport of the scrollable `Container`.
anchor: Position = .{}, anchor: Position = .{},
/// The actual container, that is scrollable. /// The actual `Container`, that is scrollable.
container: Container(Event), container: Container(Event),
pub fn element(this: *@This()) Element(Event) { pub fn element(this: *@This()) Element(Event) {
@@ -76,8 +80,11 @@ pub fn Scrollable(Event: type) type {
}; };
} }
pub fn init(container: Container(Event)) @This() { pub fn init(container: Container(Event), min_size: Size) @This() {
return .{ .container = container }; return .{
.container = container,
.min_size = min_size,
};
} }
fn handle(ctx: *anyopaque, event: Event) !void { fn handle(ctx: *anyopaque, event: Event) !void {
@@ -86,7 +93,7 @@ pub fn Scrollable(Event: type) type {
.resize => |size| { .resize => |size| {
this.size = size; this.size = size;
// TODO: scrollbar space - depending on configuration and only if necessary? // TODO: scrollbar space - depending on configuration and only if necessary?
this.container_size = size.max(this.container.minSize()); this.container_size = size.max(this.min_size);
this.container_size.anchor = size.anchor; this.container_size.anchor = size.anchor;
try this.container.handle(.{ .resize = this.container_size }); try this.container.handle(.{ .resize = this.container_size });
}, },
@@ -190,7 +197,6 @@ test "scrollable vertical" {
.direction = .vertical, .direction = .vertical,
.padding = .all(1), .padding = .all(1),
}, },
.min_size = .{ .rows = size.rows + 15 },
}, .{}); }, .{});
try box.append(try .init(std.testing.allocator, .{ try box.append(try .init(std.testing.allocator, .{
.rectangle = .{ .fill = .grey }, .rectangle = .{ .fill = .grey },
@@ -200,7 +206,7 @@ test "scrollable vertical" {
}, .{})); }, .{}));
defer box.deinit(); defer box.deinit();
var scrollable: Scrollable(event.SystemEvent) = .init(box); var scrollable: Scrollable(event.SystemEvent) = .init(box, .{ .rows = size.rows + 15 });
var container: Container(event.SystemEvent) = try .init(std.testing.allocator, .{ var container: Container(event.SystemEvent) = try .init(std.testing.allocator, .{
.border = .{ .border = .{
@@ -265,7 +271,6 @@ test "scrollable horizontal" {
.direction = .horizontal, .direction = .horizontal,
.padding = .all(1), .padding = .all(1),
}, },
.min_size = .{ .cols = size.cols + 15 },
}, .{}); }, .{});
try box.append(try .init(std.testing.allocator, .{ try box.append(try .init(std.testing.allocator, .{
.rectangle = .{ .fill = .grey }, .rectangle = .{ .fill = .grey },
@@ -275,7 +280,7 @@ test "scrollable horizontal" {
}, .{})); }, .{}));
defer box.deinit(); defer box.deinit();
var scrollable: Scrollable(event.SystemEvent) = .init(box); var scrollable: Scrollable(event.SystemEvent) = .init(box, .{ .cols = size.cols + 15 });
var container: Container(event.SystemEvent) = try .init(std.testing.allocator, .{ var container: Container(event.SystemEvent) = try .init(std.testing.allocator, .{
.border = .{ .border = .{