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>
200 lines
5.8 KiB
Zig
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",
|
|
};
|
|
}
|