//! Panel system demo for zcatui. //! //! Demonstrates: //! - Panel with borders and focus //! - TabbedPanel //! - DockingPanel (dockable/floating) //! - PanelManager //! //! Run with: zig build panel-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 Block = zcatui.widgets.Block; const Borders = zcatui.widgets.Borders; const Paragraph = zcatui.widgets.Paragraph; const Panel = zcatui.widgets.Panel; const TabbedPanel = zcatui.widgets.TabbedPanel; const DockingPanel = zcatui.widgets.DockingPanel; const DockPosition = zcatui.widgets.DockPosition; const PanelManager = zcatui.widgets.PanelManager; const StatusBar = zcatui.widgets.StatusBar; const AppState = struct { running: bool = true, manager: PanelManager, tabbed: TabbedPanel, show_floating: bool = false, floating_x: u16 = 20, floating_y: u16 = 5, fn init(allocator: std.mem.Allocator) !AppState { var manager = PanelManager.init(allocator); // Add sidebar (left docked) _ = try manager.add( DockingPanel.init("Sidebar") .setPosition(.left) .setDockSize(20), ); // Add bottom panel _ = try manager.add( DockingPanel.init("Console") .setPosition(.bottom) .setDockSize(25), ); // Focus first panel manager.focus(0); return .{ .manager = manager, .tabbed = TabbedPanel.init(&.{ "Overview", "Details", "Settings" }), }; } fn deinit(self: *AppState) void { self.manager.deinit(); } }; 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 = try AppState.init(allocator); defer state.deinit(); 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, .tab => state.manager.focusNext(), .char => |c| { switch (c) { 'q', 'Q' => state.running = false, 'f', 'F' => state.show_floating = !state.show_floating, '1' => state.tabbed.select(0), '2' => state.tabbed.select(1), '3' => state.tabbed.select(2), 'h', 'H' => { if (state.manager.get(0)) |p| { p.visible = !p.visible; } }, 'j', 'J' => { if (state.manager.get(1)) |p| { p.visible = !p.visible; } }, else => {}, } }, .left => { if (state.show_floating) { state.floating_x -|= 1; } else { state.tabbed.selectPrev(); } }, .right => { if (state.show_floating) { state.floating_x += 1; } else { state.tabbed.selectNext(); } }, .up => { if (state.show_floating) { state.floating_y -|= 1; } }, .down => { if (state.show_floating) { state.floating_y += 1; } }, else => {}, } }, else => {}, } } fn render(state: *AppState, area: Rect, buf: *Buffer) void { // Layout: main area and status bar if (area.height < 3) return; const content_area = Rect.init(area.x, area.y, area.width, area.height - 1); const status_area = Rect.init(area.x, area.y + area.height - 1, area.width, 1); // Render docked panels (modifies remaining area) state.manager.render(content_area, buf); // Calculate center area (after docked panels) var center_area = content_area; // Adjust for sidebar if (state.manager.get(0)) |sidebar| { if (sidebar.visible) { const sidebar_width = content_area.width * sidebar.dock_size / 100; center_area.x += sidebar_width; center_area.width -|= sidebar_width; // Render sidebar content const sb_area = Rect.init(content_area.x, content_area.y, sidebar_width, content_area.height); renderSidebar(sb_area, buf); } } // Adjust for bottom panel if (state.manager.get(1)) |bottom| { if (bottom.visible) { const bottom_height = content_area.height * bottom.dock_size / 100; center_area.height -|= bottom_height; // Render console content const console_area = Rect.init( center_area.x, center_area.y + center_area.height, center_area.width, bottom_height, ); renderConsole(console_area, buf); } } // Render tabbed panel in center state.tabbed.render(center_area, buf); // Render tab content const tab_content = Rect.init( center_area.x + 1, center_area.y + 2, center_area.width -| 2, center_area.height -| 4, ); renderTabContent(state.tabbed.selected, tab_content, buf); // Floating panel if (state.show_floating) { renderFloatingPanel(state, area, buf); } // Status bar const status = StatusBar.init() .setLeft("Tab: focus | H: sidebar | J: console | F: floating | 1-3: tabs") .setRight("Esc: exit"); status.render(status_area, buf); } fn renderSidebar(area: Rect, buf: *Buffer) void { const items = [_][]const u8{ " Files", " Search", " Git", " Debug", " Extensions", }; var y = area.y + 2; for (items) |item| { if (y < area.y + area.height - 1) { _ = buf.setString(area.x + 1, y, item, Style.default); y += 1; } } } fn renderConsole(area: Rect, buf: *Buffer) void { const lines = [_][]const u8{ "> Build started...", "> Compiling src/main.zig", "> Linking...", "> Build completed successfully!", }; var y = area.y + 1; for (lines) |line| { if (y < area.y + area.height - 1) { _ = buf.setString(area.x + 1, y, line, Style.default.fg(Color.green)); y += 1; } } } fn renderTabContent(tab: usize, area: Rect, buf: *Buffer) void { const content = switch (tab) { 0 => "Welcome to the Panel Demo!\n\nThis demonstrates the LEGO panel system.", 1 => "Details tab content.\n\nPanels can be:\n- Docked (left/right/top/bottom)\n- Floating\n- Tabbed", 2 => "Settings tab content.\n\nConfigure your preferences here.", else => "", }; var y = area.y; var iter = std.mem.splitScalar(u8, content, '\n'); while (iter.next()) |line| { if (y < area.y + area.height) { _ = buf.setString(area.x, y, line, Style.default); y += 1; } } } fn renderFloatingPanel(state: *AppState, bounds: Rect, buf: *Buffer) void { const width: u16 = 30; const height: u16 = 10; const x = @min(state.floating_x, bounds.width -| width); const y = @min(state.floating_y, bounds.height -| height); const float_area = Rect.init(x, y, width, height); // Clear background var cy = float_area.y; while (cy < float_area.y + float_area.height) : (cy += 1) { var cx = float_area.x; while (cx < float_area.x + float_area.width) : (cx += 1) { if (buf.getCell(cx, cy)) |cell| { cell.setChar(' '); cell.setStyle(Style.default.bg(Color.indexed(236))); } } } // Border const block = Block.init() .title(" Floating Panel ") .setBorders(Borders.all) .style(Style.default.fg(Color.yellow).bg(Color.indexed(236))); block.render(float_area, buf); // Content const inner = block.inner(float_area); _ = buf.setString(inner.x, inner.y, "This panel floats!", Style.default.bg(Color.indexed(236))); _ = buf.setString(inner.x, inner.y + 2, "Arrow keys to move", Style.default.fg(Color.indexed(8)).bg(Color.indexed(236))); _ = buf.setString(inner.x, inner.y + 3, "F to toggle", Style.default.fg(Color.indexed(8)).bg(Color.indexed(236))); }