227 lines
8.9 KiB
Zig
227 lines
8.9 KiB
Zig
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| resize: {
|
|
log.debug("Event .resize: {{ .anchor = {{ .col = {d}, .row = {d} }}, .cols = {d}, .rows = {d} }}", .{
|
|
size.anchor.col,
|
|
size.anchor.row,
|
|
size.cols,
|
|
size.rows,
|
|
});
|
|
this.size = size;
|
|
|
|
if (this.elements.items.len == 0)
|
|
break :resize;
|
|
|
|
const len: u16 = @truncate(this.elements.items.len);
|
|
const element_cols = @divTrunc(size.cols, len);
|
|
const element_rows = @divTrunc(size.rows, len);
|
|
var offset: u16 = 0;
|
|
for (this.elements.items) |*element| {
|
|
var element_size: Size = undefined;
|
|
switch (this.properties.layout.direction) {
|
|
.horizontal => {
|
|
var overflow = size.cols % len;
|
|
// adjust size according to the containing elements
|
|
var cols = element_cols;
|
|
if (overflow > 0) {
|
|
overflow -|= 1;
|
|
cols += 1;
|
|
}
|
|
element_size = .{
|
|
.anchor = .{
|
|
.col = size.anchor.col + offset,
|
|
.row = size.anchor.row,
|
|
},
|
|
.cols = cols,
|
|
.rows = size.rows,
|
|
};
|
|
offset += cols;
|
|
},
|
|
.vertical => {
|
|
var overflow = size.rows % len;
|
|
// adjust size according to the containing elements
|
|
var rows = element_rows;
|
|
if (overflow > 0) {
|
|
overflow -|= 1;
|
|
rows += 1;
|
|
}
|
|
element_size = .{
|
|
.anchor = .{
|
|
.col = size.anchor.col,
|
|
.row = size.anchor.row + offset,
|
|
},
|
|
.cols = size.cols,
|
|
.rows = rows,
|
|
};
|
|
offset += rows;
|
|
},
|
|
}
|
|
// 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{};
|
|
}
|
|
};
|
|
}
|