diff --git a/.gitignore b/.gitignore index 2cfbcc2..2fbf352 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ zig-cache/ zig-out/ *.log Session*.*vim +commit_msg diff --git a/examples/table.zig b/examples/table.zig index 930ca1e..23e650d 100644 --- a/examples/table.zig +++ b/examples/table.zig @@ -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, ); } diff --git a/src/widgets/Table.zig b/src/widgets/Table.zig index 530ddca..a8dca40 100644 --- a/src/widgets/Table.zig +++ b/src/widgets/Table.zig @@ -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;