//! Form widgets demo for zcatui. //! //! Demonstrates: //! - Checkbox, RadioGroup //! - Select dropdown //! - Slider //! - TextArea //! - StatusBar with toast notifications //! //! Run with: zig build form-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 Checkbox = zcatui.widgets.Checkbox; const RadioGroup = zcatui.widgets.RadioGroup; const Select = zcatui.widgets.Select; const Slider = zcatui.widgets.Slider; const StatusBar = zcatui.widgets.StatusBar; const Toast = zcatui.widgets.Toast; const ToastManager = zcatui.widgets.ToastManager; const FormField = enum { checkbox1, checkbox2, radio, select, slider, }; const AppState = struct { running: bool = true, current_field: FormField = .checkbox1, // Form values checkbox1: bool = false, checkbox2: bool = true, radio_selected: usize = 0, select: Select, slider_value: f64 = 50, // UI state toast_manager: ToastManager = ToastManager.init(), status: []const u8 = "Use Tab to navigate, Space/Enter to interact", fn init() AppState { return .{ .select = Select.init(&.{ "Option 1 - Basic", "Option 2 - Standard", "Option 3 - Premium", "Option 4 - Enterprise", }), }; } fn getCurrentFieldName(self: AppState) []const u8 { return switch (self.current_field) { .checkbox1 => "Dark Mode", .checkbox2 => "Notifications", .radio => "Theme", .select => "Plan", .slider => "Volume", }; } fn nextField(self: *AppState) void { self.current_field = switch (self.current_field) { .checkbox1 => .checkbox2, .checkbox2 => .radio, .radio => .select, .select => .slider, .slider => .checkbox1, }; self.status = self.getCurrentFieldName(); } fn prevField(self: *AppState) void { self.current_field = switch (self.current_field) { .checkbox1 => .slider, .checkbox2 => .checkbox1, .radio => .checkbox2, .select => .radio, .slider => .select, }; self.status = self.getCurrentFieldName(); } }; 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(); while (state.running) { // Update toasts state.toast_manager.update(); try term.drawWithContext(&state, render); if (try term.pollEvent(50)) |event| { handleEvent(&state, event); } } } fn handleEvent(state: *AppState, event: Event) void { switch (event) { .key => |key| { switch (key.code) { .esc => state.running = false, .tab => state.nextField(), .char => |c| { switch (c) { 'q', 'Q' => state.running = false, ' ' => handleSpace(state), else => {}, } }, .enter => handleEnter(state), .up => handleUp(state), .down => handleDown(state), .left => handleLeft(state), .right => handleRight(state), else => {}, } }, else => {}, } } fn handleSpace(state: *AppState) void { switch (state.current_field) { .checkbox1 => { state.checkbox1 = !state.checkbox1; if (state.checkbox1) { state.toast_manager.info("Dark mode enabled"); } else { state.toast_manager.info("Dark mode disabled"); } }, .checkbox2 => { state.checkbox2 = !state.checkbox2; }, .select => { state.select.toggle(); }, else => {}, } } fn handleEnter(state: *AppState) void { switch (state.current_field) { .select => { if (state.select.open) { state.select.confirm(); state.toast_manager.success("Plan selected!"); } else { state.select.toggle(); } }, .radio => { state.toast_manager.info("Theme applied"); }, else => handleSpace(state), } } fn handleUp(state: *AppState) void { switch (state.current_field) { .radio => { if (state.radio_selected > 0) { state.radio_selected -= 1; } }, .select => state.select.highlightPrev(), .slider => state.slider_value = @min(state.slider_value + 5, 100), else => state.prevField(), } } fn handleDown(state: *AppState) void { switch (state.current_field) { .radio => { if (state.radio_selected < 2) { state.radio_selected += 1; } }, .select => state.select.highlightNext(), .slider => state.slider_value = @max(state.slider_value - 5, 0), else => state.nextField(), } } fn handleLeft(state: *AppState) void { if (state.current_field == .slider) { state.slider_value = @max(state.slider_value - 1, 0); } } fn handleRight(state: *AppState) void { if (state.current_field == .slider) { state.slider_value = @min(state.slider_value + 1, 100); } } fn render(state: *AppState, area: Rect, buf: *Buffer) void { // Main layout const chunks = Layout.vertical(&.{ Constraint.min(0), // Content Constraint.length(1), // Status bar }).split(area); // Content area const content = chunks.get(0); const block = Block.init() .title(" Form Demo - Tab to navigate, Space/Enter to interact ") .setBorders(Borders.all) .style(Style.default.fg(Color.cyan)); block.render(content, buf); const inner = block.inner(content); // Form fields layout const form_chunks = Layout.vertical(&.{ Constraint.length(2), // Checkbox 1 Constraint.length(2), // Checkbox 2 Constraint.length(5), // Radio Constraint.length(4), // Select Constraint.length(2), // Slider }).split(inner); // Checkbox 1 - Dark Mode const cb1 = Checkbox.init("Enable Dark Mode") .setChecked(state.checkbox1) .setFocused(state.current_field == .checkbox1); cb1.render(form_chunks.get(0), buf); // Checkbox 2 - Notifications const cb2 = Checkbox.init("Enable Notifications") .setChecked(state.checkbox2) .setFocused(state.current_field == .checkbox2); cb2.render(form_chunks.get(1), buf); // Radio - Theme _ = buf.setString(form_chunks.get(2).x, form_chunks.get(2).y, "Theme:", Style.default); var radio = RadioGroup.init(&.{ "Light", "Dark", "System" }) .setFocused(state.current_field == .radio); radio.selected = state.radio_selected; radio.focused = state.radio_selected; radio.render( Rect.init(form_chunks.get(2).x, form_chunks.get(2).y + 1, form_chunks.get(2).width, 3), buf, ); // Select - Plan _ = buf.setString(form_chunks.get(3).x, form_chunks.get(3).y, "Plan:", Style.default); state.select.focused = (state.current_field == .select); state.select.render( Rect.init(form_chunks.get(3).x, form_chunks.get(3).y + 1, 30, form_chunks.get(3).height - 1), buf, ); // Slider - Volume const slider = Slider.init(0, 100) .setValue(state.slider_value) .setLabel("Volume") .setFocused(state.current_field == .slider); slider.render(form_chunks.get(4), buf); // Status bar const status = StatusBar.init() .setLeft(state.status) .setRight("Esc to exit"); status.render(chunks.get(1), buf); // Toasts state.toast_manager.render(area, buf); }