//! 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); }