From 8a7ce78aafa3082af7634d0d7cdeec371e19f8a9 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Sat, 1 Mar 2025 11:56:14 +0100 Subject: [PATCH] feat(container): introduce `fixed_size` property for fixed sizing of `Container`s Moved min_size property from `Container` to the `Scrollable` element, where it is only used anyway. --- examples/demo.zig | 8 ++++- src/container.zig | 88 +++++++++++++++++++++++++++++++---------------- src/element.zig | 21 ++++++----- 3 files changed, 79 insertions(+), 38 deletions(-) diff --git a/examples/demo.zig b/examples/demo.zig index 5c68f56..b5414d9 100644 --- a/examples/demo.zig +++ b/examples/demo.zig @@ -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? // - on the left some buttons? var box = try App.Container.init(allocator, .{ + .border = .{ + .color = .blue, + .sides = .all, + }, .layout = .{ .gap = 1, .padding = .vertical(2), }, - .min_size = .{ .cols = 50 }, }, .{}); try box.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = .light_green }, @@ -70,6 +73,7 @@ pub fn main() !void { var scrollable: App.Scrollable = .{ .container = box, + .min_size = .{ .cols = 60 }, }; var container = try App.Container.init(allocator, .{ @@ -87,9 +91,11 @@ pub fn main() !void { .color = .light_blue, .sides = .all, }, + .fixed_size = .{ .cols = 200 }, }, .{})); try container.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = .blue }, + .fixed_size = .{ .cols = 30 }, }, .{})); defer container.deinit(); // also de-initializes the children diff --git a/src/container.zig b/src/container.zig index 33cb5d4..4bb91c0 100644 --- a/src/container.zig +++ b/src/container.zig @@ -562,7 +562,7 @@ pub fn Container(comptime Event: type) type { border: Border = .{}, rectangle: Rectangle = .{}, layout: Layout = .{}, - min_size: Size = .{}, + fixed_size: Size = .{}, }; pub fn init( @@ -590,24 +590,6 @@ pub fn Container(comptime Event: type) type { 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 { switch (event) { .resize => |size| resize: { @@ -618,11 +600,32 @@ pub fn Container(comptime Event: type) type { size.rows, }); 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); if (this.elements.items.len == 0) break :resize; 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 padding = layout.padding; var gap = layout.gap; @@ -630,18 +633,28 @@ pub fn Container(comptime Event: type) type { const len: u16 = @truncate(this.elements.items.len); 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.right) cols -= 1; 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: { - var rows = size.rows - gap * (len - 1); + 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; - 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) { .horizontal => padding.left, @@ -649,18 +662,28 @@ pub fn Container(comptime Event: type) type { }; var overflow = switch (layout.direction) { .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.right) cols -= 1; 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: { - var rows = size.rows - gap * (len - 1); + 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; - 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; switch (layout.direction) { .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) { overflow -|= 1; cols += 1; @@ -692,7 +719,10 @@ pub fn Container(comptime Event: type) type { offset += cols; }, .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) { overflow -|= 1; rows += 1; diff --git a/src/element.zig b/src/element.zig index 48defca..9e23915 100644 --- a/src/element.zig +++ b/src/element.zig @@ -58,12 +58,16 @@ pub fn Scrollable(Event: type) type { /// `Size` of the actual contents where the anchor and the size is /// representing the size and location on screen. 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 /// the *size* of the `Scrollable` `Element`. container_size: Size = .{}, /// Anchor of the viewport of the scrollable `Container`. anchor: Position = .{}, - /// The actual container, that is scrollable. + /// The actual `Container`, that is scrollable. container: Container(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() { - return .{ .container = container }; + pub fn init(container: Container(Event), min_size: Size) @This() { + return .{ + .container = container, + .min_size = min_size, + }; } fn handle(ctx: *anyopaque, event: Event) !void { @@ -86,7 +93,7 @@ pub fn Scrollable(Event: type) type { .resize => |size| { this.size = size; // 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; try this.container.handle(.{ .resize = this.container_size }); }, @@ -190,7 +197,6 @@ test "scrollable vertical" { .direction = .vertical, .padding = .all(1), }, - .min_size = .{ .rows = size.rows + 15 }, }, .{}); try box.append(try .init(std.testing.allocator, .{ .rectangle = .{ .fill = .grey }, @@ -200,7 +206,7 @@ test "scrollable vertical" { }, .{})); 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, .{ .border = .{ @@ -265,7 +271,6 @@ test "scrollable horizontal" { .direction = .horizontal, .padding = .all(1), }, - .min_size = .{ .cols = size.cols + 15 }, }, .{}); try box.append(try .init(std.testing.allocator, .{ .rectangle = .{ .fill = .grey }, @@ -275,7 +280,7 @@ test "scrollable horizontal" { }, .{})); 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, .{ .border = .{