initial commit
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 30s

This commit is contained in:
2024-11-09 21:24:42 +01:00
parent ff58e7ef69
commit 6d389bcd4b
21 changed files with 2738 additions and 0 deletions

149
src/layout/VStack.zig Normal file
View File

@@ -0,0 +1,149 @@
//! Vertical Stacking layout for nested `Layout`s and/or `Widget`s.
//!
//! # Example
//! ...
const std = @import("std");
const terminal = @import("../terminal.zig");
const isTaggedUnion = @import("../event.zig").isTaggedUnion;
const Error = @import("../event.zig").Error;
const Key = terminal.Key;
const log = std.log.scoped(.layout_vstack);
pub fn Layout(comptime Event: type) type {
if (!isTaggedUnion(Event)) {
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
}
const Widget = @import("../widget.zig").Widget(Event);
const Lay = @import("../layout.zig").Layout(Event);
const Element = union(enum) {
layout: Lay,
widget: Widget,
};
const Elements = std.ArrayList(Element);
const Events = std.ArrayList(Event);
const Contents = std.ArrayList(u8);
return struct {
// TODO: current focused `Element`?
size: terminal.Size = undefined,
contents: Contents = undefined,
elements: Elements = undefined,
events: Events = undefined,
pub fn init(allocator: std.mem.Allocator, children: anytype) @This() {
const ArgsType = @TypeOf(children);
const args_type_info = @typeInfo(ArgsType);
if (args_type_info != .Struct) {
@compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
}
const fields_info = args_type_info.Struct.fields;
var elements = Elements.initCapacity(allocator, fields_info.len) catch @panic("OOM");
inline for (comptime fields_info) |field| {
const child = @field(children, field.name);
const ChildType = @TypeOf(child);
if (ChildType == Widget) {
elements.append(.{ .widget = child }) catch {};
continue;
}
if (ChildType == Lay) {
elements.append(.{ .layout = child }) catch {};
continue;
}
@compileError("child: " ++ field.name ++ " is not of type " ++ @typeName(Lay) ++ " or " ++ @typeName(Widget) ++ " but " ++ @typeName(ChildType));
}
return .{
.contents = Contents.init(allocator),
.elements = elements,
.events = Events.init(allocator),
};
}
pub fn deinit(this: *@This()) void {
this.events.deinit();
this.contents.deinit();
for (this.elements.items) |*element| {
switch (element.*) {
.layout => |*layout| {
layout.deinit();
},
.widget => |*widget| {
widget.deinit();
},
}
}
this.elements.deinit();
}
pub fn handle(this: *@This(), event: Event) !*Events {
this.events.clearRetainingCapacity();
// order is important
switch (event) {
.resize => |size| {
this.size = size;
log.debug("Using size: {{ .cols = {d}, .rows = {d} }}", .{ size.cols, size.rows });
const len: u16 = @truncate(this.elements.items.len);
const rows = size.rows / len;
// adjust size according to the containing elements
for (this.elements.items) |*element| {
const sub_event: Event = .{
.resize = .{
.cols = size.cols,
.rows = rows,
},
};
switch (element.*) {
.layout => |*layout| {
const events = try layout.handle(sub_event);
try this.events.appendSlice(events.items);
},
.widget => |*widget| {
if (widget.handle(sub_event)) |e| {
try this.events.append(e);
}
},
}
}
},
else => {
for (this.elements.items) |*element| {
switch (element.*) {
.layout => |*layout| {
const events = try layout.handle(event);
try this.events.appendSlice(events.items);
},
.widget => |*widget| {
if (widget.handle(event)) |e| {
try this.events.append(e);
}
},
}
}
},
}
return &this.events;
}
pub fn content(this: *@This()) !*Contents {
this.contents.clearRetainingCapacity();
// TODO: concat contents accordingly to create a horizontal stack
for (this.elements.items, 1..) |*element, i| {
switch (element.*) {
.layout => |*layout| {
const layout_content = try layout.content();
try this.contents.appendSlice(layout_content.items);
},
.widget => |*widget| {
const widget_content = try widget.content();
try this.contents.appendSlice(widget_content);
},
}
// TODO: support clear positioning of content on the tui screen
if (i != this.elements.items.len) {
try this.contents.appendSlice("\n"); // NOTE: this assumes that the previous content fills all the provided size.rows accordingly with content, such that a newline introduces the start of the next content
}
}
return &this.contents;
}
};
}