widgets(table): add support for Slices and MultiArrayLists

- Updated the Table Widget to support Slices and MultiArrayLists (in addtion to ArrayLists) for the `data_list` parameter of `drawTable()`.
This commit is contained in:
00JCIV00 2024-07-29 21:01:35 -04:00 committed by Tim Culverhouse
parent 707c050a76
commit 9487916406
3 changed files with 71 additions and 11 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ zig-cache/
zig-out/
*.log
Session*.*vim
commit_msg

View file

@ -23,6 +23,9 @@ pub fn main() !void {
const users_buf = try alloc.dupe(User, users[0..]);
const user_list = std.ArrayList(User).fromOwnedSlice(alloc, users_buf);
defer user_list.deinit();
var user_mal = std.MultiArrayList(User){};
for (users_buf[0..]) |user| try user_mal.append(alloc, user);
defer user_mal.deinit(alloc);
var tty = try vaxis.Tty.init();
defer tty.deinit();
@ -191,7 +194,9 @@ pub fn main() !void {
event_alloc,
middle_bar,
&.{ "First", "Last", "Username", "Email", "Phone#" },
//users_buf[0..],
user_list,
//user_mal,
&demo_tbl,
);
}

View file

@ -34,25 +34,79 @@ pub const TableContext = struct {
/// Column Width
/// Note, this should be treated as Read Only. The Column Width will be calculated during `drawTable()`.
col_width: usize = 0,
col_width: ?usize = 0,
};
/// Draw a Table for the TUI.
pub fn drawTable(
/// This should be an ArenaAllocator that can be deinitialized after each event call.
/// The Allocator is only used in two cases:
/// 1. If a cell is a non-String. If the Allocator is not provided, those cells will show "[unsupported (TypeName)]".
/// 2. To show that a value is too large to fit into a cell. If the Allocator is not provided, they'll just be cutoff.
/// The Allocator is only used in three cases:
/// 1. If a cell is a non-String. (If the Allocator is not provided, those cells will show "[unsupported (TypeName)]".)
/// 2. To show that a value is too large to fit into a cell using '...'. (If the Allocator is not provided, they'll just be cutoff.)
/// 3. To copy a MultiArrayList into a normal slice. (Note, this is an expensive operation. Prefer to pass a Slice or ArrayList if possible.)
alloc: ?mem.Allocator,
/// The parent Window to draw to.
win: vaxis.Window,
/// Headers for the Table
headers: []const []const u8,
/// This must be an ArrayList.
/// This must be a Slice, ArrayList, or MultiArrayList.
/// Note, MultiArrayList support currently requires allocation.
data_list: anytype,
// The Table Context for this Table.
table_ctx: *TableContext,
) !void {
var di_is_mal = false;
const data_items = getData: {
const DataListT = @TypeOf(data_list);
const data_ti = @typeInfo(DataListT);
switch (data_ti) {
.Pointer => |ptr| {
if (ptr.size != .Slice) return error.UnsupportedTableDataType;
break :getData data_list;
},
.Struct => {
const di_fields = meta.fields(DataListT);
const al_fields = meta.fields(std.ArrayList([]const u8));
const mal_fields = meta.fields(std.MultiArrayList(struct{ a: u8 = 0, b: u32 = 0 }));
// Probably an ArrayList
const is_al = comptime if (
mem.indexOf(u8, @typeName(DataListT), "MultiArrayList") == null and
mem.indexOf(u8, @typeName(DataListT), "ArrayList") != null and
al_fields.len == di_fields.len
) isAL: {
var is = true;
for (al_fields, di_fields) |al_field, di_field|
is = is and mem.eql(u8, al_field.name, di_field.name);
break :isAL is;
} else false;
if (is_al) break :getData data_list.items;
// Probably a MultiArrayList
const is_mal = if (
mem.indexOf(u8, @typeName(DataListT), "MultiArrayList") != null and
mal_fields.len == di_fields.len
) isMAL: {
var is = true;
inline for (mal_fields, di_fields) |mal_field, di_field|
is = is and mem.eql(u8, mal_field.name, di_field.name);
break :isMAL is;
} else false;
if (!is_mal) return error.UnsupportedTableDataType;
if (alloc) |_alloc| {
di_is_mal = true;
const mal_slice = data_list.slice();
const DataT = @TypeOf(mal_slice.get(0));
var data_out_list = std.ArrayList(DataT).init(_alloc);
for (0..mal_slice.len) |idx| try data_out_list.append(mal_slice.get(idx));
break :getData try data_out_list.toOwnedSlice();
}
return error.UnsupportedTableDataType;
},
else => return error.UnsupportedTableDataType,
}
};
defer if (di_is_mal) alloc.?.free(data_items);
const table_win = win.initChild(
0,
table_ctx.y_off,
@ -87,23 +141,23 @@ pub fn drawTable(
_ = try hdr.print(seg[0..], .{ .wrap = .word });
}
const max_items = if (data_list.items.len > table_win.height -| 1) table_win.height -| 1 else data_list.items.len;
const max_items = if (data_items.len > table_win.height -| 1) table_win.height -| 1 else data_items.len;
var end = table_ctx.*.start + max_items;
if (end > data_list.items.len) end = data_list.items.len;
if (end > data_items.len) end = data_items.len;
table_ctx.*.start = tableStart: {
if (table_ctx.row == 0)
break :tableStart 0;
if (table_ctx.row < table_ctx.start)
break :tableStart table_ctx.start - (table_ctx.start - table_ctx.row);
if (table_ctx.row >= data_list.items.len - 1)
table_ctx.*.row = data_list.items.len - 1;
if (table_ctx.row >= data_items.len - 1)
table_ctx.*.row = data_items.len - 1;
if (table_ctx.row >= end)
break :tableStart table_ctx.start + (table_ctx.row - end + 1);
break :tableStart table_ctx.start;
};
end = table_ctx.*.start + max_items;
if (end > data_list.items.len) end = data_list.items.len;
for (data_list.items[table_ctx.start..end], 0..) |data, idx| {
if (end > data_items.len) end = data_items.len;
for (data_items[table_ctx.start..end], 0..) |data, idx| {
const row_bg =
if (table_ctx.active and table_ctx.start + idx == table_ctx.row) table_ctx.selected_bg else if (idx % 2 == 0) table_ctx.row_bg_1 else table_ctx.row_bg_2;