zcatui/examples/splitter_demo.zig
reugenio 7abc87a4f5 feat: zcatui v2.2 - Complete feature set with 13 new modules
New modules (13):
- src/resize.zig: SIGWINCH terminal resize detection
- src/drag.zig: Mouse drag state and Splitter panels
- src/diagnostic.zig: Elm-style error messages with code snippets
- src/debug.zig: Debug overlay (FPS, timing, widget count)
- src/profile.zig: Performance profiling with scoped timers
- src/sixel.zig: Sixel graphics encoding for terminal images
- src/async_loop.zig: epoll-based async event loop with timers
- src/compose.zig: Widget composition utilities
- src/shortcuts.zig: Keyboard shortcut registry
- src/widgets/logo.zig: ASCII art logo widget

Enhanced modules:
- src/layout.zig: Added Constraint.ratio(num, denom)
- src/terminal.zig: Integrated resize handling
- src/root.zig: Re-exports all new modules

New examples (9):
- resize_demo, splitter_demo, dirtree_demo
- help_demo, markdown_demo, progress_demo
- spinner_demo, syntax_demo, viewport_demo

Package manager:
- build.zig.zon: Zig package manager support

Stats: 60+ source files, 186+ tests, 20 executables

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 22:46:06 +01:00

200 lines
7.5 KiB
Zig

//! 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));
}
}
}