add(widget/List): initial list widget; used in tabs.zig example
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m34s

This commit is contained in:
2024-11-23 22:41:42 +01:00
parent c0c7b9f925
commit 8d68945100
3 changed files with 136 additions and 3 deletions

View File

@@ -67,9 +67,11 @@ pub fn main() !void {
.layout = Layout.createFrom(margin: {
var margin = Layout.Margin.init(allocator, .{ .margin = 10 }, .{
.widget = Widget.createFrom(blk: {
const file = try std.fs.cwd().openFile("./examples/padding.zig", .{});
defer file.close();
var widget = Widget.RawText.init(allocator, file);
var widget = Widget.List.init(allocator, .ordered, .{
&[_]Cell{.{ .content = "First entry" }},
&[_]Cell{.{ .content = "Second entry" }},
&[_]Cell{.{ .content = "Third entry" }},
});
break :blk &widget;
}),
});

View File

@@ -93,11 +93,13 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
pub const Text = @import("widget/Text.zig").Widget(Event, Renderer);
pub const RawText = @import("widget/RawText.zig").Widget(Event, Renderer);
pub const Spacer = @import("widget/Spacer.zig").Widget(Event, Renderer);
pub const List = @import("widget/List.zig").Widget(Event, Renderer);
};
// test widget implementation satisfies the interface
comptime Type.Interface.satisfiedBy(Type);
comptime Type.Interface.satisfiedBy(Type.Text);
comptime Type.Interface.satisfiedBy(Type.RawText);
comptime Type.Interface.satisfiedBy(Type.Spacer);
comptime Type.Interface.satisfiedBy(Type.List);
return Type;
}

129
src/widget/List.zig Normal file
View File

@@ -0,0 +1,129 @@
const std = @import("std");
const terminal = @import("../terminal.zig");
const isTaggedUnion = @import("../event.zig").isTaggedUnion;
const Error = @import("../event.zig").Error;
const Cell = terminal.Cell;
const Size = terminal.Size;
const log = std.log.scoped(.widget_list);
pub fn Widget(comptime Event: type, comptime Renderer: type) type {
if (!isTaggedUnion(Event)) {
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
}
const ListItems = std.ArrayList([]const Cell);
return struct {
idx: usize = 0,
config: ListType = undefined,
contents: ListItems = undefined,
size: terminal.Size = undefined,
require_render: bool = false,
const ListType = enum {
unordered,
ordered,
};
pub fn init(allocator: std.mem.Allocator, config: ListType, 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 contents = ListItems.initCapacity(allocator, fields_info.len) catch @panic("List.zig: out of memory");
inline for (comptime fields_info) |field| {
const child = @field(children, field.name);
const ChildType = @TypeOf(child);
const child_type_info = @typeInfo(ChildType);
if (child_type_info != .array and child_type_info != .pointer) {
@compileError("child: " ++ field.name ++ " is not an array of const Cell but " ++ @typeName(ChildType));
}
contents.append(child) catch {};
}
return .{
.config = config,
.contents = contents,
};
}
pub fn deinit(this: *@This()) void {
this.contents.deinit();
this.* = undefined;
}
pub fn handle(this: *@This(), event: Event) ?Event {
switch (event) {
// store the received size
.resize => |size| {
this.size = size;
this.require_render = true;
},
.key => |key| {
var require_render = true;
if (key.matches(.{ .cp = 'g' })) {
// top
this.idx = 0;
} else if (key.matches(.{ .cp = 'G' })) {
// bottom
this.idx = this.contents.items.len -| 1;
} else if (key.matches(.{ .cp = 'j' })) {
// down
if (this.idx < this.contents.items.len -| 1) {
this.idx +|= 1;
}
} else if (key.matches(.{ .cp = 'k' })) {
// up
this.idx -|= 1;
} else {
require_render = false;
}
this.require_render = require_render;
},
else => {},
}
return null;
}
pub fn render(this: *@This(), renderer: *Renderer) !void {
if (!this.require_render) {
return;
}
try renderer.clear(this.size);
var row: u16 = 0;
for (this.contents.items[this.idx..], this.idx + 1..) |content, num| {
var size: Size = .{
.anchor = .{
.col = this.size.anchor.col,
.row = this.size.anchor.row + row,
},
.rows = this.size.rows -| row,
.cols = this.size.cols,
};
switch (this.config) {
.unordered => {
try renderer.render(size, &[_]Cell{
.{ .content = "" },
});
size.anchor.col += 2;
size.cols -|= 2;
},
.ordered => {
var buf: [32]u8 = undefined;
const val = try std.fmt.bufPrint(&buf, "{d}.", .{num});
try renderer.render(size, &[_]Cell{
.{ .content = val },
});
const cols: u16 = @truncate(val.len + 1);
size.anchor.col += cols;
size.cols -|= cols;
},
}
try renderer.render(size, content);
row += 1; // NOTE: as there are no line breaks currently there will always exactly one line be written
}
this.require_render = false;
}
};
}