//! Table Demo - Advanced widgets showcase //! //! Demonstrates: //! - Table widget with editing and navigation //! - Split panels (horizontal/vertical) //! - Panel containers with titles //! - Focus management between widgets //! //! Run with: zig build table-demo const std = @import("std"); const zcatgui = @import("zcatgui"); const Context = zcatgui.Context; const Color = zcatgui.Color; const Layout = zcatgui.Layout; const Command = zcatgui.Command; const Framebuffer = zcatgui.render.Framebuffer; const SoftwareRenderer = zcatgui.render.SoftwareRenderer; const Sdl2Backend = zcatgui.backend.Sdl2Backend; const widgets = zcatgui.widgets; const Table = widgets.Table; const Split = widgets.Split; const Panel = widgets.Panel; const print = std.debug.print; // Sample data for the table const ProductData = struct { code: []const u8, name: []const u8, price: []const u8, stock: []const u8, status: []const u8, }; const sample_products = [_]ProductData{ .{ .code = "PRD001", .name = "Widget A", .price = "29.99", .stock = "150", .status = "Active" }, .{ .code = "PRD002", .name = "Widget B", .price = "49.99", .stock = "75", .status = "Active" }, .{ .code = "PRD003", .name = "Gadget X", .price = "99.99", .stock = "30", .status = "Low Stock" }, .{ .code = "PRD004", .name = "Gadget Y", .price = "149.99", .stock = "0", .status = "Out of Stock" }, .{ .code = "PRD005", .name = "Component Z", .price = "19.99", .stock = "500", .status = "Active" }, .{ .code = "PRD006", .name = "Assembly Kit", .price = "199.99", .stock = "25", .status = "Active" }, .{ .code = "PRD007", .name = "Spare Part A", .price = "9.99", .stock = "1000", .status = "Active" }, .{ .code = "PRD008", .name = "Spare Part B", .price = "14.99", .stock = "800", .status = "Active" }, .{ .code = "PRD009", .name = "Premium Set", .price = "299.99", .stock = "10", .status = "Limited" }, .{ .code = "PRD010", .name = "Basic Set", .price = "79.99", .stock = "200", .status = "Active" }, .{ .code = "PRD011", .name = "Deluxe Pack", .price = "399.99", .stock = "5", .status = "Limited" }, .{ .code = "PRD012", .name = "Starter Kit", .price = "59.99", .stock = "300", .status = "Active" }, }; // Column definitions const columns = [_]widgets.Column{ .{ .name = "Code", .width = 80, .column_type = .text }, .{ .name = "Name", .width = 150, .column_type = .text }, .{ .name = "Price", .width = 80, .column_type = .number }, .{ .name = "Stock", .width = 60, .column_type = .number }, .{ .name = "Status", .width = 100, .column_type = .text }, }; // Cell data provider function fn getCellData(row: usize, col: usize) []const u8 { if (row >= sample_products.len) return ""; const product = sample_products[row]; return switch (col) { 0 => product.code, 1 => product.name, 2 => product.price, 3 => product.stock, 4 => product.status, else => "", }; } pub fn main() !void { print("=== zcatgui Table Demo ===\n\n", .{}); print("This demo showcases advanced widgets:\n", .{}); print("- Table with keyboard navigation and editing\n", .{}); print("- Split panels (drag divider to resize)\n", .{}); print("- Panel containers with title bars\n\n", .{}); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Initialize backend var backend = try Sdl2Backend.init("zcatgui - Table Demo", 1024, 768); defer backend.deinit(); // Create framebuffer var fb = try Framebuffer.init(allocator, 1024, 768); defer fb.deinit(); // Create renderer var renderer = SoftwareRenderer.init(&fb); // Create context var ctx = try Context.init(allocator, 1024, 768); defer ctx.deinit(); // Widget state var hsplit_state = widgets.SplitState{ .offset = 0.7 }; var vsplit_state = widgets.SplitState{ .offset = 0.6 }; var main_panel_state = widgets.PanelState{ .focused = true }; var info_panel_state = widgets.PanelState{}; var log_panel_state = widgets.PanelState{}; var table_state = widgets.TableState{}; table_state.row_count = sample_products.len; // Mark some rows as modified for demo table_state.markModified(2); table_state.markNew(sample_products.len - 1); var running = true; var frame: u32 = 0; print("Starting event loop...\n", .{}); print("Controls:\n", .{}); print(" Arrow keys: Navigate table\n", .{}); print(" Enter/F2: Edit cell\n", .{}); print(" Escape: Cancel edit / Exit\n", .{}); print(" Tab: Move between panels\n", .{}); print(" Mouse: Click to select, drag dividers\n\n", .{}); while (running) { // Poll events while (backend.pollEvent()) |event| { switch (event) { .quit => running = false, .key => |key| { // Pass key event to context for widget keyboard handling ctx.input.handleKeyEvent(key); // Handle escape at app level (quit if not editing) if (key.key == .escape and key.pressed and !table_state.editing) { running = false; } }, .mouse => |m| { ctx.input.setMousePos(m.x, m.y); if (m.button) |btn| { ctx.input.setMouseButton(btn, m.pressed); } }, .resize => |size| { try fb.resize(size.width, size.height); ctx.resize(size.width, size.height); }, else => {}, } } ctx.beginFrame(); // Clear renderer.clear(Color.background); // Main horizontal split: left (table) | right (info) ctx.layout.row_height = @as(u32, @intCast(fb.height)); const hsplit_result = Split.hsplitEx(&ctx, &hsplit_state, .{ .divider_size = 6, }); // Left side: Main panel with table { const panel_result = Panel.panelRect( &ctx, hsplit_result.first, &main_panel_state, .{ .title = "Products", .collapsible = false }, .{}, ); if (!panel_result.content.isEmpty()) { Panel.beginPanel(&ctx, "products_panel", panel_result.content); // Set up layout for table ctx.layout.area = panel_result.content; ctx.layout.cursor_x = panel_result.content.x; ctx.layout.cursor_y = panel_result.content.y; ctx.layout.row_height = panel_result.content.h; const table_result = Table.tableEx( &ctx, &table_state, &columns, getCellData, null, // no edit callback .{ .row_height = 24 }, .{}, ); if (table_result.selection_changed) { if (table_state.selectedCell()) |cell| { print("Selected: row={}, col={}\n", .{ cell.row, cell.col }); } } if (table_result.cell_edited) { if (table_state.selectedCell()) |cell| { print("Edited row {} col {}: \"{s}\"\n", .{ cell.row, cell.col, table_state.getEditText(), }); table_state.markModified(cell.row); } } Panel.endPanel(&ctx); } } // Right side: vertical split for info and log { const vsplit_result = Split.splitRect( &ctx, hsplit_result.second, &vsplit_state, .vertical, .{ .divider_size = 6 }, ); // Top right: Info panel { const info_result = Panel.panelRect( &ctx, vsplit_result.first, &info_panel_state, .{ .title = "Details", .collapsible = true }, .{}, ); if (!info_result.content.isEmpty()) { Panel.beginPanel(&ctx, "info_panel", info_result.content); // Show selected product info ctx.layout.area = info_result.content; ctx.layout.cursor_x = info_result.content.x; ctx.layout.cursor_y = info_result.content.y; ctx.layout.row_height = 20; if (table_state.selectedCell()) |cell| { if (cell.row < sample_products.len) { const product = sample_products[cell.row]; zcatgui.labelColored(&ctx, "Selected Product:", Color.primary); ctx.layout.row_height = 16; var buf: [64]u8 = undefined; const code_text = std.fmt.bufPrint(&buf, "Code: {s}", .{product.code}) catch "Error"; zcatgui.label(&ctx, code_text); const name_text = std.fmt.bufPrint(&buf, "Name: {s}", .{product.name}) catch "Error"; zcatgui.label(&ctx, name_text); const price_text = std.fmt.bufPrint(&buf, "Price: ${s}", .{product.price}) catch "Error"; zcatgui.label(&ctx, price_text); const stock_text = std.fmt.bufPrint(&buf, "Stock: {s} units", .{product.stock}) catch "Error"; zcatgui.label(&ctx, stock_text); const status_text = std.fmt.bufPrint(&buf, "Status: {s}", .{product.status}) catch "Error"; zcatgui.label(&ctx, status_text); } } else { zcatgui.labelColored(&ctx, "No product selected", Color.secondary); } Panel.endPanel(&ctx); } } // Bottom right: Log panel { const log_result = Panel.panelRect( &ctx, vsplit_result.second, &log_panel_state, .{ .title = "Activity Log", .collapsible = true }, .{}, ); if (!log_result.content.isEmpty()) { Panel.beginPanel(&ctx, "log_panel", log_result.content); ctx.layout.area = log_result.content; ctx.layout.cursor_x = log_result.content.x; ctx.layout.cursor_y = log_result.content.y; ctx.layout.row_height = 14; // Show some log entries zcatgui.labelColored(&ctx, "Recent Activity:", Color.secondary); var frame_buf: [64]u8 = undefined; const frame_text = std.fmt.bufPrint(&frame_buf, "Frame: {}", .{frame}) catch "Error"; zcatgui.label(&ctx, frame_text); const commands_text = std.fmt.bufPrint(&frame_buf, "Commands: {}", .{ctx.commands.items.len}) catch "Error"; zcatgui.label(&ctx, commands_text); const dirty_count = blk: { var count: usize = 0; for (0..sample_products.len) |i| { if (table_state.row_states[i] != .clean) count += 1; } break :blk count; }; const dirty_text = std.fmt.bufPrint(&frame_buf, "Modified rows: {}", .{dirty_count}) catch "Error"; zcatgui.label(&ctx, dirty_text); if (table_state.editing) { zcatgui.labelColored(&ctx, "Currently editing...", Color.warning); } Panel.endPanel(&ctx); } } } // Execute all draw commands for (ctx.commands.items) |cmd| { renderer.execute(cmd); } ctx.endFrame(); // Present backend.present(&fb); frame += 1; // Cap at ~60 FPS std.Thread.sleep(16 * std.time.ns_per_ms); } print("\n=== Demo Complete ===\n", .{}); print("Final state:\n", .{}); if (table_state.selectedCell()) |cell| { print(" Selected: row={}, col={}\n", .{ cell.row, cell.col }); } else { print(" No selection\n", .{}); } print(" Frames rendered: {}\n", .{frame}); }