Librería TUI inspirada en ratatui (Rust), implementada en Zig 0.15.2. Estructura inicial: - src/style.zig: Color, Style, Modifier - src/buffer.zig: Cell, Buffer, Rect - src/layout.zig: Layout, Constraint, Direction - src/terminal.zig: Terminal abstraction - src/backend/backend.zig: ANSI escape sequences - src/widgets/block.zig: Block con borders y título - src/widgets/paragraph.zig: Paragraph con wrapping - examples/hello.zig: Demo funcional 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
462 lines
13 KiB
Markdown
462 lines
13 KiB
Markdown
# zcatui - TUI Library para Zig
|
|
|
|
> **Última actualización**: 2025-12-08
|
|
> **Lenguaje**: Zig 0.15.2
|
|
> **Inspiración**: [ratatui](https://github.com/ratatui/ratatui) (Rust TUI library)
|
|
|
|
## Descripción del Proyecto
|
|
|
|
**zcatui** es una librería para crear interfaces de usuario en terminal (TUI) en Zig puro, inspirada en ratatui de Rust.
|
|
|
|
**Objetivo**: Proveer una API idiomática Zig para construir aplicaciones TUI con widgets, layouts, y estilos, manteniendo la filosofía de Zig: simple, explícito, y sin magia.
|
|
|
|
**Nombre**: "zcat" + "ui" (un guiño a ratatui y la mascota de Zig)
|
|
|
|
---
|
|
|
|
## Arquitectura Objetivo
|
|
|
|
### Diseño: Immediate Mode Rendering
|
|
|
|
Como ratatui, usamos **renderizado inmediato con buffers intermedios**:
|
|
|
|
```
|
|
┌─────────────┐ ┌────────┐ ┌──────────┐
|
|
│ Application │───▶│ Buffer │───▶│ Terminal │
|
|
│ (widgets) │ │ (cells)│ │ (output) │
|
|
└─────────────┘ └────────┘ └──────────┘
|
|
```
|
|
|
|
- Cada frame, la aplicación renderiza TODOS los widgets al buffer
|
|
- El buffer se compara con el anterior (diff)
|
|
- Solo se envían cambios a la terminal (eficiencia)
|
|
|
|
### Módulos Principales (Objetivo)
|
|
|
|
```
|
|
zcatui/
|
|
├── src/
|
|
│ ├── root.zig # Entry point, re-exports públicos
|
|
│ ├── terminal.zig # Terminal abstraction
|
|
│ ├── buffer.zig # Buffer + Cell types
|
|
│ ├── layout.zig # Layout, Constraint, Rect
|
|
│ ├── style.zig # Color, Style, Modifier
|
|
│ ├── text.zig # Text, Line, Span
|
|
│ ├── backend/
|
|
│ │ ├── backend.zig # Backend interface
|
|
│ │ └── ansi.zig # ANSI escape sequences (default)
|
|
│ └── widgets/
|
|
│ ├── widget.zig # Widget trait/interface
|
|
│ ├── block.zig # Block (borders, titles)
|
|
│ ├── paragraph.zig # Text paragraphs
|
|
│ ├── list.zig # Selectable lists
|
|
│ ├── table.zig # Tables with columns
|
|
│ ├── gauge.zig # Progress bars
|
|
│ ├── chart.zig # Line/bar charts
|
|
│ ├── canvas.zig # Arbitrary drawing
|
|
│ └── tabs.zig # Tab navigation
|
|
├── build.zig
|
|
└── examples/
|
|
├── hello.zig # Minimal example
|
|
├── demo.zig # Feature showcase
|
|
└── counter.zig # Interactive counter
|
|
```
|
|
|
|
---
|
|
|
|
## Fases de Implementación
|
|
|
|
### Fase 1: Core (Mínimo Viable)
|
|
- [ ] Buffer + Cell (almacenamiento de caracteres + estilos)
|
|
- [ ] Style + Color (colores 16, 256, RGB)
|
|
- [ ] Rect (área rectangular)
|
|
- [ ] Backend ANSI (escape sequences para cualquier terminal)
|
|
- [ ] Terminal (init, draw, restore)
|
|
- [ ] Widget trait básico
|
|
|
|
### Fase 2: Layout
|
|
- [ ] Constraint (Min, Max, Percentage, Length, Ratio)
|
|
- [ ] Layout (horizontal, vertical splitting)
|
|
- [ ] Direction (Horizontal, Vertical)
|
|
|
|
### Fase 3: Widgets Básicos
|
|
- [ ] Block (borders, titles, padding)
|
|
- [ ] Paragraph (texto con wrapping)
|
|
- [ ] List (items seleccionables)
|
|
|
|
### Fase 4: Widgets Avanzados
|
|
- [ ] Table (columnas, headers, selección)
|
|
- [ ] Gauge (barra de progreso)
|
|
- [ ] Tabs (navegación por pestañas)
|
|
- [ ] Chart (gráficos simples)
|
|
- [ ] Canvas (dibujo libre con braille/block chars)
|
|
|
|
### Fase 5: Extras
|
|
- [ ] Input handling (keyboard events)
|
|
- [ ] Mouse support
|
|
- [ ] Scrolling
|
|
- [ ] Animations (opcional)
|
|
|
|
---
|
|
|
|
## Conceptos Clave de ratatui a Replicar
|
|
|
|
### 1. Cell
|
|
Unidad mínima del buffer: un carácter + su estilo.
|
|
|
|
```zig
|
|
const Cell = struct {
|
|
char: u21, // Unicode codepoint
|
|
fg: Color, // Foreground color
|
|
bg: Color, // Background color
|
|
modifiers: Modifiers, // Bold, italic, underline, etc.
|
|
};
|
|
```
|
|
|
|
### 2. Buffer
|
|
Grid de celdas que representa el estado de la terminal.
|
|
|
|
```zig
|
|
const Buffer = struct {
|
|
area: Rect,
|
|
cells: []Cell,
|
|
|
|
pub fn get(self: *Buffer, x: u16, y: u16) *Cell { ... }
|
|
pub fn set_string(self: *Buffer, x: u16, y: u16, text: []const u8, style: Style) void { ... }
|
|
pub fn diff(self: *Buffer, other: *Buffer) []Update { ... }
|
|
};
|
|
```
|
|
|
|
### 3. Rect
|
|
Área rectangular en la terminal.
|
|
|
|
```zig
|
|
const Rect = struct {
|
|
x: u16,
|
|
y: u16,
|
|
width: u16,
|
|
height: u16,
|
|
|
|
pub fn inner(self: Rect, margin: Margin) Rect { ... }
|
|
pub fn intersection(self: Rect, other: Rect) Rect { ... }
|
|
};
|
|
```
|
|
|
|
### 4. Style
|
|
Combinación de colores y modificadores.
|
|
|
|
```zig
|
|
const Style = struct {
|
|
fg: ?Color = null,
|
|
bg: ?Color = null,
|
|
modifiers: Modifiers = .{},
|
|
|
|
pub fn fg(color: Color) Style { ... }
|
|
pub fn bg(color: Color) Style { ... }
|
|
pub fn bold() Style { ... }
|
|
pub fn patch(self: Style, other: Style) Style { ... }
|
|
};
|
|
```
|
|
|
|
### 5. Layout
|
|
Sistema de distribución de espacio.
|
|
|
|
```zig
|
|
const Layout = struct {
|
|
direction: Direction,
|
|
constraints: []const Constraint,
|
|
|
|
pub fn split(self: Layout, area: Rect) []Rect { ... }
|
|
};
|
|
|
|
const Constraint = union(enum) {
|
|
length: u16, // Exactly N cells
|
|
min: u16, // At least N cells
|
|
max: u16, // At most N cells
|
|
percentage: u16, // N% of available space
|
|
ratio: struct { num: u32, den: u32 },
|
|
};
|
|
```
|
|
|
|
### 6. Widget Interface
|
|
Trait que deben implementar todos los widgets.
|
|
|
|
```zig
|
|
const Widget = struct {
|
|
ptr: *anyopaque,
|
|
render_fn: *const fn(*anyopaque, Rect, *Buffer) void,
|
|
|
|
pub fn render(self: Widget, area: Rect, buf: *Buffer) void {
|
|
self.render_fn(self.ptr, area, buf);
|
|
}
|
|
};
|
|
|
|
// Ejemplo implementación:
|
|
const Block = struct {
|
|
title: ?[]const u8 = null,
|
|
borders: Borders = .all,
|
|
style: Style = .{},
|
|
|
|
pub fn widget(self: *Block) Widget {
|
|
return .{
|
|
.ptr = self,
|
|
.render_fn = render,
|
|
};
|
|
}
|
|
|
|
fn render(ptr: *anyopaque, area: Rect, buf: *Buffer) void {
|
|
const self: *Block = @ptrCast(@alignCast(ptr));
|
|
// ... render logic
|
|
}
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Referencia: ratatui Widgets
|
|
|
|
| Widget | Descripción | Prioridad |
|
|
|--------|-------------|-----------|
|
|
| **Block** | Contenedor con bordes y título | Alta |
|
|
| **Paragraph** | Texto con wrap y scroll | Alta |
|
|
| **List** | Lista seleccionable | Alta |
|
|
| **Table** | Tabla con columnas | Media |
|
|
| **Gauge** | Barra de progreso | Media |
|
|
| **Sparkline** | Gráfico mini de línea | Baja |
|
|
| **Chart** | Gráficos de línea/barras | Baja |
|
|
| **Canvas** | Dibujo libre (braille) | Baja |
|
|
| **BarChart** | Gráfico de barras | Baja |
|
|
| **Tabs** | Navegación por tabs | Media |
|
|
| **Scrollbar** | Indicador de scroll | Media |
|
|
| **Calendar** | Widget de calendario | Baja |
|
|
|
|
---
|
|
|
|
## Stack Técnico
|
|
|
|
| Componente | Elección |
|
|
|------------|----------|
|
|
| **Lenguaje** | Zig 0.15.2 |
|
|
| **Zig path** | `/mnt/cello2/arno/re/recode/zig/zig-0.15.2/zig-x86_64-linux-0.15.2/zig` |
|
|
| **Backend** | ANSI escape sequences (portable) |
|
|
| **Sin dependencias externas** | Solo stdlib de Zig |
|
|
| **Target** | Linux primario, cross-platform objetivo |
|
|
|
|
---
|
|
|
|
## Equipo y Metodología
|
|
|
|
### Quiénes Somos
|
|
- **Usuario**: Desarrollador independiente, proyectos comerciales propios
|
|
- **Claude**: Asistente de programación (Claude Code)
|
|
|
|
### Normas de Trabajo Centralizadas
|
|
|
|
**IMPORTANTE**: Todas las normas de trabajo están en:
|
|
```
|
|
/mnt/cello2/arno/re/recode/TEAM_STANDARDS/
|
|
```
|
|
|
|
**Archivos clave a leer**:
|
|
- `LAST_UPDATE.md` - **LEER PRIMERO** - Cambios recientes en normas
|
|
- `NORMAS_TRABAJO_CONSENSUADAS.md` - Metodología fundamental
|
|
- `QUICK_REFERENCE.md` - Cheat sheet rápido
|
|
- `INFRASTRUCTURE/` - Documentación de servidores
|
|
|
|
### Protocolo de Inicio de Conversación
|
|
|
|
1. **Leer** `TEAM_STANDARDS/LAST_UPDATE.md` (detectar cambios recientes)
|
|
2. **Leer** este archivo `CLAUDE.md`
|
|
3. **Verificar** estado del proyecto (`git status`, `zig build`)
|
|
4. **Continuar** desde donde se dejó
|
|
|
|
---
|
|
|
|
## Principios de Desarrollo
|
|
|
|
### Estándares Zig Open Source (#24 de NORMAS)
|
|
|
|
> **Este proyecto será público. El código debe ser ejemplar.**
|
|
|
|
| Aspecto | Estándar |
|
|
|---------|----------|
|
|
| **Claridad** | Código autoexplicativo, nombres descriptivos |
|
|
| **Comentarios** | Doc comments (`///`) en TODAS las funciones públicas |
|
|
| **Estructura** | Organización lógica, separación de responsabilidades |
|
|
| **Idiomático** | snake_case, error handling explícito, sin magia |
|
|
|
|
```zig
|
|
/// Renderiza un widget Block en el área especificada.
|
|
///
|
|
/// Dibuja los bordes según `borders` y el título si está definido.
|
|
/// El área interior queda disponible para contenido hijo.
|
|
pub fn render(self: *Block, area: Rect, buf: *Buffer) void {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Principios Generales
|
|
|
|
- **DRY**: Una sola función por tarea
|
|
- **Fragmentación**: <400 líneas core, <200 líneas utils
|
|
- **Testing progresivo**: Compilar y probar cada cambio
|
|
- **Funcionalidad > Performance**: Primero que funcione, luego optimizar
|
|
|
|
---
|
|
|
|
## API de Zig 0.15.2 - Cambios Importantes
|
|
|
|
> Ver guía completa: `TEAM_STANDARDS/INFRASTRUCTURE/ZIG_0.15_GUIA.md`
|
|
|
|
### Cambios Clave para este Proyecto
|
|
|
|
| Componente | Zig 0.15 |
|
|
|------------|----------|
|
|
| stdout | `std.fs.File.stdout().deprecatedWriter()` |
|
|
| ArrayList | `std.array_list.Managed(T).init(alloc)` |
|
|
| file.reader() | `file.deprecatedReader()` |
|
|
| sleep | `std.Thread.sleep()` |
|
|
|
|
### Terminal I/O
|
|
|
|
```zig
|
|
// Escribir a stdout
|
|
const stdout = std.fs.File.stdout();
|
|
const writer = stdout.deprecatedWriter();
|
|
try writer.print("\x1b[2J", .{}); // Clear screen
|
|
|
|
// Leer de stdin (para eventos)
|
|
const stdin = std.fs.File.stdin();
|
|
const reader = stdin.deprecatedReader();
|
|
```
|
|
|
|
---
|
|
|
|
## Otros Proyectos del Ecosistema
|
|
|
|
### Proyectos Zig
|
|
| Proyecto | Descripción | Estado |
|
|
|----------|-------------|--------|
|
|
| **service-monitor** | Monitor HTTP/TCP con notificaciones | Completado |
|
|
|
|
### Proyectos Go (referencia)
|
|
| Proyecto | Descripción |
|
|
|----------|-------------|
|
|
| **simifactu** | API facturación electrónica |
|
|
| **ms-web** (mundisofa) | Web e-commerce |
|
|
| **0fiS** | Aplicación desktop Fyne |
|
|
|
|
### Infraestructura
|
|
| Recurso | Ubicación |
|
|
|---------|-----------|
|
|
| **Git server** | git.reugenio.com (Forgejo) |
|
|
| **Servidor** | Simba (188.245.244.244) |
|
|
| **Docs infra** | `TEAM_STANDARDS/INFRASTRUCTURE/` |
|
|
|
|
---
|
|
|
|
## Control de Versiones
|
|
|
|
```bash
|
|
# Remote (cuando se cree el repo)
|
|
git remote: git@git.reugenio.com:reugenio/zcatui.git
|
|
|
|
# Comandos frecuentes
|
|
zig build # Compilar
|
|
zig build test # Tests
|
|
zig build run -- examples/hello.zig # Ejecutar ejemplo
|
|
```
|
|
|
|
---
|
|
|
|
## Ejemplo de Uso (Objetivo Final)
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
const zcatui = @import("zcatui");
|
|
|
|
const Terminal = zcatui.Terminal;
|
|
const Block = zcatui.widgets.Block;
|
|
const Paragraph = zcatui.widgets.Paragraph;
|
|
const Layout = zcatui.Layout;
|
|
const Constraint = zcatui.Constraint;
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
// Inicializar terminal
|
|
var term = try Terminal.init(allocator);
|
|
defer term.deinit();
|
|
|
|
// Loop principal
|
|
while (true) {
|
|
try term.draw(struct {
|
|
pub fn render(frame: *Frame) void {
|
|
// Layout: dividir en 2 áreas
|
|
const chunks = Layout.vertical(&.{
|
|
Constraint.length(3),
|
|
Constraint.min(0),
|
|
}).split(frame.area);
|
|
|
|
// Header
|
|
var header = Block.init()
|
|
.title("zcatui Demo")
|
|
.borders(.all);
|
|
frame.render(header.widget(), chunks[0]);
|
|
|
|
// Content
|
|
var content = Paragraph.init("Hello from zcatui!")
|
|
.block(Block.init().borders(.all));
|
|
frame.render(content.widget(), chunks[1]);
|
|
}
|
|
}.render);
|
|
|
|
// Handle input
|
|
if (try term.pollEvent()) |event| {
|
|
if (event.key == .q) break;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Recursos y Referencias
|
|
|
|
### ratatui (Rust)
|
|
- Repo: https://github.com/ratatui/ratatui
|
|
- Docs: https://docs.rs/ratatui/latest/ratatui/
|
|
- Website: https://ratatui.rs/
|
|
|
|
### Zig
|
|
- Docs 0.15: https://ziglang.org/documentation/0.15.0/std/
|
|
- Guía migración: `TEAM_STANDARDS/INFRASTRUCTURE/ZIG_0.15_GUIA.md`
|
|
|
|
### ANSI Escape Codes
|
|
- Referencia: https://en.wikipedia.org/wiki/ANSI_escape_code
|
|
- Colores: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
|
|
|
---
|
|
|
|
## Estado del Proyecto
|
|
|
|
| Componente | Estado |
|
|
|------------|--------|
|
|
| CLAUDE.md | ✅ Creado |
|
|
| build.zig | ⏳ Pendiente |
|
|
| Fase 1 (Core) | ⏳ Pendiente |
|
|
| Fase 2 (Layout) | ⏳ Pendiente |
|
|
| Fase 3 (Widgets básicos) | ⏳ Pendiente |
|
|
| Fase 4 (Widgets avanzados) | ⏳ Pendiente |
|
|
| Fase 5 (Input/extras) | ⏳ Pendiente |
|
|
|
|
---
|
|
|
|
**Próximos pasos sugeridos para la primera sesión:**
|
|
1. Crear `build.zig` básico
|
|
2. Implementar `src/style.zig` (Color, Style, Modifiers)
|
|
3. Implementar `src/buffer.zig` (Cell, Buffer, Rect)
|
|
4. Implementar `src/backend/ansi.zig` (escape sequences)
|
|
5. Crear ejemplo mínimo que pinte algo en pantalla
|