//! Splitter Demo - Resizable panels with mouse drag //! //! Demonstrates mouse drag to resize panels. //! Run with: zig build splitter-demo const std = @import("std"); const zcatui = @import("zcatui"); const Terminal = zcatui.Terminal; const Rect = zcatui.Rect; const Buffer = zcatui.Buffer; const Style = zcatui.Style; const Color = zcatui.Color; const Block = zcatui.widgets.Block; const Borders = zcatui.widgets.Borders; const DragState = zcatui.DragState; const DragType = zcatui.DragType; const Splitter = zcatui.Splitter; /// State for the demo const State = struct { // Horizontal splitter (splits left/right) h_splitter: Splitter = Splitter.horizontal(30).setMinSizes(10, 20), // Vertical splitter for right side (splits top/bottom) v_splitter: Splitter = Splitter.vertical(50).setMinSizes(5, 5), // Drag state drag_state: DragState = .{}, // Which splitter is being dragged active_splitter: ActiveSplitter = .none, // Current area for hit testing current_area: Rect = Rect.init(0, 0, 80, 24), running: bool = true, }; const ActiveSplitter = enum { none, horizontal, vertical }; 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(); // Enable mouse capture for drag support try term.enableMouseCapture(); term.enableAutoResize(); var state = State{}; while (state.running) { try term.drawWithContext(&state, render); if (try term.pollEvent(50)) |event| { switch (event) { .key => |key| { switch (key.code) { .char => |c| { if (c == 'q') state.running = false; // Keyboard shortcuts to adjust splitters if (c == 'h' or c == 'H') { const delta: i32 = if (c == 'H') -5 else 5; state.h_splitter.adjustPosition(state.current_area, delta); } if (c == 'v' or c == 'V') { const parts = state.h_splitter.split(state.current_area); const delta: i32 = if (c == 'V') -5 else 5; state.v_splitter.adjustPosition(parts.second, delta); } }, .esc => state.running = false, else => {}, } }, .mouse => |mouse| { handleMouse(&state, mouse); }, else => {}, } } } } fn handleMouse(state: *State, mouse: zcatui.event.MouseEvent) void { switch (mouse.kind) { .down => { if (mouse.button == .left) { // Check if on horizontal splitter if (state.h_splitter.isOnHandle(state.current_area, mouse.column, mouse.row)) { state.drag_state.start(.horizontal_resize, mouse.column, mouse.row); state.active_splitter = .horizontal; } else { // Check if on vertical splitter (in second panel) const parts = state.h_splitter.split(state.current_area); if (state.v_splitter.isOnHandle(parts.second, mouse.column, mouse.row)) { state.drag_state.start(.vertical_resize, mouse.column, mouse.row); state.active_splitter = .vertical; } } } }, .drag => { if (state.drag_state.isDragging()) { const old_x = state.drag_state.current_x; const old_y = state.drag_state.current_y; state.drag_state.update(mouse.column, mouse.row); // Apply the delta to the active splitter switch (state.active_splitter) { .horizontal => { const delta = @as(i32, mouse.column) - @as(i32, old_x); if (delta != 0) { state.h_splitter.adjustPosition(state.current_area, delta); } }, .vertical => { const parts = state.h_splitter.split(state.current_area); const delta = @as(i32, mouse.row) - @as(i32, old_y); if (delta != 0) { state.v_splitter.adjustPosition(parts.second, delta); } }, .none => {}, } } }, .up => { state.drag_state.end(); state.active_splitter = .none; }, else => {}, } } fn render(state: *State, area: Rect, buf: *Buffer) void { // Store area for mouse hit testing state.current_area = area; // Get the split areas const h_parts = state.h_splitter.split(area); const v_parts = state.v_splitter.split(h_parts.second); // Draw left panel drawPanel(buf, h_parts.first, " Left Panel ", Color.blue, "This is the left panel.\n\nDrag the vertical bar to resize.\n\nOr press h/H to adjust."); // Draw top-right panel drawPanel(buf, v_parts.first, " Top Right ", Color.green, "This is the top-right panel.\n\nDrag the horizontal bar to resize.\n\nOr press v/V to adjust."); // Draw bottom-right panel drawPanel(buf, v_parts.second, " Bottom Right ", Color.yellow, "This is the bottom-right panel.\n\nTry dragging both splitters!"); // Draw splitter handles drawSplitter(buf, h_parts.handle, true, state.active_splitter == .horizontal); drawSplitter(buf, v_parts.handle, false, state.active_splitter == .vertical); // Draw status bar var status_buf: [128]u8 = undefined; const status = std.fmt.bufPrint(&status_buf, "H-split: {d}% | V-split: {d}% | {s}", .{ state.h_splitter.position, state.v_splitter.position, if (state.drag_state.isDragging()) "Dragging..." else "Drag splitters or press h/H v/V | q to quit", }) catch "..."; _ = buf.setString(0, area.height -| 1, status, (Style{}).fg(Color.white).bg(Color.rgb(40, 40, 40))); } fn drawPanel(buf: *Buffer, area: Rect, title: []const u8, color: Color, content: []const u8) void { const block = Block.init() .title(title) .setBorders(Borders.all) .borderStyle((Style{}).fg(color)); block.render(area, buf); // Draw content const inner = Rect.init(area.x + 1, area.y + 1, area.width -| 2, area.height -| 2); var y: u16 = 0; var lines = std.mem.splitScalar(u8, content, '\n'); while (lines.next()) |line| { if (y >= inner.height) break; const max_len = @min(line.len, inner.width); _ = buf.setString(inner.x, inner.y + y, line[0..max_len], Style{}); y += 1; } } fn drawSplitter(buf: *Buffer, handle: Rect, is_vertical: bool, is_active: bool) void { const color = if (is_active) Color.cyan else Color.rgb(100, 100, 100); const char: []const u8 = if (is_vertical) "|" else "-"; if (is_vertical) { var y: u16 = 0; while (y < handle.height) : (y += 1) { _ = buf.setString(handle.x, handle.y + y, char, (Style{}).fg(color)); } } else { var x: u16 = 0; while (x < handle.width) : (x += 1) { _ = buf.setString(handle.x + x, handle.y, char, (Style{}).fg(color)); } } }