From b9223dec8515560a20147adc68565886f82f931a Mon Sep 17 00:00:00 2001 From: reugenio Date: Mon, 8 Dec 2025 13:22:42 +0100 Subject: [PATCH] Add 3 new interactive demo examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New examples: - list_demo.zig: Interactive list with j/k navigation - table_demo.zig: Table with row selection and status colors - dashboard.zig: Multi-widget demo (Tabs, Gauges, Sparklines, List) Also includes: - build.zig: Added build targets for all new examples - table.zig: Fixed Cell.content default, HighlightSpacing check, ColumnPos type Run with: zig build list-demo / table-demo / dashboard 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- build.zig | 57 +++++++++ examples/dashboard.zig | 253 ++++++++++++++++++++++++++++++++++++++++ examples/list_demo.zig | 188 +++++++++++++++++++++++++++++ examples/table_demo.zig | 210 +++++++++++++++++++++++++++++++++ src/widgets/table.zig | 41 ++++--- 5 files changed, 736 insertions(+), 13 deletions(-) create mode 100644 examples/dashboard.zig create mode 100644 examples/list_demo.zig create mode 100644 examples/table_demo.zig diff --git a/build.zig b/build.zig index 4d4b5fd..f3bfd03 100644 --- a/build.zig +++ b/build.zig @@ -61,4 +61,61 @@ pub fn build(b: *std.Build) void { run_events_demo.step.dependOn(b.getInstallStep()); const events_demo_step = b.step("events-demo", "Run events demo"); events_demo_step.dependOn(&run_events_demo.step); + + // Ejemplo: list_demo + const list_demo_exe = b.addExecutable(.{ + .name = "list-demo", + .root_module = b.createModule(.{ + .root_source_file = b.path("examples/list_demo.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "zcatui", .module = zcatui_mod }, + }, + }), + }); + b.installArtifact(list_demo_exe); + + const run_list_demo = b.addRunArtifact(list_demo_exe); + run_list_demo.step.dependOn(b.getInstallStep()); + const list_demo_step = b.step("list-demo", "Run list demo"); + list_demo_step.dependOn(&run_list_demo.step); + + // Ejemplo: table_demo + const table_demo_exe = b.addExecutable(.{ + .name = "table-demo", + .root_module = b.createModule(.{ + .root_source_file = b.path("examples/table_demo.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "zcatui", .module = zcatui_mod }, + }, + }), + }); + b.installArtifact(table_demo_exe); + + const run_table_demo = b.addRunArtifact(table_demo_exe); + run_table_demo.step.dependOn(b.getInstallStep()); + const table_demo_step = b.step("table-demo", "Run table demo"); + table_demo_step.dependOn(&run_table_demo.step); + + // Ejemplo: dashboard + const dashboard_exe = b.addExecutable(.{ + .name = "dashboard", + .root_module = b.createModule(.{ + .root_source_file = b.path("examples/dashboard.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "zcatui", .module = zcatui_mod }, + }, + }), + }); + b.installArtifact(dashboard_exe); + + const run_dashboard = b.addRunArtifact(dashboard_exe); + run_dashboard.step.dependOn(b.getInstallStep()); + const dashboard_step = b.step("dashboard", "Run dashboard demo"); + dashboard_step.dependOn(&run_dashboard.step); } diff --git a/examples/dashboard.zig b/examples/dashboard.zig new file mode 100644 index 0000000..1cbf322 --- /dev/null +++ b/examples/dashboard.zig @@ -0,0 +1,253 @@ +//! Dashboard demo for zcatui. +//! +//! Demonstrates multiple widgets working together: +//! - Gauges showing system metrics +//! - Sparkline for real-time data +//! - List for logs/events +//! - Tabs for navigation +//! +//! Run with: zig build dashboard + +const std = @import("std"); +const zcatui = @import("zcatui"); + +const Terminal = zcatui.Terminal; +const Buffer = zcatui.Buffer; +const Rect = zcatui.Rect; +const Style = zcatui.Style; +const Color = zcatui.Color; +const Event = zcatui.Event; +const KeyCode = zcatui.KeyCode; +const Layout = zcatui.Layout; +const Constraint = zcatui.Constraint; +const Block = zcatui.widgets.Block; +const Borders = zcatui.widgets.Borders; +const Gauge = zcatui.widgets.Gauge; +const Sparkline = zcatui.widgets.Sparkline; +const List = zcatui.widgets.List; +const ListItem = zcatui.widgets.ListItem; +const Tabs = zcatui.widgets.Tabs; +const Paragraph = zcatui.widgets.Paragraph; +const Line = zcatui.Line; + +const AppState = struct { + running: bool = true, + tick: u64 = 0, + tab_index: usize = 0, + cpu_history: [64]u64 = [_]u64{0} ** 64, + mem_history: [64]u64 = [_]u64{0} ** 64, + history_idx: usize = 0, + cpu: u64 = 0, + mem: u64 = 0, + + fn update(self: *AppState) void { + self.tick += 1; + + // Simulate changing metrics + const seed = @as(u32, @truncate(self.tick)); + self.cpu = 30 + (seed * 7) % 50; + self.mem = 40 + (seed * 13) % 40; + + // Update history + self.cpu_history[self.history_idx] = self.cpu; + self.mem_history[self.history_idx] = self.mem; + self.history_idx = (self.history_idx + 1) % 64; + } + + fn nextTab(self: *AppState) void { + self.tab_index = (self.tab_index + 1) % 3; + } + + fn prevTab(self: *AppState) void { + if (self.tab_index == 0) { + self.tab_index = 2; + } else { + self.tab_index -= 1; + } + } +}; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var term = try Terminal.init(allocator); + defer term.deinit(); + + var state = AppState{}; + + while (state.running) { + state.update(); + try term.drawWithContext(&state, render); + + if (try term.pollEvent(50)) |event| { + handleEvent(&state, event); + } + } +} + +fn handleEvent(state: *AppState, event: Event) void { + switch (event) { + .key => |key| { + switch (key.code) { + .esc => state.running = false, + .char => |c| { + switch (c) { + 'q', 'Q' => state.running = false, + '1' => state.tab_index = 0, + '2' => state.tab_index = 1, + '3' => state.tab_index = 2, + else => {}, + } + }, + .tab => state.nextTab(), + .backtab => state.prevTab(), + .right => state.nextTab(), + .left => state.prevTab(), + else => {}, + } + }, + else => {}, + } +} + +fn render(state: *AppState, area: Rect, buf: *Buffer) void { + // Main layout: tabs at top, content below + const main_chunks = Layout.vertical(&.{ + Constraint.length(3), + Constraint.min(0), + }).split(area); + + // Render tabs + renderTabs(state, main_chunks.get(0), buf); + + // Render content based on selected tab + switch (state.tab_index) { + 0 => renderOverview(state, main_chunks.get(1), buf), + 1 => renderMetrics(state, main_chunks.get(1), buf), + 2 => renderHelp(main_chunks.get(1), buf), + else => {}, + } +} + +fn renderTabs(state: *AppState, area: Rect, buf: *Buffer) void { + const titles = [_]Line{ + Line.raw("Overview"), + Line.raw("Metrics"), + Line.raw("Help"), + }; + const tabs = Tabs.init(&titles) + .setBlock(Block.init() + .title(" Dashboard ") + .setBorders(Borders.all) + .style(Style.default.fg(Color.cyan))) + .select(state.tab_index) + .highlightStyle(Style.default.fg(Color.yellow).bold()); + tabs.render(area, buf); +} + +fn renderOverview(state: *AppState, area: Rect, buf: *Buffer) void { + // Split into gauges and sparklines + const chunks = Layout.vertical(&.{ + Constraint.length(5), + Constraint.length(5), + Constraint.min(0), + }).split(area); + + // CPU Gauge + const cpu_gauge = Gauge.init() + .setBlock(Block.init().title(" CPU ").setBorders(Borders.all)) + .percent(@intCast(state.cpu)) + .gaugeStyle(Style.default.fg(Color.green)); + cpu_gauge.render(chunks.get(0), buf); + + // Memory Gauge + const mem_gauge = Gauge.init() + .setBlock(Block.init().title(" Memory ").setBorders(Borders.all)) + .percent(@intCast(state.mem)) + .gaugeStyle(Style.default.fg(Color.blue)); + mem_gauge.render(chunks.get(1), buf); + + // Recent events + renderEvents(state, chunks.get(2), buf); +} + +fn renderMetrics(state: *AppState, area: Rect, buf: *Buffer) void { + // Split for two sparklines + const chunks = Layout.vertical(&.{ + Constraint.percentage(50), + Constraint.percentage(50), + }).split(area); + + // CPU Sparkline + const cpu_spark = Sparkline.init() + .setBlock(Block.init().title(" CPU History ").setBorders(Borders.all)) + .setData(&state.cpu_history) + .setMax(100) + .setStyle(Style.default.fg(Color.green)); + cpu_spark.render(chunks.get(0), buf); + + // Memory Sparkline + const mem_spark = Sparkline.init() + .setBlock(Block.init().title(" Memory History ").setBorders(Borders.all)) + .setData(&state.mem_history) + .setMax(100) + .setStyle(Style.default.fg(Color.blue)); + mem_spark.render(chunks.get(1), buf); +} + +fn renderEvents(state: *AppState, area: Rect, buf: *Buffer) void { + // Generate some fake events based on tick + var event_strs: [5][64]u8 = undefined; + var event_items: [5]ListItem = undefined; + + for (0..5) |i| { + const tick_val = state.tick -| (4 - i); + const written = std.fmt.bufPrint(&event_strs[i], "[{d:0>4}] System event {d}", .{ tick_val, i + 1 }) catch "???"; + event_items[i] = ListItem.raw(written); + } + + const list = List.init(&event_items) + .setBlock(Block.init() + .title(" Recent Events ") + .setBorders(Borders.all) + .style(Style.default.fg(Color.yellow))); + list.render(area, buf); +} + +fn renderHelp(area: Rect, buf: *Buffer) void { + const help_block = Block.init() + .title(" Help ") + .setBorders(Borders.all) + .style(Style.default.fg(Color.magenta)); + help_block.render(area, buf); + + const inner = help_block.inner(area); + var y = inner.top(); + + const lines = [_][]const u8{ + "Dashboard Demo - zcatui", + "", + "Navigation:", + " Tab/Shift+Tab - Switch tabs", + " Left/Right - Switch tabs", + " 1/2/3 - Jump to tab", + "", + "General:", + " q/ESC - Quit", + "", + "This demo shows multiple widgets:", + " - Tabs for navigation", + " - Gauges for percentages", + " - Sparklines for time series", + " - Lists for events/logs", + }; + + for (lines) |line| { + if (y < inner.bottom()) { + _ = buf.setString(inner.left(), y, line, Style.default); + y += 1; + } + } +} diff --git a/examples/list_demo.zig b/examples/list_demo.zig new file mode 100644 index 0000000..04fa043 --- /dev/null +++ b/examples/list_demo.zig @@ -0,0 +1,188 @@ +//! Interactive list demo for zcatui. +//! +//! Demonstrates a navigable list with keyboard controls. +//! - Up/Down or j/k: Navigate items +//! - Enter: Select item +//! - q/ESC: Quit +//! +//! Run with: zig build list-demo + +const std = @import("std"); +const zcatui = @import("zcatui"); + +const Terminal = zcatui.Terminal; +const Buffer = zcatui.Buffer; +const Rect = zcatui.Rect; +const Style = zcatui.Style; +const Color = zcatui.Color; +const Event = zcatui.Event; +const KeyCode = zcatui.KeyCode; +const Layout = zcatui.Layout; +const Constraint = zcatui.Constraint; +const Block = zcatui.widgets.Block; +const Borders = zcatui.widgets.Borders; +const List = zcatui.widgets.List; +const ListItem = zcatui.widgets.ListItem; +const ListState = zcatui.widgets.ListState; + +const items = [_][]const u8{ + "Item 1 - First item in the list", + "Item 2 - Second item", + "Item 3 - Third item", + "Item 4 - Fourth item", + "Item 5 - Fifth item", + "Item 6 - Sixth item", + "Item 7 - Seventh item", + "Item 8 - Eighth item", + "Item 9 - Ninth item", + "Item 10 - Tenth item", +}; + +const AppState = struct { + list_state: ListState, + selected_item: ?usize = null, + running: bool = true, + + fn init() AppState { + return .{ + .list_state = ListState.default, + }; + } + + fn nextItem(self: *AppState) void { + const current = self.list_state.selected orelse 0; + if (current < items.len - 1) { + self.list_state.selected = current + 1; + } + } + + fn prevItem(self: *AppState) void { + const current = self.list_state.selected orelse 0; + if (current > 0) { + self.list_state.selected = current - 1; + } + } + + fn selectCurrent(self: *AppState) void { + self.selected_item = self.list_state.selected; + } +}; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var term = try Terminal.init(allocator); + defer term.deinit(); + + var state = AppState.init(); + state.list_state.selected = 0; // Start with first item selected + + while (state.running) { + try term.drawWithContext(&state, render); + + if (try term.pollEvent(100)) |event| { + handleEvent(&state, event); + } + } +} + +fn handleEvent(state: *AppState, event: Event) void { + switch (event) { + .key => |key| { + switch (key.code) { + .esc => state.running = false, + .char => |c| { + switch (c) { + 'q', 'Q' => state.running = false, + 'j' => state.nextItem(), + 'k' => state.prevItem(), + else => {}, + } + }, + .down => state.nextItem(), + .up => state.prevItem(), + .enter => state.selectCurrent(), + else => {}, + } + }, + else => {}, + } +} + +fn render(state: *AppState, area: Rect, buf: *Buffer) void { + // Layout: list on left, info on right + const chunks = Layout.horizontal(&.{ + Constraint.percentage(60), + Constraint.percentage(40), + }).split(area); + + // Render list + renderList(state, chunks.get(0), buf); + + // Render info panel + renderInfo(state, chunks.get(1), buf); +} + +fn renderList(state: *AppState, area: Rect, buf: *Buffer) void { + // Create list items + var list_items: [items.len]ListItem = undefined; + for (items, 0..) |item, i| { + list_items[i] = ListItem.raw(item); + } + + const list = List.init(&list_items) + .setBlock(Block.init() + .title(" Items (j/k or arrows) ") + .setBorders(Borders.all) + .style(Style.default.fg(Color.cyan))) + .highlightStyle(Style.default.fg(Color.black).bg(Color.cyan)) + .highlightSymbol("> "); + + list.renderStateful(area, buf, &state.list_state); +} + +fn renderInfo(state: *AppState, area: Rect, buf: *Buffer) void { + const info_block = Block.init() + .title(" Info ") + .setBorders(Borders.all) + .style(Style.default.fg(Color.yellow)); + info_block.render(area, buf); + + const inner = info_block.inner(area); + var y = inner.top(); + + // Selected index + var idx_buf: [32]u8 = undefined; + const idx_str = if (state.list_state.selected) |sel| + std.fmt.bufPrint(&idx_buf, "Index: {d}", .{sel}) catch "?" + else + "Index: none"; + _ = buf.setString(inner.left(), y, idx_str, Style.default); + y += 2; + + // Last selected item + _ = buf.setString(inner.left(), y, "Last selected:", Style.default.bold()); + y += 1; + + if (state.selected_item) |sel| { + if (sel < items.len) { + _ = buf.setString(inner.left(), y, items[sel], Style.default.fg(Color.green)); + } + } else { + _ = buf.setString(inner.left(), y, "(press Enter)", Style.default.fg(Color.white)); + } + y += 3; + + // Help + _ = buf.setString(inner.left(), y, "Controls:", Style.default.bold()); + y += 1; + _ = buf.setString(inner.left(), y, "Up/k - Previous", Style.default); + y += 1; + _ = buf.setString(inner.left(), y, "Down/j - Next", Style.default); + y += 1; + _ = buf.setString(inner.left(), y, "Enter - Select", Style.default); + y += 1; + _ = buf.setString(inner.left(), y, "q/ESC - Quit", Style.default); +} diff --git a/examples/table_demo.zig b/examples/table_demo.zig new file mode 100644 index 0000000..c116eb8 --- /dev/null +++ b/examples/table_demo.zig @@ -0,0 +1,210 @@ +//! Interactive table demo for zcatui. +//! +//! Demonstrates a table with row selection and scrolling. +//! - Up/Down or j/k: Navigate rows +//! - Left/Right or h/l: Change column widths +//! - q/ESC: Quit +//! +//! Run with: zig build table-demo + +const std = @import("std"); +const zcatui = @import("zcatui"); + +const Terminal = zcatui.Terminal; +const Buffer = zcatui.Buffer; +const Rect = zcatui.Rect; +const Style = zcatui.Style; +const Color = zcatui.Color; +const Event = zcatui.Event; +const KeyCode = zcatui.KeyCode; +const Layout = zcatui.Layout; +const Constraint = zcatui.Constraint; +const Block = zcatui.widgets.Block; +const Borders = zcatui.widgets.Borders; +const Table = zcatui.widgets.Table; +const TableRow = zcatui.widgets.TableRow; +const TableCell = zcatui.widgets.TableCell; +const TableState = zcatui.widgets.TableState; + +const data = [_][4][]const u8{ + .{ "1", "Alice", "alice@example.com", "Active" }, + .{ "2", "Bob", "bob@example.com", "Active" }, + .{ "3", "Charlie", "charlie@example.com", "Inactive" }, + .{ "4", "Diana", "diana@example.com", "Active" }, + .{ "5", "Eve", "eve@example.com", "Pending" }, + .{ "6", "Frank", "frank@example.com", "Active" }, + .{ "7", "Grace", "grace@example.com", "Inactive" }, + .{ "8", "Henry", "henry@example.com", "Active" }, + .{ "9", "Ivy", "ivy@example.com", "Pending" }, + .{ "10", "Jack", "jack@example.com", "Active" }, + .{ "11", "Kate", "kate@example.com", "Active" }, + .{ "12", "Leo", "leo@example.com", "Inactive" }, +}; + +const AppState = struct { + table_state: TableState, + running: bool = true, + + fn init() AppState { + var state = AppState{ + .table_state = TableState.init(), + }; + state.table_state.selected = 0; + return state; + } + + fn nextRow(self: *AppState) void { + const current = self.table_state.selected orelse 0; + if (current < data.len - 1) { + self.table_state.selected = current + 1; + } + } + + fn prevRow(self: *AppState) void { + const current = self.table_state.selected orelse 0; + if (current > 0) { + self.table_state.selected = current - 1; + } + } + + fn firstRow(self: *AppState) void { + self.table_state.selected = 0; + } + + fn lastRow(self: *AppState) void { + self.table_state.selected = data.len - 1; + } +}; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var term = try Terminal.init(allocator); + defer term.deinit(); + + var state = AppState.init(); + + while (state.running) { + try term.drawWithContext(&state, render); + + if (try term.pollEvent(100)) |event| { + handleEvent(&state, event); + } + } +} + +fn handleEvent(state: *AppState, event: Event) void { + switch (event) { + .key => |key| { + switch (key.code) { + .esc => state.running = false, + .char => |c| { + switch (c) { + 'q', 'Q' => state.running = false, + 'j' => state.nextRow(), + 'k' => state.prevRow(), + 'g' => state.firstRow(), + 'G' => state.lastRow(), + else => {}, + } + }, + .down => state.nextRow(), + .up => state.prevRow(), + .home => state.firstRow(), + .end => state.lastRow(), + else => {}, + } + }, + else => {}, + } +} + +fn render(state: *AppState, area: Rect, buf: *Buffer) void { + // Layout: table takes most space, status bar at bottom + const chunks = Layout.vertical(&.{ + Constraint.min(0), + Constraint.length(3), + }).split(area); + + // Render table + renderTable(state, chunks.get(0), buf); + + // Render status bar + renderStatus(state, chunks.get(1), buf); +} + +fn renderTable(state: *AppState, area: Rect, buf: *Buffer) void { + // Create header + var header_cells: [4]TableCell = undefined; + const headers = [_][]const u8{ "ID", "Name", "Email", "Status" }; + for (headers, 0..) |h, i| { + header_cells[i] = TableCell.fromString(h); + } + const header = TableRow.init(&header_cells) + .setStyle(Style.default.fg(Color.yellow).bold()); + + // Create rows + var rows: [data.len]TableRow = undefined; + for (data, 0..) |row_data, i| { + var cells: [4]TableCell = undefined; + for (row_data, 0..) |cell_data, j| { + // Color status column + const cell_style = if (j == 3) blk: { + if (std.mem.eql(u8, cell_data, "Active")) { + break :blk Style.default.fg(Color.green); + } else if (std.mem.eql(u8, cell_data, "Inactive")) { + break :blk Style.default.fg(Color.red); + } else { + break :blk Style.default.fg(Color.yellow); + } + } else Style.default; + cells[j] = TableCell.fromString(cell_data).setStyle(cell_style); + } + rows[i] = TableRow.init(&cells); + } + + // Column widths + const widths = [_]Constraint{ + Constraint.length(4), + Constraint.length(12), + Constraint.min(20), + Constraint.length(10), + }; + + const table = Table.init() + .setRows(&rows) + .setWidths(&widths) + .setHeader(header) + .setBlock(Block.init() + .title(" Users Table ") + .setBorders(Borders.all) + .style(Style.default.fg(Color.cyan))) + .rowHighlightStyle(Style.default.fg(Color.black).bg(Color.cyan)) + .highlightSymbol("> "); + + table.renderStateful(area, buf, &state.table_state); +} + +fn renderStatus(state: *AppState, area: Rect, buf: *Buffer) void { + const status_block = Block.init() + .setBorders(Borders.all) + .style(Style.default.fg(Color.blue)); + status_block.render(area, buf); + + const inner = status_block.inner(area); + + // Row info + var row_buf: [64]u8 = undefined; + const row_str = if (state.table_state.selected) |sel| + std.fmt.bufPrint(&row_buf, "Row {d}/{d}", .{ sel + 1, data.len }) catch "?" + else + "No selection"; + _ = buf.setString(inner.left(), inner.top(), row_str, Style.default); + + // Help text + const help = "j/k:Navigate | g/G:First/Last | q:Quit"; + const help_x = if (inner.width > help.len) inner.right() - @as(u16, @intCast(help.len)) else inner.left(); + _ = buf.setString(help_x, inner.top(), help, Style.default.fg(Color.white)); +} diff --git a/src/widgets/table.zig b/src/widgets/table.zig index 117faea..35e0a8b 100644 --- a/src/widgets/table.zig +++ b/src/widgets/table.zig @@ -28,6 +28,16 @@ const Constraint = layout_mod.Constraint; const list_mod = @import("list.zig"); const HighlightSpacing = list_mod.HighlightSpacing; +// ============================================================================ +// ColumnPos +// ============================================================================ + +/// Column position information used during rendering. +const ColumnPos = struct { + x: u16, + width: u16, +}; + // ============================================================================ // Cell // ============================================================================ @@ -37,7 +47,7 @@ const HighlightSpacing = list_mod.HighlightSpacing; /// You can style the cell and its content independently. pub const Cell = struct { /// The text content of the cell. - content: Text = Text.default, + content: Text = Text.empty, /// Style applied to the cell area. style: Style = Style.default, @@ -423,7 +433,12 @@ pub const Table = struct { /// Returns the selection column width. fn selectionWidth(self: Table, state: *TableState) u16 { const has_selection = state.selected != null; - if (self.highlight_spacing.shouldAdd(has_selection)) { + const should_show = switch (self.highlight_spacing) { + .always => true, + .when_selected => has_selection, + .never => false, + }; + if (should_show) { return @intCast(text_mod.unicodeWidth(self.highlight_symbol)); } return 0; @@ -470,7 +485,7 @@ pub const Table = struct { const sel_width = self.selectionWidth(state); // Calculate column widths - var column_positions: [64]struct { x: u16, width: u16 } = undefined; + var column_positions: [64]ColumnPos = undefined; const positions = self.calculateColumnPositions(table_area.width, sel_width, col_count, &column_positions); // Calculate layout areas @@ -519,8 +534,8 @@ pub const Table = struct { max_width: u16, selection_width: u16, col_count: usize, - out: []struct { x: u16, width: u16 }, - ) []struct { x: u16, width: u16 } { + out: []ColumnPos, + ) []ColumnPos { if (col_count == 0) return out[0..0]; const actual_count = @min(col_count, out.len); @@ -543,13 +558,13 @@ pub const Table = struct { const space_for_cols = available_width -| total_spacing; for (out[0..actual_count], 0..) |*pos, i| { - const constraint = if (i < self.widths.len) self.widths[i] else Constraint{ .min = 0 }; + const constraint = if (i < self.widths.len) self.widths[i] else Constraint.min(0); const width: u16 = switch (constraint) { - .length => |l| @min(l, space_for_cols), - .percentage => |p| @intCast((space_for_cols * p) / 100), - .min => |m| m, - .max => |m| @min(m, space_for_cols), - .ratio => |r| @intCast(@as(u32, space_for_cols) * r.num / @max(1, r.den)), + .len => |l| @min(l, space_for_cols), + .pct => |p| @intCast((space_for_cols * p) / 100), + .min_size => |m| m, + .max_size => |m| @min(m, space_for_cols), + .rat => |r| @intCast(@as(u32, space_for_cols) * r.num / @max(1, r.den)), }; pos.x = x; pos.width = width; @@ -565,7 +580,7 @@ pub const Table = struct { row: Row, area: Rect, buf: *Buffer, - positions: []struct { x: u16, width: u16 }, + positions: []ColumnPos, ) void { _ = self; for (positions, 0..) |pos, i| { @@ -587,7 +602,7 @@ pub const Table = struct { buf: *Buffer, state: *TableState, selection_width: u16, - positions: []struct { x: u16, width: u16 }, + positions: []ColumnPos, ) void { if (self.rows.len == 0) return;