zcatgui/examples/table_demo.zig
R.Eugenio f9179b4e9a build: Migrar a Zig 0.16
- Crear src/utils/time.zig para centralizar helpers de tiempo (timestamp, milliTimestamp, nanoTimestamp).
- Reemplazar std.io.fixedBufferStream por std.Io.Writer.fixed.
- Migrar de std.fs.cwd() a std.Io.Dir.cwd() y actualizar firmas de lectura/escritura.
- Reemplazar std.Thread.sleep por std.posix.system.nanosleep.
- Corregir capturas descartadas en switches (text_input => {}).
- Actualizar tests y ejemplos para compatibilidad con std.Io y nuevos helpers.
- Actualizar build.zig.zon a 0.16.0.

Co-Authored-By: Gemini <noreply@google.com>
2026-01-18 02:01:04 +01:00

349 lines
13 KiB
Zig

//! 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
const ts = std.posix.timespec{ .sec = 0, .nsec = 16 * std.time.ns_per_ms };
_ = std.posix.system.nanosleep(&ts, null);
}
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});
}