diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md new file mode 100644 index 0000000..e4736c5 --- /dev/null +++ b/docs/API_REFERENCE.md @@ -0,0 +1,1400 @@ +# 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*