WIP: add Container type with corresponding Properties configuration

The configuration of the `Container` types is very much inspired by
[clay](https://github.com/nicbarker/clay).
This commit is contained in:
2025-02-01 01:05:56 +01:00
parent bdbe05c996
commit 1293cb065d
9 changed files with 361 additions and 142 deletions

View File

@@ -0,0 +1,174 @@
const std = @import("std");
const isTaggedUnion = @import("event.zig").isTaggedUnion;
const Cell = @import("cell.zig");
const Color = @import("color.zig").Color;
const Size = @import("size.zig");
const log = std.log.scoped(.container);
/// Border configuration struct
pub const Border = struct {
/// Color to use for the border
color: Color = .default,
/// Configure the corner type to be used for the border
corners: enum(u1) {
squared,
rounded,
} = .squared,
/// Configure the sides where the borders shall be rendered
sides: packed struct {
top: bool = true,
bottom: bool = true,
left: bool = true,
right: bool = true,
} = .{},
/// Configure separator borders between child element to added to the layout
separator: struct {
enabled: bool = false,
color: Color = .default,
line: enum {
line,
dotted,
// TODO: add more variations which could be used for the separator
} = .line,
} = .{},
};
/// Rectangle configuration struct
pub const Rectangle = struct {
/// `Color` to use to fill the `Rectangle` with
/// NOTE: used as background color when rendering! such that it renders the
/// children accordingly without removing the coloring of the `Rectangle`
fill: Color = .default,
/// Configure the corners of the `Rectangle`
corners: enum(u1) {
squared,
rounded,
} = .squared,
};
/// Scroll configuration struct
pub const Scroll = packed struct {
/// Enable horizontal scrolling for this element
horizontal: bool = false,
/// Enable vertical scrolling for this element
vertical: bool = false,
};
/// Layout configuration struct
pub const Layout = struct {
/// control the direction in which child elements are laid out
direction: enum(u1) { horizontal, vertical } = .horizontal,
/// Padding outside of the child elements
padding: packed struct {
top: u16 = 0,
bottom: u16 = 0,
left: u16 = 0,
right: u16 = 0,
/// Create a padding with equivalent padding in all four directions.
pub fn all(padding: u16) @This() {
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.
pub fn horizontal(padding: u16) @This() {
return .{ .left = padding, .right = padding };
}
/// Create a padding with equivalent padding in the top and bottom directions; others directions remain the default value.
pub fn vertical(padding: u16) @This() {
return .{ .top = padding, .bottom = padding };
}
} = .{},
/// Padding used in between child elements as gaps when laid out
gap: u16 = 0,
// TODO: is there a way to make x / y type copied by the compiler at comptime instead? such that this only has to be defined once?
/// Alignment of where the child elements are positioned relative to the parent container when laid out
alignment: packed struct {
x: enum(u2) { center, left, right } = .center,
y: enum(u2) { center, left, right } = .center,
} = .{},
// TODO: is there a way to make width / height type copied by the compiler at comptime instead? such that this only has to be defined once?
// NOTE: `sizing` cannot be *packed* because of the tagged unions? is this necessary -> I would need to measure the size differences
/// Sizing to be used for the width and height of this element to use
sizing: struct {
width: union(enum) { fit, grow, fixed: u16, percent: u16 } = .fit,
height: union(enum) { fit, grow, fixed: u16, percent: u16 } = .fit,
} = .{},
};
pub fn Container(comptime Event: type) type {
if (!isTaggedUnion(Event)) {
@compileError("Provided user event `Event` for `Container(comptime Event: type)`");
}
return struct {
size: Size,
properties: Properties,
elements: std.ArrayList(@This()),
/// Properties for each `Container` to configure their layout,
/// border, styling, etc. For details see the corresponding individual
/// documentation of the members of this struct accordingly.
pub const Properties = struct {
border: Border = .{},
rectangle: Rectangle = .{},
scroll: Scroll = .{},
layout: Layout = .{},
};
pub fn init(allocator: std.mem.Allocator, properties: Properties) !@This() {
return .{
.size = .{ .cols = 0, .rows = 0 },
.properties = properties,
.elements = std.ArrayList(@This()).init(allocator),
};
}
pub fn deinit(this: *@This()) void {
for (this.elements.items) |*element| {
element.deinit();
}
this.elements.deinit();
}
pub fn append(this: *@This(), element: @This()) !void {
try this.elements.append(element);
}
pub fn handle(this: *@This(), event: Event) ?Event {
switch (event) {
.init => log.debug(".init event", .{}),
.resize => |size| {
this.size = size;
for (this.elements.items) |*element| {
const element_size: Size = size;
// TODO; adjust size according to the layout of the `Container`
if (element.handle(.{ .resize = element_size })) |e| {
_ = e;
}
}
return null;
},
else => {},
}
for (this.elements.items) |*element| {
if (element.handle(event)) |e| {
// TODO: if only the top level container returns a single
// event (i.e. as a reaction to a certain other event) what
// should happen to potential other events?
_ = e;
}
}
return null;
}
pub fn contents(this: *const @This()) []const Cell {
// TODO: use the size and the corresponding contents to determine what should be show in form of a `Cell` array
_ = this;
return &[0]Cell{};
}
};
}