- Complete API documentation for zcatui v2.2 - All 35 widgets documented with examples - Core types: Buffer, Style, Text, Layout - Terminal interface and event handling - Animation, focus, themes systems - v2.2 features: resize, drag, debug, profiler - Utilities: clipboard, hyperlinks, notifications - File index with all 70+ source files - 1100+ lines of documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1400 lines
32 KiB
Markdown
1400 lines
32 KiB
Markdown
# zcatui API Reference v2.2
|
|
|
|
> Manual de referencia completo para la libreria TUI de Zig.
|
|
> Equivalente a ratatui + crossterm de Rust, en un solo paquete.
|
|
|
|
---
|
|
|
|
## Tabla de Contenidos
|
|
|
|
1. [Quick Start](#quick-start)
|
|
2. [Core Types](#core-types)
|
|
3. [Terminal](#terminal)
|
|
4. [Layout System](#layout-system)
|
|
5. [Widgets](#widgets)
|
|
6. [Events](#events)
|
|
7. [Styling](#styling)
|
|
8. [Animation](#animation)
|
|
9. [Focus Management](#focus-management)
|
|
10. [Themes](#themes)
|
|
11. [Utilities](#utilities)
|
|
12. [v2.2 Features](#v22-features)
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
const zcatui = @import("zcatui");
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
// Initialize terminal
|
|
var term = try zcatui.Terminal.init(allocator);
|
|
defer term.deinit();
|
|
|
|
// Enable features
|
|
try term.enableMouseCapture();
|
|
term.enableAutoResize();
|
|
|
|
// Main loop
|
|
while (true) {
|
|
try term.draw(render);
|
|
|
|
if (try term.pollEvent(100)) |event| {
|
|
switch (event) {
|
|
.key => |key| {
|
|
if (key.code == .esc) break;
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn render(area: zcatui.Rect, buf: *zcatui.Buffer) void {
|
|
const block = zcatui.widgets.Block.bordered()
|
|
.title("Hello zcatui!");
|
|
block.render(area, buf);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Core Types
|
|
|
|
### Buffer (`src/buffer.zig`)
|
|
|
|
Buffer de renderizado con diff para actualizaciones eficientes.
|
|
|
|
| Type | Description |
|
|
|------|-------------|
|
|
| `Buffer` | Grid de celdas para renderizado |
|
|
| `Cell` | Celda individual (simbolo + estilo) |
|
|
| `Rect` | Area rectangular (x, y, width, height) |
|
|
| `Symbol` | UTF-8 compacto (hasta 4 bytes) |
|
|
| `Margin` | Margenes para `Rect.inner()` |
|
|
| `CellUpdate` | Update de celda para diff |
|
|
| `DiffIterator` | Iterador de diferencias entre buffers |
|
|
|
|
```zig
|
|
// Rect
|
|
const area = Rect.init(0, 0, 80, 24);
|
|
area.isEmpty() // bool
|
|
area.area() // u32 (width * height)
|
|
area.left(), .right() // u16
|
|
area.top(), .bottom() // u16
|
|
area.inner(margin) // Rect con margenes aplicados
|
|
area.intersection(other) // Rect interseccion
|
|
area.contains(x, y) // bool
|
|
|
|
// Margin
|
|
Margin.uniform(2) // 2 en todos lados
|
|
Margin.symmetric(h, v) // horizontal, vertical
|
|
|
|
// Buffer
|
|
var buf = try Buffer.init(allocator, rect);
|
|
defer buf.deinit();
|
|
buf.setChar(x, y, 'X', style)
|
|
buf.setString(x, y, "text", style) // returns u16 chars written
|
|
buf.fill(rect, char, style)
|
|
buf.clear()
|
|
buf.get(x, y) // ?Cell
|
|
buf.getPtr(x, y) // ?*Cell
|
|
buf.diff(&other_buffer) // DiffIterator
|
|
buf.merge(&other_buffer)
|
|
buf.setStyle(rect, style)
|
|
try buf.resize(new_rect)
|
|
|
|
// Symbol
|
|
Symbol.fromSlice("X")
|
|
Symbol.fromCodepoint('X')
|
|
symbol.slice() // []const u8
|
|
symbol.eql(other) // bool
|
|
|
|
// Cell
|
|
Cell.init('X')
|
|
Cell.fromStr("X")
|
|
cell.setStyle(style)
|
|
cell.setChar(codepoint)
|
|
cell.setSymbol(string)
|
|
cell.reset()
|
|
cell.eql(other)
|
|
cell.char() // u21
|
|
```
|
|
|
|
### Style (`src/style.zig`)
|
|
|
|
Colores y modificadores de texto.
|
|
|
|
| Type | Description |
|
|
|------|-------------|
|
|
| `Color` | Color (ANSI, 256, RGB) |
|
|
| `Style` | Estilo completo (fg, bg, modifiers) |
|
|
| `Modifier` | Modificadores (bold, italic, etc) |
|
|
|
|
```zig
|
|
// Color
|
|
Color.reset // Reset to default
|
|
Color.black, .red, .green, .yellow, .blue, .magenta, .cyan, .white
|
|
Color.rgb(255, 128, 0) // 24-bit RGB
|
|
Color.indexed(200) // 256 palette
|
|
|
|
// Style (builder pattern)
|
|
const style = Style{}
|
|
.fg(Color.red)
|
|
.bg(Color.black)
|
|
.bold()
|
|
.italic()
|
|
.underlined()
|
|
.dim()
|
|
.reversed();
|
|
|
|
style.add_modifier(.{ .bold = true })
|
|
style.remove_modifier(.{ .bold = true })
|
|
style.patch(other_style)
|
|
|
|
// Modifier
|
|
Modifier.BOLD
|
|
Modifier.DIM
|
|
Modifier.ITALIC
|
|
Modifier.UNDERLINED
|
|
Modifier.REVERSED
|
|
Modifier.CROSSED_OUT
|
|
modifier.insert(other) // union
|
|
modifier.remove(other) // difference
|
|
```
|
|
|
|
### Text (`src/text.zig`)
|
|
|
|
Primitivas de texto con estilos.
|
|
|
|
| Type | Description |
|
|
|------|-------------|
|
|
| `Span` | String con un solo estilo |
|
|
| `Line` | Linea de Spans |
|
|
| `Text` | Multiples Lines |
|
|
| `StyledGrapheme` | Grafema con estilo |
|
|
| `Alignment` | left, center, right |
|
|
|
|
```zig
|
|
// Span
|
|
Span.raw("hello")
|
|
Span.styled("hello", style)
|
|
span.fg(Color.red).bold()
|
|
span.width() // usize
|
|
span.render(area, buf) // u16 chars written
|
|
|
|
// Line
|
|
Line.raw("hello world")
|
|
Line.styled("hello", style)
|
|
Line.fromSpans(&[_]Span{ span1, span2 })
|
|
line.centered()
|
|
line.rightAligned()
|
|
line.leftAligned()
|
|
line.fg(Color.blue).bold()
|
|
line.width()
|
|
line.render(area, buf)
|
|
|
|
// Text
|
|
Text.fromLine(line)
|
|
Text.fromLines(&[_]Line{ line1, line2 })
|
|
text.centered()
|
|
text.width() // max width of lines
|
|
text.height() // number of lines
|
|
text.render(area, buf)
|
|
```
|
|
|
|
---
|
|
|
|
## Terminal
|
|
|
|
### Terminal (`src/terminal.zig`)
|
|
|
|
Interfaz principal para aplicaciones TUI.
|
|
|
|
```zig
|
|
// Initialization
|
|
var term = try Terminal.init(allocator);
|
|
defer term.deinit();
|
|
|
|
// Properties
|
|
term.area() // Rect
|
|
term.buffer() // *Buffer
|
|
|
|
// Drawing
|
|
try term.draw(render_fn); // fn(Rect, *Buffer) void
|
|
try term.drawWithContext(ctx, render_fn); // fn(T, Rect, *Buffer) void
|
|
try term.clear();
|
|
|
|
// Resize
|
|
term.enableAutoResize()
|
|
term.disableAutoResize()
|
|
term.getSize() // Size { width, height }
|
|
try term.checkAndHandleResize() // bool
|
|
term.isResizePending() // bool
|
|
try term.resize(width, height)
|
|
|
|
// Events
|
|
try term.pollEvent(timeout_ms) // ?Event (null = timeout)
|
|
try term.readEvent() // Event (blocking)
|
|
|
|
// Mouse
|
|
try term.enableMouseCapture()
|
|
try term.disableMouseCapture()
|
|
|
|
// Focus
|
|
try term.enableFocusChange()
|
|
try term.disableFocusChange()
|
|
|
|
// Paste
|
|
try term.enableBracketedPaste()
|
|
try term.disableBracketedPaste()
|
|
|
|
// Cursor
|
|
try term.showCursor()
|
|
try term.hideCursor()
|
|
try term.setCursorPosition(col, row)
|
|
```
|
|
|
|
---
|
|
|
|
## Layout System
|
|
|
|
### Layout (`src/layout.zig`)
|
|
|
|
Sistema de layout para dividir areas.
|
|
|
|
| Type | Description |
|
|
|------|-------------|
|
|
| `Layout` | Divisor de areas |
|
|
| `Constraint` | Restriccion de tamano |
|
|
| `Direction` | horizontal, vertical |
|
|
| `Flex` | Layout CSS-like |
|
|
| `JustifyContent` | start, end, center, space_between, space_around, space_evenly |
|
|
| `AlignItems` | stretch, start, end, center |
|
|
| `SplitResult` | Resultado con hasta 16 Rects |
|
|
|
|
```zig
|
|
// Basic Layout
|
|
const result = Layout.vertical(&.{
|
|
Constraint.length(3), // 3 rows exactas
|
|
Constraint.min(0), // resto del espacio
|
|
}).split(area);
|
|
|
|
const header = result.get(0); // Rect
|
|
const content = result.get(1); // Rect
|
|
|
|
// Constraint types
|
|
Constraint.length(10) // exactly 10 cells
|
|
Constraint.min(5) // at least 5 cells
|
|
Constraint.max(20) // at most 20 cells
|
|
Constraint.percentage(50) // 50% of space
|
|
Constraint.ratio(1, 3) // 1/3 of space
|
|
Constraint.fill() // fill remaining (= min(0))
|
|
|
|
// Ratio helpers
|
|
Constraint.half() // 1/2
|
|
Constraint.third() // 1/3
|
|
Constraint.twoThirds() // 2/3
|
|
Constraint.quarter() // 1/4
|
|
Constraint.threeQuarters() // 3/4
|
|
Constraint.fifth() // 1/5
|
|
Constraint.goldenLarge() // ~61.8%
|
|
Constraint.goldenSmall() // ~38.2%
|
|
Constraint.prop(2, 5) // 2 parts of 5
|
|
|
|
// Layout with margin
|
|
Layout.vertical(&constraints).withMargin(2)
|
|
|
|
// Flex layout (CSS-like)
|
|
const flex = Flex.horizontal()
|
|
.setJustify(.center)
|
|
.setAlign(.stretch)
|
|
.setGap(2)
|
|
.setMargin(1)
|
|
.items(&.{ 20, 30, 20 });
|
|
const result = flex.split(area);
|
|
|
|
// Helper functions
|
|
centerRect(outer, width, height) // Rect centered
|
|
alignBottom(outer, height) // Rect at bottom
|
|
alignRight(outer, width) // Rect at right
|
|
alignBottomRight(outer, width, height)
|
|
```
|
|
|
|
---
|
|
|
|
## Widgets
|
|
|
|
### Widget Index
|
|
|
|
| Widget | File | Description |
|
|
|--------|------|-------------|
|
|
| Block | `widgets/block.zig` | Container con bordes y titulos |
|
|
| Paragraph | `widgets/paragraph.zig` | Texto con wrapping |
|
|
| List | `widgets/list.zig` | Lista seleccionable |
|
|
| Table | `widgets/table.zig` | Tabla multi-columna |
|
|
| Gauge | `widgets/gauge.zig` | Barra de progreso |
|
|
| LineGauge | `widgets/gauge.zig` | Progreso en linea |
|
|
| Tabs | `widgets/tabs.zig` | Navegacion por tabs |
|
|
| Sparkline | `widgets/sparkline.zig` | Mini graficos |
|
|
| Scrollbar | `widgets/scrollbar.zig` | Indicador de scroll |
|
|
| BarChart | `widgets/barchart.zig` | Graficos de barras |
|
|
| Canvas | `widgets/canvas.zig` | Dibujo libre |
|
|
| Chart | `widgets/chart.zig` | Graficos con ejes |
|
|
| Calendar | `widgets/calendar.zig` | Calendario mensual |
|
|
| Clear | `widgets/clear.zig` | Limpiar area |
|
|
| Input | `widgets/input.zig` | Campo de texto |
|
|
| TextArea | `widgets/textarea.zig` | Editor multi-linea |
|
|
| Popup | `widgets/popup.zig` | Ventana emergente |
|
|
| Modal | `widgets/popup.zig` | Dialogo modal |
|
|
| Menu | `widgets/menu.zig` | Menu desplegable |
|
|
| MenuBar | `widgets/menu.zig` | Barra de menu |
|
|
| ContextMenu | `widgets/menu.zig` | Menu contextual |
|
|
| Tooltip | `widgets/tooltip.zig` | Tooltips |
|
|
| Tree | `widgets/tree.zig` | Vista de arbol |
|
|
| FilePicker | `widgets/filepicker.zig` | Selector de archivos |
|
|
| ScrollView | `widgets/scroll.zig` | Vista con scroll |
|
|
| VirtualList | `widgets/scroll.zig` | Lista virtualizada |
|
|
| Panel | `widgets/panel.zig` | Panel basico |
|
|
| TabbedPanel | `widgets/panel.zig` | Panel con tabs |
|
|
| Checkbox | `widgets/checkbox.zig` | Checkbox |
|
|
| RadioGroup | `widgets/checkbox.zig` | Radio buttons |
|
|
| Select | `widgets/select.zig` | Dropdown select |
|
|
| MultiSelect | `widgets/select.zig` | Multi-select |
|
|
| Slider | `widgets/slider.zig` | Slider de valor |
|
|
| RangeSlider | `widgets/slider.zig` | Slider de rango |
|
|
| StatusBar | `widgets/statusbar.zig` | Barra de estado |
|
|
| Toast | `widgets/statusbar.zig` | Notificaciones toast |
|
|
| Spinner | `widgets/spinner.zig` | Indicador de carga |
|
|
| Help | `widgets/help.zig` | Panel de keybindings |
|
|
| Viewport | `widgets/viewport.zig` | Scroll generico |
|
|
| Progress | `widgets/progress.zig` | Progreso con ETA |
|
|
| Markdown | `widgets/markdown.zig` | Renderizado markdown |
|
|
| DirectoryTree | `widgets/dirtree.zig` | Navegador de archivos |
|
|
| SyntaxHighlighter | `widgets/syntax.zig` | Resaltado de codigo |
|
|
| Logo | `widgets/logo.zig` | Logo ASCII art |
|
|
|
|
### Block
|
|
|
|
```zig
|
|
const block = zcatui.widgets.Block.default()
|
|
.title("Title")
|
|
.titleBottom("Footer")
|
|
.titleAlignment(.center)
|
|
.borders(.all) // .none, .top, .bottom, .left, .right, .all
|
|
.borderType(.rounded) // .plain, .rounded, .double, .thick
|
|
.borderStyle(style)
|
|
.style(style);
|
|
|
|
block.render(area, buf);
|
|
const inner = block.inner(area); // area inside borders
|
|
```
|
|
|
|
### Paragraph
|
|
|
|
```zig
|
|
const para = zcatui.widgets.Paragraph.init(text)
|
|
.block(block)
|
|
.style(style)
|
|
.alignment(.center)
|
|
.wrap(.{ .trim = true }); // word wrapping
|
|
|
|
para.render(area, buf);
|
|
```
|
|
|
|
### List
|
|
|
|
```zig
|
|
const items = [_]zcatui.widgets.ListItem{
|
|
zcatui.widgets.ListItem.init("Item 1"),
|
|
zcatui.widgets.ListItem.init("Item 2").style(style),
|
|
};
|
|
|
|
var state = zcatui.widgets.ListState{};
|
|
state.select(0);
|
|
|
|
const list = zcatui.widgets.List.init(&items)
|
|
.block(block)
|
|
.highlightStyle(style)
|
|
.highlightSymbol("> ");
|
|
|
|
list.renderStateful(area, buf, &state);
|
|
|
|
// State methods
|
|
state.selected() // ?usize
|
|
state.select(index)
|
|
state.selectNext()
|
|
state.selectPrevious()
|
|
state.selectFirst()
|
|
state.selectLast()
|
|
```
|
|
|
|
### Table
|
|
|
|
```zig
|
|
const rows = [_]zcatui.widgets.TableRow{
|
|
zcatui.widgets.TableRow.init(&.{
|
|
zcatui.widgets.TableCell.init("Cell 1"),
|
|
zcatui.widgets.TableCell.init("Cell 2"),
|
|
}),
|
|
};
|
|
|
|
var state = zcatui.widgets.TableState{};
|
|
|
|
const table = zcatui.widgets.Table.init(&rows)
|
|
.block(block)
|
|
.header(header_row)
|
|
.widths(&.{ Constraint.percentage(50), Constraint.percentage(50) })
|
|
.highlightStyle(style)
|
|
.highlightSymbol("> ");
|
|
|
|
table.renderStateful(area, buf, &state);
|
|
```
|
|
|
|
### Gauge / LineGauge
|
|
|
|
```zig
|
|
// Full gauge
|
|
const gauge = zcatui.widgets.Gauge.default()
|
|
.ratio(0.5) // 0.0 - 1.0
|
|
.percent(50) // alternative: 0-100
|
|
.label("50%")
|
|
.gaugeStyle(style);
|
|
gauge.render(area, buf);
|
|
|
|
// Line gauge (single line)
|
|
const line_gauge = zcatui.widgets.LineGauge.default()
|
|
.ratio(0.75)
|
|
.lineSet(.thick) // .normal, .thick
|
|
.filledStyle(style)
|
|
.unfilledStyle(style);
|
|
line_gauge.render(area, buf);
|
|
```
|
|
|
|
### Input
|
|
|
|
```zig
|
|
var state = zcatui.widgets.InputState.init(allocator);
|
|
defer state.deinit();
|
|
|
|
const input = zcatui.widgets.Input.default()
|
|
.placeholder("Enter text...")
|
|
.style(style)
|
|
.cursorStyle(style);
|
|
|
|
input.renderStateful(area, buf, &state);
|
|
|
|
// State methods
|
|
state.insert(char)
|
|
state.deleteBack()
|
|
state.deleteForward()
|
|
state.moveCursorLeft()
|
|
state.moveCursorRight()
|
|
state.moveCursorStart()
|
|
state.moveCursorEnd()
|
|
state.getText() // []const u8
|
|
state.clear()
|
|
```
|
|
|
|
### Popup / Modal
|
|
|
|
```zig
|
|
// Popup (positioned)
|
|
const popup = zcatui.widgets.Popup.init()
|
|
.title("Popup")
|
|
.content("Content here")
|
|
.percentWidth(50)
|
|
.percentHeight(30);
|
|
popup.render(area, buf);
|
|
|
|
// Modal dialog
|
|
const modal = zcatui.widgets.Modal.init()
|
|
.title("Confirm")
|
|
.message("Are you sure?")
|
|
.buttons(&.{
|
|
zcatui.widgets.ModalButton.init("Yes", .confirm),
|
|
zcatui.widgets.ModalButton.init("No", .cancel),
|
|
});
|
|
modal.render(area, buf);
|
|
|
|
// Pre-built dialogs
|
|
zcatui.widgets.confirmDialog("Title", "Message")
|
|
zcatui.widgets.alertDialog("Title", "Message")
|
|
zcatui.widgets.yesNoCancelDialog("Title", "Message")
|
|
```
|
|
|
|
### Spinner
|
|
|
|
```zig
|
|
const spinner = zcatui.widgets.Spinner.init()
|
|
.spinnerStyle(.dots) // .dots, .line, .arc, .box, .bounce, etc (17 styles)
|
|
.label("Loading...")
|
|
.style(style);
|
|
spinner.render(area, buf, frame_count); // frame_count for animation
|
|
```
|
|
|
|
### Progress
|
|
|
|
```zig
|
|
const progress = zcatui.widgets.Progress.init()
|
|
.ratio(0.5)
|
|
.format(.percentage) // .percentage, .ratio, .bytes, .eta
|
|
.showEta(true)
|
|
.showSpeed(true)
|
|
.style(style);
|
|
progress.render(area, buf);
|
|
```
|
|
|
|
### Markdown
|
|
|
|
```zig
|
|
const md = zcatui.widgets.Markdown.init(
|
|
\\# Title
|
|
\\
|
|
\\Some **bold** and *italic* text.
|
|
\\
|
|
\\- List item 1
|
|
\\- List item 2
|
|
)
|
|
.theme(.default); // .default, .github, .dracula
|
|
|
|
md.render(area, buf);
|
|
```
|
|
|
|
### SyntaxHighlighter
|
|
|
|
```zig
|
|
const highlighter = zcatui.widgets.SyntaxHighlighter.init(code)
|
|
.language(.zig) // .zig, .rust, .python, .javascript, etc
|
|
.theme(.monokai) // .monokai, .dracula, .github
|
|
.showLineNumbers(true);
|
|
|
|
highlighter.render(area, buf);
|
|
```
|
|
|
|
### DirectoryTree
|
|
|
|
```zig
|
|
var tree = try zcatui.widgets.DirectoryTree.init(allocator, "/path/to/dir");
|
|
defer tree.deinit();
|
|
|
|
tree.toggleHidden(); // show/hide hidden files
|
|
tree.expand();
|
|
tree.collapse();
|
|
tree.selectNext();
|
|
tree.selectPrev();
|
|
tree.getSelectedPath() // ?[]const u8
|
|
|
|
tree.render(area, buf);
|
|
```
|
|
|
|
### Logo
|
|
|
|
```zig
|
|
const logo = zcatui.widgets.Logo.init()
|
|
.text(zcatui.widgets.predefined_logos.zcatui)
|
|
.style(style)
|
|
.animation(.pulse) // .none, .pulse, .wave, .rainbow
|
|
.alignment(.center);
|
|
|
|
logo.render(area, buf, frame_count);
|
|
```
|
|
|
|
---
|
|
|
|
## Events
|
|
|
|
### Event Types (`src/event.zig`)
|
|
|
|
```zig
|
|
const Event = union(enum) {
|
|
key: KeyEvent,
|
|
mouse: MouseEvent,
|
|
resize: ResizeEvent,
|
|
focus_gained,
|
|
focus_lost,
|
|
paste: []const u8,
|
|
};
|
|
```
|
|
|
|
### KeyEvent
|
|
|
|
```zig
|
|
// Handling
|
|
switch (event) {
|
|
.key => |key| {
|
|
// Check specific key
|
|
if (key.code == .esc) return;
|
|
if (key.code == .enter) submit();
|
|
if (key.code == .{ .char = 'q' }) quit();
|
|
if (key.code == .{ .f = 1 }) showHelp();
|
|
|
|
// Check modifiers
|
|
if (key.isCtrl() and key.getChar() == 'c') quit();
|
|
if (key.isAlt()) handleAlt(key);
|
|
|
|
// Get character
|
|
if (key.getChar()) |c| {
|
|
insertChar(c);
|
|
}
|
|
},
|
|
}
|
|
|
|
// KeyCode variants
|
|
.char: u21 // Unicode character
|
|
.f: u8 // F1-F12
|
|
.backspace, .enter, .tab, .backtab
|
|
.left, .right, .up, .down
|
|
.home, .end, .page_up, .page_down
|
|
.insert, .delete
|
|
.esc
|
|
|
|
// KeyModifiers
|
|
key.modifiers.ctrl
|
|
key.modifiers.alt
|
|
key.modifiers.shift
|
|
key.modifiers.super
|
|
```
|
|
|
|
### MouseEvent
|
|
|
|
```zig
|
|
switch (event) {
|
|
.mouse => |mouse| {
|
|
if (mouse.kind == .down and mouse.button == .left) {
|
|
handleClick(mouse.column, mouse.row);
|
|
}
|
|
if (mouse.kind == .scroll_up) scrollUp();
|
|
if (mouse.kind == .scroll_down) scrollDown();
|
|
if (mouse.kind == .drag) handleDrag(mouse);
|
|
},
|
|
}
|
|
|
|
// MouseEventKind
|
|
.down, .up, .drag, .moved
|
|
.scroll_down, .scroll_up, .scroll_left, .scroll_right
|
|
|
|
// MouseButton
|
|
.left, .right, .middle, .none
|
|
```
|
|
|
|
### Event Reader (`src/event/reader.zig`)
|
|
|
|
```zig
|
|
var reader = zcatui.EventReader.init();
|
|
|
|
// Poll with timeout (non-blocking)
|
|
if (try reader.poll(100)) |event| {
|
|
// handle event
|
|
}
|
|
|
|
// Read blocking
|
|
const event = try reader.read();
|
|
```
|
|
|
|
---
|
|
|
|
## Styling
|
|
|
|
### Color Examples
|
|
|
|
```zig
|
|
// ANSI 16 colors
|
|
Color.black, Color.red, Color.green, Color.yellow
|
|
Color.blue, Color.magenta, Color.cyan, Color.white
|
|
|
|
// 256 palette
|
|
Color.indexed(196) // bright red
|
|
Color.indexed(46) // bright green
|
|
Color.indexed(236) // dark gray
|
|
|
|
// True color (24-bit)
|
|
Color.rgb(255, 128, 0) // orange
|
|
Color.rgb(30, 30, 30) // dark background
|
|
```
|
|
|
|
### Style Composition
|
|
|
|
```zig
|
|
// Builder pattern
|
|
const title_style = Style{}
|
|
.fg(Color.yellow)
|
|
.bold();
|
|
|
|
const error_style = Style{}
|
|
.fg(Color.red)
|
|
.bg(Color.black)
|
|
.bold();
|
|
|
|
const selected = Style{}
|
|
.fg(Color.white)
|
|
.bg(Color.blue)
|
|
.reversed();
|
|
|
|
// Patching styles
|
|
const combined = base_style.patch(overlay_style);
|
|
```
|
|
|
|
---
|
|
|
|
## Animation
|
|
|
|
### Animation System (`src/animation.zig`)
|
|
|
|
```zig
|
|
// Basic animation
|
|
var anim = zcatui.Animation.init(0, 100, 1000); // from, to, duration_ms
|
|
anim.easing = zcatui.Easing.easeInOut;
|
|
|
|
while (!anim.isComplete()) {
|
|
const value = anim.getValue(); // current interpolated value
|
|
render(value);
|
|
anim.advance(delta_ms);
|
|
}
|
|
|
|
// Easing functions
|
|
zcatui.Easing.linear(t)
|
|
zcatui.Easing.easeIn(t)
|
|
zcatui.Easing.easeOut(t)
|
|
zcatui.Easing.easeInOut(t)
|
|
zcatui.Easing.easeInCubic(t)
|
|
zcatui.Easing.easeOutCubic(t)
|
|
zcatui.Easing.easeInOutCubic(t)
|
|
zcatui.Easing.easeInExpo(t)
|
|
zcatui.Easing.easeOutExpo(t)
|
|
zcatui.Easing.easeOutBounce(t)
|
|
|
|
// Timer
|
|
var timer = zcatui.Timer.init(1000); // 1000ms
|
|
if (timer.tick(delta_ms)) {
|
|
// Timer fired
|
|
}
|
|
timer.reset();
|
|
```
|
|
|
|
---
|
|
|
|
## Focus Management
|
|
|
|
### Focus System (`src/focus.zig`)
|
|
|
|
```zig
|
|
// FocusRing - manages focus within widget group
|
|
var ring = zcatui.FocusRing{};
|
|
ring.add(widget1.focusable());
|
|
ring.add(widget2.focusable());
|
|
|
|
ring.focusNext();
|
|
ring.focusPrev();
|
|
ring.focusFirst();
|
|
ring.focusLast();
|
|
|
|
if (ring.getFocused()) |focused| {
|
|
// render focused widget differently
|
|
}
|
|
|
|
// FocusManager - global focus management
|
|
var manager = zcatui.FocusManager.init(allocator);
|
|
defer manager.deinit();
|
|
|
|
manager.register("input1", input.focusable());
|
|
manager.setFocus("input1");
|
|
manager.moveFocus(.next);
|
|
manager.moveFocus(.prev);
|
|
|
|
// Focusable interface
|
|
pub const Focusable = struct {
|
|
ptr: *anyopaque,
|
|
vtable: *const VTable,
|
|
|
|
pub const VTable = struct {
|
|
onFocusChange: ?*const fn (*anyopaque, FocusEvent) void,
|
|
canFocus: *const fn (*anyopaque) bool,
|
|
getId: *const fn (*anyopaque) []const u8,
|
|
getOrder: ?*const fn (*anyopaque) i32,
|
|
getGroup: ?*const fn (*anyopaque) ?[]const u8,
|
|
};
|
|
};
|
|
|
|
// FocusDirection
|
|
.next, .prev, .up, .down, .left, .right
|
|
|
|
// FocusEvent
|
|
.gained, .lost
|
|
```
|
|
|
|
---
|
|
|
|
## Themes
|
|
|
|
### Theme System (`src/theme.zig`)
|
|
|
|
```zig
|
|
// Built-in themes
|
|
const theme = zcatui.Theme.default;
|
|
const theme = zcatui.Theme.dark;
|
|
const theme = zcatui.Theme.light;
|
|
const theme = zcatui.Theme.nord;
|
|
const theme = zcatui.Theme.dracula;
|
|
const theme = zcatui.Theme.solarized_dark;
|
|
const theme = zcatui.Theme.solarized_light;
|
|
const theme = zcatui.Theme.monokai;
|
|
const theme = zcatui.Theme.gruvbox;
|
|
const theme = zcatui.Theme.one_dark;
|
|
|
|
// Theme colors
|
|
theme.background
|
|
theme.foreground
|
|
theme.primary
|
|
theme.secondary
|
|
theme.success
|
|
theme.warning
|
|
theme.error_color
|
|
theme.info
|
|
theme.border
|
|
theme.border_focused
|
|
theme.selection_bg
|
|
theme.selection_fg
|
|
|
|
// Theme styles (return Style)
|
|
theme.default()
|
|
theme.primaryStyle()
|
|
theme.secondaryStyle()
|
|
theme.successStyle()
|
|
theme.warningStyle()
|
|
theme.errorStyle()
|
|
theme.infoStyle()
|
|
theme.disabledStyle()
|
|
theme.borderStyle()
|
|
theme.borderFocusedStyle()
|
|
theme.selectionStyle()
|
|
theme.highlightStyle()
|
|
theme.surfaceStyle()
|
|
theme.statusBarStyle()
|
|
```
|
|
|
|
### Theme Hot-Reload (`src/theme_loader.zig`)
|
|
|
|
```zig
|
|
var loader = zcatui.ThemeLoader.init(allocator);
|
|
defer loader.deinit();
|
|
|
|
// Load from file
|
|
const theme = try loader.loadFromFile("theme.json");
|
|
|
|
// Watch for changes
|
|
var watcher = try zcatui.ThemeWatcher.init(allocator, "theme.json");
|
|
defer watcher.deinit();
|
|
|
|
if (watcher.hasChanged()) {
|
|
theme = try loader.reload();
|
|
}
|
|
|
|
// Export theme
|
|
try zcatui.exportTheme(theme, "theme.json");
|
|
```
|
|
|
|
---
|
|
|
|
## Utilities
|
|
|
|
### Clipboard (`src/clipboard.zig`)
|
|
|
|
```zig
|
|
// Copy to clipboard (OSC 52)
|
|
try zcatui.Clipboard.copy(writer, "text to copy");
|
|
|
|
// Selection types
|
|
zcatui.ClipboardSelection.clipboard // system clipboard
|
|
zcatui.ClipboardSelection.primary // X11 primary
|
|
zcatui.ClipboardSelection.secondary // X11 secondary
|
|
```
|
|
|
|
### Hyperlinks (`src/hyperlink.zig`)
|
|
|
|
```zig
|
|
// Create clickable link (OSC 8)
|
|
const link = zcatui.Hyperlink.init("Click here", "https://example.com");
|
|
link.render(area, buf);
|
|
```
|
|
|
|
### Notifications (`src/notification.zig`)
|
|
|
|
```zig
|
|
// Send desktop notification (OSC 9/777)
|
|
try zcatui.Notification.send(writer, "Title", "Body");
|
|
```
|
|
|
|
### Images (`src/image.zig`)
|
|
|
|
```zig
|
|
// Kitty protocol
|
|
try zcatui.Kitty.display(writer, image_data, options);
|
|
|
|
// iTerm2 protocol
|
|
try zcatui.Iterm2.display(writer, image_data, options);
|
|
|
|
// Options
|
|
const options = zcatui.ImageOptions{
|
|
.width = 40,
|
|
.height = 20,
|
|
.format = .png,
|
|
};
|
|
```
|
|
|
|
### Unicode (`src/unicode.zig`)
|
|
|
|
```zig
|
|
zcatui.charWidth(codepoint) // u2 (0, 1, or 2)
|
|
zcatui.stringWidth("hello") // usize
|
|
zcatui.truncateToWidth(str, max) // []const u8
|
|
```
|
|
|
|
### Terminal Capabilities (`src/termcap.zig`)
|
|
|
|
```zig
|
|
const caps = zcatui.detectCapabilities();
|
|
|
|
caps.color_support // .none, .ansi16, .ansi256, .truecolor
|
|
caps.unicode // bool
|
|
caps.mouse // bool
|
|
caps.bracketed_paste // bool
|
|
caps.kitty_keyboard // bool
|
|
caps.sixel // bool
|
|
caps.kitty_graphics // bool
|
|
caps.iterm2_images // bool
|
|
```
|
|
|
|
### Lazy Rendering (`src/lazy.zig`)
|
|
|
|
```zig
|
|
// Render cache
|
|
var cache = zcatui.RenderCache.init(allocator);
|
|
defer cache.deinit();
|
|
|
|
if (cache.isValid(key)) {
|
|
// use cached
|
|
} else {
|
|
// render and cache
|
|
cache.store(key, rendered);
|
|
}
|
|
|
|
// Throttle
|
|
var throttle = zcatui.Throttle.init(16); // 16ms = ~60fps
|
|
if (throttle.shouldRun()) {
|
|
render();
|
|
}
|
|
|
|
// Debounce
|
|
var debounce = zcatui.Debounce.init(100); // 100ms delay
|
|
debounce.trigger();
|
|
if (debounce.isReady()) {
|
|
execute();
|
|
}
|
|
```
|
|
|
|
### Serialization (`src/serialize.zig`)
|
|
|
|
```zig
|
|
// State snapshot
|
|
var snapshot = zcatui.StateSnapshot.init(allocator);
|
|
defer snapshot.deinit();
|
|
snapshot.save("key", value);
|
|
const restored = snapshot.load("key", T);
|
|
|
|
// Undo stack
|
|
var undo = zcatui.UndoStack.init(allocator);
|
|
defer undo.deinit();
|
|
undo.push(state);
|
|
if (undo.undo()) |prev| { state = prev; }
|
|
if (undo.redo()) |next| { state = next; }
|
|
|
|
// JSON export
|
|
const json = try zcatui.toJson(allocator, value);
|
|
defer allocator.free(json);
|
|
```
|
|
|
|
### Accessibility (`src/accessibility.zig`)
|
|
|
|
```zig
|
|
// Accessible info
|
|
const info = zcatui.AccessibleInfo{
|
|
.role = .button,
|
|
.label = "Submit",
|
|
.description = "Submit the form",
|
|
};
|
|
|
|
// Announcer for screen readers
|
|
var announcer = zcatui.Announcer.init();
|
|
announcer.announce("Form submitted successfully");
|
|
|
|
// Check preferences
|
|
if (zcatui.prefersReducedMotion()) {
|
|
// disable animations
|
|
}
|
|
if (zcatui.prefersHighContrast()) {
|
|
theme = zcatui.high_contrast_theme;
|
|
}
|
|
|
|
// Skip links
|
|
var skip = zcatui.SkipLinks.init();
|
|
skip.add("main", "Skip to main content");
|
|
```
|
|
|
|
---
|
|
|
|
## v2.2 Features
|
|
|
|
### Resize Handler (`src/resize.zig`)
|
|
|
|
```zig
|
|
// Automatic (recommended)
|
|
term.enableAutoResize();
|
|
// Resize is handled automatically in draw()
|
|
|
|
// Manual
|
|
var handler = zcatui.ResizeHandler.init();
|
|
defer handler.deinit();
|
|
|
|
if (handler.hasResized()) {
|
|
const size = handler.getLastKnownSize();
|
|
try term.resize(size.width, size.height);
|
|
}
|
|
|
|
// Check pending
|
|
if (zcatui.resize.isResizePending()) {
|
|
// handle resize
|
|
}
|
|
```
|
|
|
|
### Drag & Drop (`src/drag.zig`)
|
|
|
|
```zig
|
|
// Drag state
|
|
var drag = zcatui.DragState{};
|
|
|
|
// On mouse down
|
|
drag.start(.resize, mouse.column, mouse.row);
|
|
|
|
// On mouse move
|
|
drag.update(mouse.column, mouse.row);
|
|
|
|
// Get delta
|
|
const dx = drag.deltaX(); // i32
|
|
const dy = drag.deltaY(); // i32
|
|
|
|
// On mouse up
|
|
drag.stop();
|
|
|
|
// Splitter panel
|
|
var splitter = zcatui.Splitter{
|
|
.direction = .vertical,
|
|
.position = 50, // percentage
|
|
};
|
|
|
|
const areas = splitter.split(area);
|
|
// areas.first, areas.second, areas.handle
|
|
|
|
// Adjust on drag
|
|
splitter.adjustPosition(area, delta);
|
|
```
|
|
|
|
### Debug Overlay (`src/debug.zig`)
|
|
|
|
```zig
|
|
var debug = zcatui.DebugOverlay.init();
|
|
debug.setEnabled(true);
|
|
debug.setFlags(.{
|
|
.show_fps = true,
|
|
.show_render_time = true,
|
|
.show_boundaries = true,
|
|
.show_mouse = true,
|
|
});
|
|
|
|
// In render loop
|
|
debug.beginFrame();
|
|
// ... render ...
|
|
debug.endFrame();
|
|
debug.render(area, buf);
|
|
|
|
// Log events
|
|
debug.logEvent("Button clicked: {s}", .{button_id});
|
|
|
|
// Get metrics
|
|
debug.current_fps // f32
|
|
debug.getFrameTimeMs() // f32
|
|
debug.widget_count // u32
|
|
|
|
// Global debug
|
|
zcatui.debug.toggleDebug();
|
|
zcatui.debug.isDebugEnabled();
|
|
```
|
|
|
|
### Profiler (`src/profile.zig`)
|
|
|
|
```zig
|
|
var profiler = zcatui.Profiler.init();
|
|
|
|
// Time a section
|
|
profiler.begin("render");
|
|
// ... code ...
|
|
profiler.end("render");
|
|
|
|
// Get stats
|
|
if (profiler.getStats("render")) |stats| {
|
|
stats.avg_ns // average time
|
|
stats.min_ns // minimum time
|
|
stats.max_ns // maximum time
|
|
stats.count // call count
|
|
}
|
|
|
|
// Scoped timer (RAII-style)
|
|
{
|
|
var timer = zcatui.ScopedTimer.start(&profiler, "section");
|
|
defer timer.stop();
|
|
// ... code ...
|
|
}
|
|
|
|
// Reset
|
|
profiler.reset();
|
|
```
|
|
|
|
### Diagnostic Messages (`src/diagnostic.zig`)
|
|
|
|
```zig
|
|
// Create diagnostic
|
|
const diag = zcatui.Diagnostic.err("INVALID VALUE", "Value must be positive")
|
|
.withSnippet(.{
|
|
.lines = &.{ "let x = -5;", " ^^^^" },
|
|
.start_line = 10,
|
|
.highlight_line = 0,
|
|
.highlight_col = 8,
|
|
.highlight_len = 2,
|
|
})
|
|
.withHint("Use a positive number instead")
|
|
.withSeeAlso("https://docs.example.com/values");
|
|
|
|
// Render to buffer
|
|
diag.render(area, buf);
|
|
|
|
// Format as string
|
|
const text = try diag.format(allocator);
|
|
defer allocator.free(text);
|
|
|
|
// Severity levels
|
|
zcatui.Severity.hint
|
|
zcatui.Severity.warning
|
|
zcatui.Severity.@"error"
|
|
|
|
// Pre-built diagnostics
|
|
zcatui.diagnostic.invalidPercentage(150)
|
|
zcatui.diagnostic.emptyConstraints()
|
|
zcatui.diagnostic.widgetOutOfBounds("Button")
|
|
zcatui.diagnostic.invalidColor("red", 300)
|
|
```
|
|
|
|
### Sixel Graphics (`src/sixel.zig`)
|
|
|
|
```zig
|
|
var encoder = zcatui.SixelEncoder.init(allocator);
|
|
defer encoder.deinit();
|
|
|
|
// Encode image
|
|
const pixels: []const zcatui.Pixel = // ...
|
|
const sixel_data = try encoder.encode(pixels, width, height);
|
|
defer allocator.free(sixel_data);
|
|
|
|
// Write to terminal
|
|
try writer.writeAll(sixel_data);
|
|
```
|
|
|
|
### Async Event Loop (`src/async_loop.zig`)
|
|
|
|
```zig
|
|
var loop = try zcatui.AsyncLoop.init(allocator);
|
|
defer loop.deinit();
|
|
|
|
// Add stdin
|
|
try loop.addStdin();
|
|
|
|
// Add timer (100ms interval)
|
|
const timer_id = try loop.addTimer(100);
|
|
|
|
// Add custom fd
|
|
try loop.addFd(fd, true, false); // readable, writable
|
|
|
|
// Event loop
|
|
while (running) {
|
|
const events = try loop.wait(1000); // 1s timeout
|
|
for (events) |event| {
|
|
switch (event.source) {
|
|
.stdin => handleInput(),
|
|
.timer => |id| if (id == timer_id) animate(),
|
|
.fd => |f| handleFd(f),
|
|
.signal => |sig| handleSignal(sig),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove timer
|
|
loop.removeTimer(timer_id);
|
|
|
|
// Ticker helper
|
|
var ticker = try zcatui.Ticker.init(&loop, 16); // 16ms = ~60fps
|
|
defer ticker.deinit();
|
|
```
|
|
|
|
### Shortcuts (`src/shortcuts.zig`)
|
|
|
|
```zig
|
|
var shortcuts = zcatui.ShortcutMap.init(allocator);
|
|
defer shortcuts.deinit();
|
|
|
|
// Register shortcuts
|
|
try shortcuts.register(.{
|
|
.key = .{ .char = 's' },
|
|
.modifiers = .{ .ctrl = true },
|
|
.action = .save,
|
|
.description = "Save file",
|
|
});
|
|
|
|
// Match event
|
|
if (shortcuts.match(key_event)) |action| {
|
|
switch (action) {
|
|
.save => saveFile(),
|
|
.quit => quit(),
|
|
// ...
|
|
}
|
|
}
|
|
|
|
// Context-aware shortcuts
|
|
var ctx = zcatui.ShortcutContext.init(allocator);
|
|
ctx.push("editor"); // enter editor context
|
|
// shortcuts for editor...
|
|
ctx.pop(); // exit context
|
|
```
|
|
|
|
### Widget Composition (`src/compose.zig`)
|
|
|
|
```zig
|
|
// Vertical stack
|
|
const areas = zcatui.vstack(area, &.{
|
|
Constraint.length(3),
|
|
Constraint.fill(),
|
|
Constraint.length(1),
|
|
});
|
|
|
|
// Horizontal stack
|
|
const cols = zcatui.hstack(area, &.{
|
|
Constraint.percentage(30),
|
|
Constraint.fill(),
|
|
});
|
|
|
|
// Splits
|
|
const split = zcatui.splitV(area, 50); // 50%
|
|
// split.first, split.second
|
|
|
|
const split3 = zcatui.splitV3(area, 3, 1); // header, footer sizes
|
|
// split3.top, split3.middle, split3.bottom
|
|
|
|
// Sizing
|
|
const sized = zcatui.sized(area, 40, 10); // width, height (centered)
|
|
|
|
// Flex children
|
|
const child = zcatui.flexChild(2); // flex: 2
|
|
const filler = zcatui.fillChild(); // flex: 1 (fill remaining)
|
|
const space = zcatui.spacer(5); // fixed 5 cells
|
|
```
|
|
|
|
---
|
|
|
|
## File Index
|
|
|
|
| File | Description |
|
|
|------|-------------|
|
|
| `src/root.zig` | Entry point, re-exports |
|
|
| `src/buffer.zig` | Buffer, Cell, Rect, Symbol |
|
|
| `src/style.zig` | Color, Style, Modifier |
|
|
| `src/text.zig` | Span, Line, Text |
|
|
| `src/layout.zig` | Layout, Constraint, Flex |
|
|
| `src/terminal.zig` | Terminal interface |
|
|
| `src/event.zig` | Event types |
|
|
| `src/event/reader.zig` | EventReader |
|
|
| `src/event/parse.zig` | Escape sequence parser |
|
|
| `src/animation.zig` | Animation, Easing, Timer |
|
|
| `src/focus.zig` | FocusRing, FocusManager |
|
|
| `src/theme.zig` | Theme definitions |
|
|
| `src/theme_loader.zig` | Theme hot-reload |
|
|
| `src/cursor.zig` | Cursor control |
|
|
| `src/clipboard.zig` | OSC 52 clipboard |
|
|
| `src/hyperlink.zig` | OSC 8 hyperlinks |
|
|
| `src/notification.zig` | OSC 9/777 notifications |
|
|
| `src/image.zig` | Kitty/iTerm2 images |
|
|
| `src/unicode.zig` | Unicode width |
|
|
| `src/termcap.zig` | Terminal capabilities |
|
|
| `src/lazy.zig` | Render cache, throttle |
|
|
| `src/testing.zig` | Widget testing framework |
|
|
| `src/serialize.zig` | JSON, undo/redo |
|
|
| `src/accessibility.zig` | A11y support |
|
|
| `src/resize.zig` | SIGWINCH handler |
|
|
| `src/drag.zig` | Drag state, Splitter |
|
|
| `src/debug.zig` | Debug overlay |
|
|
| `src/profile.zig` | Performance profiler |
|
|
| `src/diagnostic.zig` | Elm-style errors |
|
|
| `src/sixel.zig` | Sixel encoding |
|
|
| `src/async_loop.zig` | Async epoll loop |
|
|
| `src/shortcuts.zig` | Shortcut registry |
|
|
| `src/compose.zig` | Widget composition |
|
|
| `src/backend/backend.zig` | ANSI backend |
|
|
| `src/symbols/*.zig` | Unicode symbols |
|
|
| `src/widgets/*.zig` | All widgets |
|
|
|
|
---
|
|
|
|
## Examples
|
|
|
|
Los ejemplos estan en `examples/`:
|
|
|
|
```bash
|
|
zig build hello # Hello World basico
|
|
zig build events-demo # Manejo de eventos
|
|
zig build list-demo # Lista interactiva
|
|
zig build table-demo # Tabla con datos
|
|
zig build dashboard # Dashboard completo
|
|
zig build input-demo # Campos de entrada
|
|
zig build animation-demo # Animaciones
|
|
zig build clipboard-demo # Clipboard
|
|
zig build menu-demo # Menus
|
|
zig build form-demo # Formularios
|
|
zig build panel-demo # Paneles
|
|
zig build resize-demo # Manejo resize
|
|
zig build splitter-demo # Paneles redimensionables
|
|
zig build dirtree-demo # Navegador archivos
|
|
zig build spinner-demo # Spinners
|
|
zig build progress-demo # Barras progreso
|
|
zig build markdown-demo # Markdown
|
|
zig build syntax-demo # Syntax highlighting
|
|
zig build help-demo # Panel de ayuda
|
|
zig build viewport-demo # Scroll viewport
|
|
```
|
|
|
|
---
|
|
|
|
## Version
|
|
|
|
- **zcatui v2.2**
|
|
- **Zig 0.15.2**
|
|
- **70+ source files**
|
|
- **35 widgets**
|
|
- **186+ tests**
|
|
- **20 demos**
|
|
|
|
---
|
|
|
|
*Generated 2025-12-08*
|