zcatui/examples/events_demo.zig
reugenio 5556ee1370 zcatui v1.2 - Sistema de eventos integrado (crossterm-style)
Eventos de teclado:
- Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind
- Soporte para todas las teclas: caracteres, F1-F12, navegación, control
- Modificadores: Ctrl, Alt, Shift, Super

Eventos de ratón:
- MouseEvent, MouseEventKind, MouseButton
- Click, release, drag, scroll (up/down/left/right)
- Posición (column, row) con modificadores
- Protocolos: SGR extended y X10 legacy

Parser de escape sequences:
- CSI sequences (arrows, F-keys, navigation)
- SS3 sequences (F1-F4 alternativo)
- SGR mouse protocol (mejores coordenadas)
- X10 mouse protocol (compatibilidad)
- Focus events, bracketed paste

Cursor control:
- Visibility: show/hide
- Blinking: enable/disable
- Styles: block, underline, bar (blinking/steady)
- Position: moveTo, moveUp/Down/Left/Right
- Save/restore position

Terminal integrado:
- pollEvent(timeout_ms) - polling con timeout
- readEvent() - blocking read
- enableMouseCapture/disableMouseCapture
- enableFocusChange/disableFocusChange
- enableBracketedPaste/disableBracketedPaste

Ejemplo interactivo:
- examples/events_demo.zig

Tests: 47 (29 nuevos para eventos y cursor)

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

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

200 lines
5.8 KiB
Zig

//! Interactive events demo for zcatui.
//!
//! Demonstrates keyboard and mouse event handling.
//! Press 'q' or ESC to quit.
//!
//! Run with: zig build run-events-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 Layout = zcatui.Layout;
const Constraint = zcatui.Constraint;
/// Application state
const AppState = struct {
last_event: ?Event = null,
key_count: u32 = 0,
mouse_x: u16 = 0,
mouse_y: u16 = 0,
mouse_clicks: u32 = 0,
running: bool = true,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Initialize terminal
var term = try Terminal.init(allocator);
defer term.deinit();
// Enable mouse capture
try term.enableMouseCapture();
// Enable focus events
try term.enableFocusChange();
var state = AppState{};
// Main loop
while (state.running) {
// Draw UI
try term.drawWithContext(&state, render);
// Poll for events (100ms timeout)
if (try term.pollEvent(100)) |event| {
handleEvent(&state, event);
}
}
}
fn handleEvent(state: *AppState, event: Event) void {
state.last_event = event;
switch (event) {
.key => |key| {
state.key_count += 1;
// Quit on 'q' or ESC
switch (key.code) {
.esc => state.running = false,
.char => |c| {
if (c == 'q' or c == 'Q') {
state.running = false;
}
},
else => {},
}
},
.mouse => |mouse| {
state.mouse_x = mouse.column;
state.mouse_y = mouse.row;
if (mouse.kind == .down) {
state.mouse_clicks += 1;
}
},
.resize => |_| {
// Terminal handles resize automatically
},
else => {},
}
}
fn render(state: *AppState, area: Rect, buf: *Buffer) void {
// Create layout: header, content, footer
const chunks = Layout.vertical(&.{
Constraint.length(3),
Constraint.min(0),
Constraint.length(3),
}).split(area);
// Header
const header = Block.init()
.title(" zcatui Events Demo ")
.setBorders(Borders.all)
.style(Style.default.fg(Color.cyan));
header.render(chunks.get(0), buf);
// Content - show event info
renderContent(state, chunks.get(1), buf);
// Footer
const footer_text = "Press 'q' or ESC to quit | Mouse enabled";
const footer = Block.init()
.setBorders(Borders.all)
.style(Style.default.fg(Color.blue));
const footer_area = chunks.get(2);
footer.render(footer_area, buf);
// Render footer text manually
const inner = footer.inner(footer_area);
_ = buf.setString(inner.left(), inner.top(), footer_text, Style.default);
}
fn renderContent(state: *AppState, area: Rect, buf: *Buffer) void {
const content = Block.init()
.title(" Event Info ")
.setBorders(Borders.all);
content.render(area, buf);
const inner = content.inner(area);
var y = inner.top();
// Key count
var key_buf: [64]u8 = undefined;
const key_str = std.fmt.bufPrint(&key_buf, "Key presses: {d}", .{state.key_count}) catch "???";
_ = buf.setString(inner.left(), y, key_str, Style.default.fg(Color.green));
y += 1;
// Mouse position
var mouse_buf: [64]u8 = undefined;
const mouse_str = std.fmt.bufPrint(&mouse_buf, "Mouse position: ({d}, {d})", .{ state.mouse_x, state.mouse_y }) catch "???";
_ = buf.setString(inner.left(), y, mouse_str, Style.default.fg(Color.yellow));
y += 1;
// Mouse clicks
var clicks_buf: [64]u8 = undefined;
const clicks_str = std.fmt.bufPrint(&clicks_buf, "Mouse clicks: {d}", .{state.mouse_clicks}) catch "???";
_ = buf.setString(inner.left(), y, clicks_str, Style.default.fg(Color.magenta));
y += 2;
// Last event
_ = buf.setString(inner.left(), y, "Last event:", Style.default.bold());
y += 1;
if (state.last_event) |event| {
const event_str = formatEvent(event);
_ = buf.setString(inner.left() + 2, y, event_str, Style.default.fg(Color.white));
} else {
_ = buf.setString(inner.left() + 2, y, "(none)", Style.default);
}
}
fn formatEvent(event: Event) []const u8 {
return switch (event) {
.key => |key| switch (key.code) {
.char => "Key: char",
.enter => "Key: Enter",
.esc => "Key: Escape",
.tab => "Key: Tab",
.backspace => "Key: Backspace",
.up => "Key: Up",
.down => "Key: Down",
.left => "Key: Left",
.right => "Key: Right",
.home => "Key: Home",
.end => "Key: End",
.page_up => "Key: PageUp",
.page_down => "Key: PageDown",
.delete => "Key: Delete",
.insert => "Key: Insert",
.f => "Key: F-key",
else => "Key: other",
},
.mouse => |mouse| switch (mouse.kind) {
.down => "Mouse: click",
.up => "Mouse: release",
.drag => "Mouse: drag",
.moved => "Mouse: move",
.scroll_up => "Mouse: scroll up",
.scroll_down => "Mouse: scroll down",
else => "Mouse: other",
},
.resize => "Resize",
.focus_gained => "Focus gained",
.focus_lost => "Focus lost",
.paste => "Paste",
};
}