New widgets (Phase 1-3): - Spinner: 10 animation styles (dots, line, arc, pulse, etc.) - Help: Keybinding display with categories - Viewport: Content scrolling (static/scrollable) - Progress: Multi-step progress with styles - Markdown: Basic markdown rendering (headers, lists, code) - DirectoryTree: File browser with icons and filters - SyntaxHighlighter: Code highlighting (Zig, Rust, Python, etc.) Innovation modules: - testing.zig: Widget testing framework (harness, simulated input, benchmarks) - theme_loader.zig: Theme hot-reload from JSON/KV files - serialize.zig: State serialization, undo/redo stack - accessibility.zig: A11y support (ARIA roles, screen reader, high contrast) Layout improvements: - Flex layout with JustifyContent and AlignItems Documentation: - TECHNICAL_REFERENCE.md: Comprehensive 1200+ line technical manual Stats: 67 files, 34 widgets, 250+ tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1260 lines
28 KiB
Markdown
1260 lines
28 KiB
Markdown
# zcatui v2.1 - Manual Técnico de Referencia
|
|
|
|
> Referencia completa de todos los módulos, widgets y funcionalidades implementadas.
|
|
|
|
---
|
|
|
|
## Índice
|
|
|
|
1. [Arquitectura General](#arquitectura-general)
|
|
2. [Módulos Core](#módulos-core)
|
|
3. [Widgets](#widgets)
|
|
4. [Sistema de Eventos](#sistema-de-eventos)
|
|
5. [Layout y Posicionamiento](#layout-y-posicionamiento)
|
|
6. [Themes y Estilos](#themes-y-estilos)
|
|
7. [Innovaciones v2.1](#innovaciones-v21)
|
|
8. [Utilidades](#utilidades)
|
|
|
|
---
|
|
|
|
## Arquitectura General
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Application │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Widgets (34 tipos) │ Layout │ Events │ Animation │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Buffer (diff) │ Style │ Text │ Symbols │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Terminal Backend │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Flujo de Renderizado
|
|
|
|
1. **Frame Start**: El usuario llama a `terminal.draw()`
|
|
2. **Widget Render**: Cada widget escribe al Buffer
|
|
3. **Diff**: Se calcula qué celdas cambiaron
|
|
4. **Output**: Solo los cambios van al terminal
|
|
|
|
---
|
|
|
|
## Módulos Core
|
|
|
|
### Buffer (`src/buffer.zig`)
|
|
|
|
El buffer es la representación en memoria de la pantalla.
|
|
|
|
```zig
|
|
const Buffer = @import("zcatui").Buffer;
|
|
const Rect = @import("zcatui").Rect;
|
|
|
|
// Crear buffer
|
|
var buf = try Buffer.init(allocator, Rect.init(0, 0, 80, 24));
|
|
defer buf.deinit();
|
|
|
|
// Obtener celda
|
|
if (buf.get(x, y)) |cell| {
|
|
// cell.symbol, cell.style
|
|
}
|
|
|
|
// Limpiar
|
|
buf.clear();
|
|
|
|
// Redimensionar
|
|
try buf.resize(new_rect);
|
|
```
|
|
|
|
**Tipos importantes:**
|
|
- `Cell`: Una celda con símbolo y estilo
|
|
- `Rect`: Rectángulo (x, y, width, height)
|
|
- `Symbol`: Carácter Unicode (hasta 7 bytes inline)
|
|
|
|
### Style (`src/style.zig`)
|
|
|
|
Sistema de estilos con colores y modificadores.
|
|
|
|
```zig
|
|
const Style = @import("zcatui").Style;
|
|
const Color = @import("zcatui").Color;
|
|
|
|
// Crear estilo
|
|
const style = Style.init()
|
|
.fg(Color.red)
|
|
.bg(Color.black)
|
|
.bold()
|
|
.italic()
|
|
.underline();
|
|
|
|
// Colores disponibles
|
|
Color.reset // Terminal default
|
|
Color.black // ANSI 0
|
|
Color.red // ANSI 1
|
|
Color.green // ANSI 2
|
|
Color.yellow // ANSI 3
|
|
Color.blue // ANSI 4
|
|
Color.magenta // ANSI 5
|
|
Color.cyan // ANSI 6
|
|
Color.white // ANSI 7
|
|
Color.indexed(n) // 256-color palette
|
|
Color.rgb(r,g,b) // True color
|
|
```
|
|
|
|
### Text (`src/text.zig`)
|
|
|
|
Texto con estilos inline.
|
|
|
|
```zig
|
|
const Text = @import("zcatui").Text;
|
|
const Line = @import("zcatui").Line;
|
|
const Span = @import("zcatui").Span;
|
|
|
|
// Texto simple
|
|
const text = Text.raw("Hello, World!");
|
|
|
|
// Con estilos
|
|
const styled = Text.fromSpans(&.{
|
|
Span.styled("Error: ", Style.init().fg(Color.red).bold()),
|
|
Span.raw("file not found"),
|
|
});
|
|
|
|
// Alineación
|
|
text.alignment(.center);
|
|
```
|
|
|
|
---
|
|
|
|
## Widgets
|
|
|
|
### Widgets Básicos
|
|
|
|
#### Block (`src/widgets/block.zig`)
|
|
|
|
Contenedor con bordes y títulos.
|
|
|
|
```zig
|
|
const Block = @import("zcatui").widgets.Block;
|
|
|
|
const block = Block.init()
|
|
.title("Mi Panel")
|
|
.borders(.all)
|
|
.borderType(.rounded)
|
|
.style(Style.init().fg(Color.blue));
|
|
|
|
block.render(area, buf);
|
|
```
|
|
|
|
#### Paragraph (`src/widgets/paragraph.zig`)
|
|
|
|
Texto con word-wrap.
|
|
|
|
```zig
|
|
const Paragraph = @import("zcatui").widgets.Paragraph;
|
|
|
|
const para = Paragraph.init(text)
|
|
.block(block)
|
|
.alignment(.center)
|
|
.wrap(.word);
|
|
|
|
para.render(area, buf);
|
|
```
|
|
|
|
#### List (`src/widgets/list.zig`)
|
|
|
|
Lista seleccionable.
|
|
|
|
```zig
|
|
const List = @import("zcatui").widgets.List;
|
|
const ListState = @import("zcatui").widgets.ListState;
|
|
|
|
var state = ListState{};
|
|
state.select(0);
|
|
|
|
const list = List.init(&items)
|
|
.block(block)
|
|
.highlightStyle(Style.init().bg(Color.blue))
|
|
.highlightSymbol("> ");
|
|
|
|
list.render(area, buf, &state);
|
|
|
|
// Navegación
|
|
state.selectNext(items.len);
|
|
state.selectPrevious(items.len);
|
|
```
|
|
|
|
#### Table (`src/widgets/table.zig`)
|
|
|
|
Tabla multi-columna.
|
|
|
|
```zig
|
|
const Table = @import("zcatui").widgets.Table;
|
|
const TableState = @import("zcatui").widgets.TableState;
|
|
|
|
var state = TableState{};
|
|
|
|
const table = Table.init(&rows)
|
|
.header(header_row)
|
|
.widths(&.{
|
|
.{ .percentage = 30 },
|
|
.{ .percentage = 70 },
|
|
})
|
|
.highlightStyle(highlight);
|
|
|
|
table.render(area, buf, &state);
|
|
```
|
|
|
|
### Widgets de Progreso
|
|
|
|
#### Gauge (`src/widgets/gauge.zig`)
|
|
|
|
Barra de progreso simple.
|
|
|
|
```zig
|
|
const Gauge = @import("zcatui").widgets.Gauge;
|
|
|
|
const gauge = Gauge.init()
|
|
.percent(75)
|
|
.label("75%")
|
|
.gaugeStyle(Style.init().fg(Color.green));
|
|
|
|
gauge.render(area, buf);
|
|
```
|
|
|
|
#### Progress (`src/widgets/progress.zig`) - **NUEVO v2.1**
|
|
|
|
Barra de progreso con ETA y velocidad.
|
|
|
|
```zig
|
|
const Progress = @import("zcatui").widgets.Progress;
|
|
|
|
var progress = Progress.init(100); // 100 items total
|
|
progress.start();
|
|
|
|
// En cada iteración
|
|
progress.tick(); // o progress.set(n);
|
|
|
|
// Obtener métricas
|
|
const eta = progress.etaSeconds();
|
|
const speed = progress.itemsPerSecond();
|
|
const elapsed = progress.elapsedSeconds();
|
|
|
|
progress.render(area, buf);
|
|
```
|
|
|
|
**Formatos:**
|
|
- `.bar`: Barra simple
|
|
- `.bar_with_percent`: Barra + porcentaje
|
|
- `.bar_with_eta`: Barra + ETA
|
|
- `.full`: Barra + porcentaje + ETA + velocidad
|
|
|
|
#### Spinner (`src/widgets/spinner.zig`) - **NUEVO v2.1**
|
|
|
|
Indicador de carga animado.
|
|
|
|
```zig
|
|
const Spinner = @import("zcatui").widgets.Spinner;
|
|
const SpinnerStyle = @import("zcatui").widgets.SpinnerStyle;
|
|
|
|
var spinner = Spinner.init(.dots); // 17 estilos disponibles
|
|
|
|
// En el loop de eventos
|
|
spinner.tick();
|
|
spinner.render(area, buf);
|
|
|
|
// Estilos disponibles:
|
|
// .dots, .dots2, .dots3, .line, .line2
|
|
// .pipe, .simple_dots, .simple_dots_scrolling
|
|
// .star, .star2, .flip, .hamburger
|
|
// .grow_vertical, .grow_horizontal, .balloon
|
|
// .noise, .bounce, .box_bounce
|
|
```
|
|
|
|
### Widgets de Navegación
|
|
|
|
#### Tabs (`src/widgets/tabs.zig`)
|
|
|
|
Pestañas de navegación.
|
|
|
|
```zig
|
|
const Tabs = @import("zcatui").widgets.Tabs;
|
|
|
|
const tabs = Tabs.init(&.{"Home", "Settings", "Help"})
|
|
.select(0)
|
|
.highlightStyle(Style.init().bold());
|
|
|
|
tabs.render(area, buf);
|
|
```
|
|
|
|
#### Menu (`src/widgets/menu.zig`)
|
|
|
|
Menús desplegables.
|
|
|
|
```zig
|
|
const Menu = @import("zcatui").widgets.Menu;
|
|
const MenuItem = @import("zcatui").widgets.MenuItem;
|
|
const MenuBar = @import("zcatui").widgets.MenuBar;
|
|
|
|
// Menú simple
|
|
const menu = Menu.init(&items)
|
|
.title("File")
|
|
.width(20);
|
|
|
|
// Barra de menú
|
|
const menubar = MenuBar.init(&.{
|
|
MenuBarItem.init("File", &file_items),
|
|
MenuBarItem.init("Edit", &edit_items),
|
|
});
|
|
```
|
|
|
|
#### Tree (`src/widgets/tree.zig`)
|
|
|
|
Vista de árbol.
|
|
|
|
```zig
|
|
const Tree = @import("zcatui").widgets.Tree;
|
|
const TreeItem = @import("zcatui").widgets.TreeItem;
|
|
const TreeState = @import("zcatui").widgets.TreeState;
|
|
|
|
var state = TreeState{};
|
|
|
|
const tree = Tree.init(&items)
|
|
.block(block)
|
|
.highlightStyle(highlight);
|
|
|
|
tree.render(area, buf, &state);
|
|
|
|
// Navegación
|
|
state.toggle(); // Expandir/colapsar
|
|
state.selectNext();
|
|
state.selectPrevious();
|
|
```
|
|
|
|
#### DirectoryTree (`src/widgets/dirtree.zig`) - **NUEVO v2.1**
|
|
|
|
Navegador de sistema de archivos.
|
|
|
|
```zig
|
|
const DirectoryTree = @import("zcatui").widgets.DirectoryTree;
|
|
|
|
var dirtree = try DirectoryTree.init(allocator, "/home/user");
|
|
defer dirtree.deinit();
|
|
|
|
// Cargar contenido
|
|
try dirtree.loadRoot();
|
|
|
|
// Navegación
|
|
dirtree.moveDown();
|
|
dirtree.moveUp();
|
|
dirtree.toggleExpand();
|
|
dirtree.goToParent();
|
|
dirtree.toggleHidden(); // Mostrar/ocultar archivos ocultos
|
|
|
|
// Obtener selección actual
|
|
if (dirtree.getSelected()) |node| {
|
|
// node.path, node.name, node.kind
|
|
}
|
|
|
|
dirtree.render(area, buf);
|
|
```
|
|
|
|
### Widgets de Entrada
|
|
|
|
#### Input (`src/widgets/input.zig`)
|
|
|
|
Campo de texto de una línea.
|
|
|
|
```zig
|
|
const Input = @import("zcatui").widgets.Input;
|
|
const InputState = @import("zcatui").widgets.InputState;
|
|
|
|
var state = InputState.init(allocator);
|
|
defer state.deinit();
|
|
|
|
const input = Input.init()
|
|
.placeholder("Enter name...")
|
|
.block(block);
|
|
|
|
input.render(area, buf, &state);
|
|
|
|
// Manejar eventos
|
|
state.handleKey(key_event);
|
|
const text = state.getText();
|
|
```
|
|
|
|
#### TextArea (`src/widgets/textarea.zig`)
|
|
|
|
Editor multi-línea.
|
|
|
|
```zig
|
|
const TextArea = @import("zcatui").widgets.TextArea;
|
|
|
|
var textarea = TextArea.init(allocator);
|
|
defer textarea.deinit();
|
|
|
|
textarea.render(area, buf);
|
|
textarea.handleKey(key_event);
|
|
```
|
|
|
|
#### Checkbox (`src/widgets/checkbox.zig`)
|
|
|
|
Casillas de verificación.
|
|
|
|
```zig
|
|
const Checkbox = @import("zcatui").widgets.Checkbox;
|
|
const RadioGroup = @import("zcatui").widgets.RadioGroup;
|
|
|
|
// Checkbox
|
|
const checkbox = Checkbox.init("Accept terms")
|
|
.checked(true);
|
|
|
|
// Radio group
|
|
var selected: usize = 0;
|
|
const radio = RadioGroup.init(&.{"Option A", "Option B", "Option C"}, &selected);
|
|
```
|
|
|
|
#### Slider (`src/widgets/slider.zig`)
|
|
|
|
Control deslizante.
|
|
|
|
```zig
|
|
const Slider = @import("zcatui").widgets.Slider;
|
|
|
|
var value: f32 = 0.5;
|
|
const slider = Slider.init(&value)
|
|
.min(0)
|
|
.max(100)
|
|
.step(1);
|
|
|
|
slider.render(area, buf);
|
|
```
|
|
|
|
### Widgets de Visualización
|
|
|
|
#### Chart (`src/widgets/chart.zig`)
|
|
|
|
Gráficos con ejes.
|
|
|
|
```zig
|
|
const Chart = @import("zcatui").widgets.Chart;
|
|
const Dataset = @import("zcatui").widgets.Dataset;
|
|
const Axis = @import("zcatui").widgets.Axis;
|
|
|
|
const chart = Chart.init(&.{
|
|
Dataset.init(&data_points)
|
|
.name("Series 1")
|
|
.graphType(.line)
|
|
.style(Style.init().fg(Color.cyan)),
|
|
})
|
|
.xAxis(Axis.init().title("Time"))
|
|
.yAxis(Axis.init().title("Value").bounds(0, 100));
|
|
|
|
chart.render(area, buf);
|
|
```
|
|
|
|
#### BarChart (`src/widgets/barchart.zig`)
|
|
|
|
Gráfico de barras.
|
|
|
|
```zig
|
|
const BarChart = @import("zcatui").widgets.BarChart;
|
|
const Bar = @import("zcatui").widgets.Bar;
|
|
|
|
const barchart = BarChart.init(&.{
|
|
Bar.init(75).label("Mon"),
|
|
Bar.init(90).label("Tue"),
|
|
Bar.init(60).label("Wed"),
|
|
})
|
|
.barWidth(5)
|
|
.barGap(1);
|
|
|
|
barchart.render(area, buf);
|
|
```
|
|
|
|
#### Sparkline (`src/widgets/sparkline.zig`)
|
|
|
|
Mini-gráficos.
|
|
|
|
```zig
|
|
const Sparkline = @import("zcatui").widgets.Sparkline;
|
|
|
|
const spark = Sparkline.init(&data)
|
|
.block(block)
|
|
.style(Style.init().fg(Color.yellow));
|
|
|
|
spark.render(area, buf);
|
|
```
|
|
|
|
#### Calendar (`src/widgets/calendar.zig`)
|
|
|
|
Calendario mensual.
|
|
|
|
```zig
|
|
const Monthly = @import("zcatui").widgets.Monthly;
|
|
const Date = @import("zcatui").widgets.Date;
|
|
|
|
const calendar = Monthly.init(2024, 12) // Año, mes
|
|
.showWeekNumbers(true)
|
|
.highlightToday(true);
|
|
|
|
calendar.render(area, buf);
|
|
```
|
|
|
|
#### Markdown (`src/widgets/markdown.zig`) - **NUEVO v2.1**
|
|
|
|
Renderizado de Markdown.
|
|
|
|
```zig
|
|
const Markdown = @import("zcatui").widgets.Markdown;
|
|
|
|
const md = Markdown.init(
|
|
\\# Título
|
|
\\
|
|
\\Texto con **negrita** y *cursiva*.
|
|
\\
|
|
\\- Item 1
|
|
\\- Item 2
|
|
\\
|
|
\\```zig
|
|
\\const x = 42;
|
|
\\```
|
|
);
|
|
|
|
md.render(area, buf);
|
|
```
|
|
|
|
**Soporta:**
|
|
- Headers (# ## ###)
|
|
- **Bold** y *Italic*
|
|
- Listas (- y números)
|
|
- Bloques de código
|
|
- Citas (>)
|
|
- Links [text](url)
|
|
- Líneas horizontales (---)
|
|
|
|
#### SyntaxHighlighter (`src/widgets/syntax.zig`) - **NUEVO v2.1**
|
|
|
|
Resaltado de sintaxis.
|
|
|
|
```zig
|
|
const SyntaxHighlighter = @import("zcatui").widgets.SyntaxHighlighter;
|
|
const SyntaxLanguage = @import("zcatui").widgets.SyntaxLanguage;
|
|
|
|
const highlighter = SyntaxHighlighter.init(.zig)
|
|
.showLineNumbers(true)
|
|
.highlightLine(5); // Resaltar línea 5
|
|
|
|
highlighter.render(code, area, buf);
|
|
```
|
|
|
|
**Lenguajes soportados:**
|
|
- Zig, Rust, Go, C, C++
|
|
- Python, JavaScript, TypeScript
|
|
- JSON, Bash
|
|
|
|
### Widgets de Overlay
|
|
|
|
#### Popup (`src/widgets/popup.zig`)
|
|
|
|
Ventanas emergentes y modales.
|
|
|
|
```zig
|
|
const Popup = @import("zcatui").widgets.Popup;
|
|
const Modal = @import("zcatui").widgets.Modal;
|
|
|
|
// Popup simple
|
|
const popup = Popup.init()
|
|
.title("Info")
|
|
.content("Message here")
|
|
.percentSize(50, 30);
|
|
|
|
popup.render(area, buf);
|
|
|
|
// Modal con botones
|
|
const modal = Modal.confirm("Delete file?", &on_confirm);
|
|
modal.render(area, buf);
|
|
|
|
// Helpers
|
|
const confirm = confirmDialog("Sure?", &callback);
|
|
const alert = alertDialog("Error!", &callback);
|
|
```
|
|
|
|
#### Tooltip (`src/widgets/tooltip.zig`)
|
|
|
|
Tooltips posicionales.
|
|
|
|
```zig
|
|
const Tooltip = @import("zcatui").widgets.Tooltip;
|
|
const TooltipPosition = @import("zcatui").widgets.TooltipPosition;
|
|
|
|
const tooltip = Tooltip.init("Help text")
|
|
.position(.below)
|
|
.style(Style.init().bg(Color.yellow));
|
|
|
|
tooltip.render(anchor_x, anchor_y, buf);
|
|
```
|
|
|
|
### Widgets de Contenedor
|
|
|
|
#### Viewport (`src/widgets/viewport.zig`) - **NUEVO v2.1**
|
|
|
|
Contenido scrollable.
|
|
|
|
```zig
|
|
const Viewport = @import("zcatui").widgets.Viewport;
|
|
const ViewportState = @import("zcatui").widgets.ViewportState;
|
|
|
|
var state = ViewportState{};
|
|
|
|
var viewport = try Viewport.init(allocator, 100, 500); // content width x height
|
|
defer viewport.deinit();
|
|
|
|
// Renderizar contenido al buffer interno
|
|
const content_buf = viewport.buffer();
|
|
// ... render widgets to content_buf ...
|
|
|
|
// Scroll
|
|
viewport.scrollDown(1);
|
|
viewport.scrollUp(1);
|
|
viewport.pageDown(area.height);
|
|
viewport.pageUp(area.height);
|
|
|
|
viewport.render(area, buf);
|
|
```
|
|
|
|
#### ScrollView (`src/widgets/scroll.zig`)
|
|
|
|
Vista con scroll y virtualización.
|
|
|
|
```zig
|
|
const ScrollView = @import("zcatui").widgets.ScrollView;
|
|
const VirtualList = @import("zcatui").widgets.VirtualList;
|
|
|
|
// Lista virtual (solo renderiza items visibles)
|
|
const virtual = VirtualList.init(total_items, item_height, render_fn);
|
|
virtual.render(area, buf, &scroll_state);
|
|
```
|
|
|
|
#### Panel (`src/widgets/panel.zig`)
|
|
|
|
Paneles divididos.
|
|
|
|
```zig
|
|
const Panel = @import("zcatui").widgets.Panel;
|
|
const PanelSplit = @import("zcatui").widgets.PanelSplit;
|
|
const TabbedPanel = @import("zcatui").widgets.TabbedPanel;
|
|
|
|
// Panel split
|
|
const split = PanelSplit.horizontal()
|
|
.ratio(0.3) // 30% izquierda
|
|
.gap(1);
|
|
|
|
const areas = split.split(area);
|
|
// areas[0] = izquierda, areas[1] = derecha
|
|
|
|
// Tabbed panel
|
|
const tabbed = TabbedPanel.init(&.{"Tab1", "Tab2"});
|
|
tabbed.render(area, buf, &state);
|
|
```
|
|
|
|
### Widgets de Estado
|
|
|
|
#### StatusBar (`src/widgets/statusbar.zig`)
|
|
|
|
Barra de estado.
|
|
|
|
```zig
|
|
const StatusBar = @import("zcatui").widgets.StatusBar;
|
|
const StatusBarBuilder = @import("zcatui").widgets.StatusBarBuilder;
|
|
|
|
const status = StatusBarBuilder.init()
|
|
.left("NORMAL")
|
|
.center("file.zig")
|
|
.right("Ln 42, Col 8")
|
|
.build();
|
|
|
|
status.render(area, buf);
|
|
```
|
|
|
|
#### Toast (`src/widgets/statusbar.zig`)
|
|
|
|
Notificaciones temporales.
|
|
|
|
```zig
|
|
const Toast = @import("zcatui").widgets.Toast;
|
|
const ToastManager = @import("zcatui").widgets.ToastManager;
|
|
|
|
var toasts = ToastManager.init(allocator);
|
|
defer toasts.deinit();
|
|
|
|
try toasts.show("File saved!", .success, 3000); // 3 segundos
|
|
try toasts.show("Error!", .err, 5000);
|
|
|
|
toasts.render(area, buf);
|
|
```
|
|
|
|
#### Help (`src/widgets/help.zig`) - **NUEVO v2.1**
|
|
|
|
Auto-genera ayuda de keybindings.
|
|
|
|
```zig
|
|
const Help = @import("zcatui").widgets.Help;
|
|
const KeyBinding = @import("zcatui").widgets.KeyBinding;
|
|
const CommonBindings = @import("zcatui").widgets.CommonBindings;
|
|
|
|
const help = Help.init(&.{
|
|
KeyBinding.init("q", "Quit"),
|
|
KeyBinding.init("?", "Help"),
|
|
KeyBinding.init("j/k", "Up/Down"),
|
|
})
|
|
.mode(.compact) // .single_line, .multi_line, .full
|
|
.separator(" | ");
|
|
|
|
help.render(area, buf);
|
|
|
|
// Bindings comunes predefinidos
|
|
const common = CommonBindings.vim(); // o .emacs(), .arrows()
|
|
```
|
|
|
|
---
|
|
|
|
## Sistema de Eventos
|
|
|
|
### EventReader (`src/event/reader.zig`)
|
|
|
|
```zig
|
|
const EventReader = @import("zcatui").EventReader;
|
|
const Event = @import("zcatui").Event;
|
|
const KeyCode = @import("zcatui").KeyCode;
|
|
|
|
var reader = try EventReader.init();
|
|
defer reader.deinit();
|
|
|
|
// Polling
|
|
if (try reader.poll(100)) { // timeout ms
|
|
const event = try reader.read();
|
|
|
|
switch (event) {
|
|
.key => |k| {
|
|
if (k.code == .char and k.getChar() == 'q') {
|
|
// Quit
|
|
}
|
|
if (k.code == .enter) {
|
|
// Enter pressed
|
|
}
|
|
if (k.isCtrl() and k.getChar() == 'c') {
|
|
// Ctrl+C
|
|
}
|
|
},
|
|
.mouse => |m| {
|
|
// m.column, m.row, m.kind, m.button
|
|
},
|
|
.resize => |r| {
|
|
// r.width, r.height
|
|
},
|
|
.focus_gained, .focus_lost => {},
|
|
}
|
|
}
|
|
```
|
|
|
|
### KeyCode Values
|
|
|
|
```zig
|
|
KeyCode.enter
|
|
KeyCode.tab
|
|
KeyCode.backspace
|
|
KeyCode.escape
|
|
KeyCode.left, .right, .up, .down
|
|
KeyCode.home, .end
|
|
KeyCode.page_up, .page_down
|
|
KeyCode.insert, .delete
|
|
KeyCode.f1 ... KeyCode.f12
|
|
KeyCode.char // con .getChar()
|
|
```
|
|
|
|
---
|
|
|
|
## Layout y Posicionamiento
|
|
|
|
### Layout (`src/layout.zig`)
|
|
|
|
```zig
|
|
const Layout = @import("zcatui").Layout;
|
|
const Constraint = @import("zcatui").Constraint;
|
|
const Direction = @import("zcatui").Direction;
|
|
|
|
// Dividir área
|
|
const chunks = Layout.init(.vertical)
|
|
.constraints(&.{
|
|
Constraint.length(3), // 3 filas fijas
|
|
Constraint.percentage(50), // 50% del resto
|
|
Constraint.min(10), // Mínimo 10
|
|
Constraint.max(20), // Máximo 20
|
|
Constraint.ratio(1, 3), // 1/3
|
|
})
|
|
.split(area);
|
|
```
|
|
|
|
### Flex Layout - **NUEVO v2.1**
|
|
|
|
Layout CSS-like con justify y align.
|
|
|
|
```zig
|
|
const Flex = @import("zcatui").Flex;
|
|
const JustifyContent = @import("zcatui").JustifyContent;
|
|
const AlignItems = @import("zcatui").AlignItems;
|
|
|
|
const flex = Flex.horizontal()
|
|
.setJustify(.space_between) // .start, .end, .center, .space_around, .space_evenly
|
|
.setAlign(.center) // .start, .end, .center, .stretch
|
|
.setGap(2)
|
|
.setSizes(&.{20, 30, 20}); // Tamaños de items
|
|
|
|
const rects = flex.layout(area);
|
|
// rects[0], rects[1], rects[2]...
|
|
```
|
|
|
|
### Helpers de Posicionamiento
|
|
|
|
```zig
|
|
const centerRect = @import("zcatui").centerRect;
|
|
const alignBottom = @import("zcatui").alignBottom;
|
|
const alignRight = @import("zcatui").alignRight;
|
|
const alignBottomRight = @import("zcatui").alignBottomRight;
|
|
|
|
// Centrar un área de 40x10 dentro de parent
|
|
const centered = centerRect(parent, 40, 10);
|
|
|
|
// Alinear al fondo
|
|
const bottom = alignBottom(parent, 3); // 3 filas de alto
|
|
|
|
// Alinear a la derecha
|
|
const right_area = alignRight(parent, 20); // 20 columnas de ancho
|
|
```
|
|
|
|
---
|
|
|
|
## Themes y Estilos
|
|
|
|
### Theme (`src/theme.zig`)
|
|
|
|
```zig
|
|
const Theme = @import("zcatui").Theme;
|
|
|
|
const theme = Theme{
|
|
.background = Color.black,
|
|
.foreground = Color.white,
|
|
.primary = Color.blue,
|
|
.secondary = Color.magenta,
|
|
.success = Color.green,
|
|
.warning = Color.yellow,
|
|
.error_color = Color.red,
|
|
.info = Color.cyan,
|
|
.border = Color.indexed(240),
|
|
// ... más colores
|
|
};
|
|
|
|
// Usar theme para estilos
|
|
const button_style = theme.buttonStyle();
|
|
const input_style = theme.inputStyle();
|
|
```
|
|
|
|
### ThemeLoader - **NUEVO v2.1**
|
|
|
|
Hot-reload de themes desde archivos.
|
|
|
|
```zig
|
|
const ThemeLoader = @import("zcatui").ThemeLoader;
|
|
const ThemeWatcher = @import("zcatui").ThemeWatcher;
|
|
|
|
// Cargar theme
|
|
var loader = try ThemeLoader.init(allocator, "theme.json");
|
|
defer loader.deinit();
|
|
|
|
const theme = loader.getTheme();
|
|
|
|
// Watcher con auto-reload
|
|
var watcher = try ThemeWatcher.init(allocator, "theme.json", 1000); // check cada 1s
|
|
defer watcher.deinit();
|
|
|
|
// En el event loop
|
|
if (watcher.poll()) {
|
|
// Theme changed, re-render
|
|
}
|
|
```
|
|
|
|
**Formato JSON:**
|
|
```json
|
|
{
|
|
"primary": "#3498db",
|
|
"secondary": "#9b59b6",
|
|
"success": "green",
|
|
"warning": "#f1c40f",
|
|
"error": "red"
|
|
}
|
|
```
|
|
|
|
**Formato KV:**
|
|
```
|
|
# theme.conf
|
|
primary = #3498db
|
|
secondary = magenta
|
|
success = green
|
|
```
|
|
|
|
---
|
|
|
|
## Innovaciones v2.1
|
|
|
|
### Testing Framework (`src/testing.zig`)
|
|
|
|
Framework para tests de widgets.
|
|
|
|
```zig
|
|
const testing = @import("zcatui").testing_framework;
|
|
const WidgetHarness = @import("zcatui").WidgetHarness;
|
|
const SimulatedInput = @import("zcatui").SimulatedInput;
|
|
const Benchmark = @import("zcatui").Benchmark;
|
|
|
|
test "widget renders correctly" {
|
|
var harness = WidgetHarness.init(std.testing.allocator, 40, 10);
|
|
defer harness.deinit();
|
|
|
|
// Renderizar widget
|
|
const widget = MyWidget.init();
|
|
harness.render(widget);
|
|
|
|
// Assertions
|
|
try harness.expectText(0, 0, "Hello");
|
|
try harness.expectFg(0, 0, Color.red);
|
|
try harness.expectNotEmpty(area);
|
|
|
|
// Debug
|
|
harness.debugPrint();
|
|
}
|
|
|
|
test "keyboard input" {
|
|
// Simular eventos
|
|
const key = SimulatedInput.key(.enter);
|
|
const char = SimulatedInput.char('a');
|
|
const click = SimulatedInput.click(10, 5, .left);
|
|
const ctrl_c = SimulatedInput.keyWithMod(.{ .char = 'c' }, true, false, false);
|
|
}
|
|
|
|
test "benchmark" {
|
|
var bench = Benchmark.start();
|
|
|
|
var i: u32 = 0;
|
|
while (i < 1000) : (i += 1) {
|
|
widget.render(area, buf);
|
|
bench.lap();
|
|
}
|
|
|
|
bench.report("Widget render"); // Imprime stats
|
|
}
|
|
```
|
|
|
|
### Serialization (`src/serialize.zig`)
|
|
|
|
Guardar y restaurar estados de widgets.
|
|
|
|
```zig
|
|
const serialize = @import("zcatui").serialize;
|
|
const StateSnapshot = @import("zcatui").StateSnapshot;
|
|
const UndoStack = @import("zcatui").UndoStack;
|
|
const toJson = @import("zcatui").toJson;
|
|
|
|
// Serializar a JSON
|
|
const json = try toJson(allocator, my_state);
|
|
defer allocator.free(json);
|
|
|
|
// Snapshot de múltiples estados
|
|
var snapshot = StateSnapshot.init(allocator);
|
|
defer snapshot.deinit();
|
|
|
|
try snapshot.save("list", list_state);
|
|
try snapshot.save("table", table_state);
|
|
|
|
// Exportar todo
|
|
const all_json = try snapshot.exportAll();
|
|
|
|
// Undo/Redo
|
|
var undo = UndoStack(MyState).init(allocator, 50); // max 50 items
|
|
defer undo.deinit();
|
|
|
|
try undo.push(current_state);
|
|
|
|
if (undo.canUndo()) {
|
|
const prev = undo.undo();
|
|
}
|
|
if (undo.canRedo()) {
|
|
const next = undo.redo();
|
|
}
|
|
```
|
|
|
|
### Accessibility (`src/accessibility.zig`)
|
|
|
|
Soporte para lectores de pantalla y preferencias.
|
|
|
|
```zig
|
|
const a11y = @import("zcatui").accessibility;
|
|
const AccessibleInfo = @import("zcatui").AccessibleInfo;
|
|
const A11yRole = @import("zcatui").A11yRole;
|
|
const Announcer = @import("zcatui").Announcer;
|
|
|
|
// Verificar preferencias
|
|
if (a11y.prefersReducedMotion()) {
|
|
// Desactivar animaciones
|
|
}
|
|
if (a11y.prefersHighContrast()) {
|
|
theme = a11y.high_contrast_theme;
|
|
}
|
|
|
|
// Información accesible para widget
|
|
const info = AccessibleInfo{
|
|
.role = .button,
|
|
.label = "Submit",
|
|
.disabled = false,
|
|
.shortcut = "Enter",
|
|
};
|
|
|
|
// Anuncios para screen readers
|
|
var announcer = Announcer.init(allocator);
|
|
defer announcer.deinit();
|
|
|
|
try announcer.announce("Selection changed");
|
|
const output = try announcer.flush();
|
|
// Escribir output al terminal
|
|
|
|
// Skip links para navegación
|
|
var links = a11y.SkipLinks.init(allocator);
|
|
try links.register("main", "Main content", 10);
|
|
try links.register("footer", "Footer", 50);
|
|
```
|
|
|
|
---
|
|
|
|
## Utilidades
|
|
|
|
### Unicode (`src/unicode.zig`)
|
|
|
|
Cálculo de ancho de caracteres.
|
|
|
|
```zig
|
|
const unicode = @import("zcatui").unicode;
|
|
|
|
const width = unicode.charWidth('漢'); // 2 (full-width)
|
|
const str_width = unicode.stringWidth("Hello 世界"); // 11
|
|
|
|
const truncated = unicode.truncateToWidth("Long text here", 10); // "Long text "
|
|
```
|
|
|
|
### Terminal Capabilities (`src/termcap.zig`)
|
|
|
|
Detección de capacidades del terminal.
|
|
|
|
```zig
|
|
const termcap = @import("zcatui").termcap;
|
|
|
|
const caps = termcap.detect();
|
|
|
|
if (caps.color_support == .true_color) {
|
|
// Usar colores RGB
|
|
}
|
|
if (caps.unicode) {
|
|
// Usar caracteres Unicode
|
|
}
|
|
if (caps.mouse) {
|
|
// Habilitar mouse
|
|
}
|
|
```
|
|
|
|
### Animation (`src/animation.zig`)
|
|
|
|
Sistema de animaciones.
|
|
|
|
```zig
|
|
const Animation = @import("zcatui").Animation;
|
|
const Easing = @import("zcatui").Easing;
|
|
const Timer = @import("zcatui").Timer;
|
|
|
|
var anim = Animation.init(0, 100, 1000, .ease_out); // from, to, duration_ms, easing
|
|
|
|
// En cada frame
|
|
anim.update(delta_ms);
|
|
const current_value = anim.value();
|
|
|
|
if (anim.isFinished()) {
|
|
// Done
|
|
}
|
|
|
|
// Timer
|
|
var timer = Timer.init(5000); // 5 segundos
|
|
timer.start();
|
|
|
|
if (timer.isExpired()) {
|
|
// Timeout
|
|
}
|
|
```
|
|
|
|
### Clipboard (`src/clipboard.zig`)
|
|
|
|
Acceso al portapapeles (OSC 52).
|
|
|
|
```zig
|
|
const Clipboard = @import("zcatui").Clipboard;
|
|
|
|
// Copiar
|
|
const seq = Clipboard.copy("Hello");
|
|
// Escribir seq al terminal
|
|
|
|
// Pegar (solicitar)
|
|
const req = Clipboard.requestPaste();
|
|
// Escribir req, leer respuesta
|
|
```
|
|
|
|
### Hyperlinks (`src/hyperlink.zig`)
|
|
|
|
Links clickeables (OSC 8).
|
|
|
|
```zig
|
|
const Hyperlink = @import("zcatui").Hyperlink;
|
|
|
|
const link = Hyperlink.init("https://example.com", "Click here");
|
|
// link.start() ... text ... link.end()
|
|
```
|
|
|
|
### Notifications (`src/notification.zig`)
|
|
|
|
Notificaciones de escritorio (OSC 9/777).
|
|
|
|
```zig
|
|
const Notification = @import("zcatui").Notification;
|
|
|
|
const notif = Notification.init("Title", "Body text");
|
|
// Escribir notif.sequence() al terminal
|
|
```
|
|
|
|
### Images (`src/image.zig`)
|
|
|
|
Imágenes en terminal (Kitty/iTerm2).
|
|
|
|
```zig
|
|
const Kitty = @import("zcatui").Kitty;
|
|
const Iterm2 = @import("zcatui").Iterm2;
|
|
|
|
// Kitty protocol
|
|
const kitty = Kitty.init(image_data, width, height);
|
|
kitty.render(buf);
|
|
|
|
// iTerm2 protocol
|
|
const iterm = Iterm2.init(image_data);
|
|
iterm.render(buf);
|
|
```
|
|
|
|
---
|
|
|
|
## Archivos del Proyecto
|
|
|
|
```
|
|
src/
|
|
├── root.zig # Entry point, re-exports
|
|
├── buffer.zig # Buffer, Cell, Rect, Symbol
|
|
├── style.zig # Color, Style, Modifier
|
|
├── text.zig # Text, Line, Span
|
|
├── layout.zig # Layout, Constraint, Flex
|
|
├── terminal.zig # Terminal abstraction
|
|
├── event.zig # Event, KeyEvent, MouseEvent
|
|
├── event/
|
|
│ ├── reader.zig # EventReader
|
|
│ └── parse.zig # Escape parser
|
|
├── focus.zig # FocusRing, FocusManager
|
|
├── theme.zig # Theme definitions
|
|
├── animation.zig # Animation, Easing, Timer
|
|
├── cursor.zig # Cursor control
|
|
├── clipboard.zig # OSC 52
|
|
├── hyperlink.zig # OSC 8
|
|
├── notification.zig # OSC 9/777
|
|
├── image.zig # Kitty/iTerm2 images
|
|
├── lazy.zig # RenderCache, Throttle
|
|
├── unicode.zig # charWidth, stringWidth
|
|
├── termcap.zig # Terminal capabilities
|
|
├── testing.zig # Widget testing framework [NEW v2.1]
|
|
├── theme_loader.zig # Theme hot-reload [NEW v2.1]
|
|
├── serialize.zig # State serialization [NEW v2.1]
|
|
├── accessibility.zig # A11y support [NEW v2.1]
|
|
├── backend/
|
|
│ └── backend.zig # ANSI sequences
|
|
├── symbols/ # Line, border, block chars
|
|
└── widgets/
|
|
├── block.zig
|
|
├── paragraph.zig
|
|
├── list.zig
|
|
├── table.zig
|
|
├── gauge.zig
|
|
├── tabs.zig
|
|
├── sparkline.zig
|
|
├── scrollbar.zig
|
|
├── barchart.zig
|
|
├── canvas.zig
|
|
├── chart.zig
|
|
├── calendar.zig
|
|
├── clear.zig
|
|
├── input.zig
|
|
├── textarea.zig
|
|
├── popup.zig
|
|
├── menu.zig
|
|
├── tooltip.zig
|
|
├── tree.zig
|
|
├── filepicker.zig
|
|
├── scroll.zig
|
|
├── panel.zig
|
|
├── checkbox.zig
|
|
├── select.zig
|
|
├── slider.zig
|
|
├── statusbar.zig
|
|
├── spinner.zig [NEW v2.1]
|
|
├── help.zig [NEW v2.1]
|
|
├── viewport.zig [NEW v2.1]
|
|
├── progress.zig [NEW v2.1]
|
|
├── markdown.zig [NEW v2.1]
|
|
├── dirtree.zig [NEW v2.1]
|
|
└── syntax.zig [NEW v2.1]
|
|
```
|
|
|
|
---
|
|
|
|
## Estadísticas v2.1
|
|
|
|
| Métrica | Valor |
|
|
|---------|-------|
|
|
| Archivos fuente | 67 archivos .zig |
|
|
| Widgets | 34 widgets |
|
|
| Módulos core | 20 módulos |
|
|
| Tests | 250+ tests |
|
|
| Examples | 11 demos |
|
|
| Nuevos en v2.1 | 11 módulos/widgets |
|
|
|
|
---
|
|
|
|
## Changelog v2.1
|
|
|
|
### Nuevos Widgets
|
|
- **Spinner**: Indicadores de carga animados (17 estilos)
|
|
- **Help**: Auto-genera ayuda de keybindings
|
|
- **Viewport**: Scroll genérico con buffer interno
|
|
- **Progress**: Barras de progreso con ETA y velocidad
|
|
- **Markdown**: Renderizado de Markdown styled
|
|
- **DirectoryTree**: Navegador de archivos
|
|
- **SyntaxHighlighter**: Resaltado de sintaxis (10 lenguajes)
|
|
|
|
### Nuevas Funcionalidades
|
|
- **Flex Layout**: CSS-like justify/align
|
|
- **Widget Testing Framework**: Harness, assertions, benchmarks
|
|
- **Theme Hot-Reload**: Cargar themes desde archivos
|
|
- **Widget Serialization**: JSON, undo/redo, snapshots
|
|
- **Accessibility**: Roles ARIA, announcements, high contrast
|
|
|
|
---
|
|
|
|
*Generado para zcatui v2.1 - Diciembre 2024*
|