zcatui/examples/list_demo.zig
reugenio b9223dec85 Add 3 new interactive demo examples
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 <noreply@anthropic.com>
2025-12-08 13:22:42 +01:00

188 lines
5.3 KiB
Zig

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