Widgets implementados (13): - Block, Paragraph, List, Table - Gauge, LineGauge, Tabs, Sparkline - Scrollbar, BarChart, Canvas, Chart - Calendar (Monthly), Clear Modulos: - src/text.zig: Span, Line, Text, Alignment - src/symbols/: line, border, block, bar, braille, half_block, scrollbar, marker - src/widgets/: todos los widgets con tests Documentacion: - docs/ARCHITECTURE.md: arquitectura tecnica - docs/WIDGETS.md: guia completa de widgets - docs/API.md: referencia rapida - CLAUDE.md: actualizado con estado v1.0 Tests: 103+ tests en widgets, todos pasan 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
413 lines
12 KiB
Markdown
413 lines
12 KiB
Markdown
# zcatui - Arquitectura
|
|
|
|
> Documentación técnica de la arquitectura de zcatui
|
|
|
|
## Visión General
|
|
|
|
zcatui es una librería TUI (Terminal User Interface) para Zig, inspirada en ratatui de Rust. Utiliza un patrón de **renderizado inmediato con buffers intermedios**.
|
|
|
|
## Diagrama de Arquitectura
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Application │
|
|
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
|
│ │ Widget1 │ │ Widget2 │ │ Widget3 │ │ Widget4 │ │
|
|
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
|
|
│ │ │ │ │ │
|
|
│ └────────────┴─────┬──────┴────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────┐ │
|
|
│ │ Buffer │ Grid de Cells │
|
|
│ │ (current)│ Cada Cell: char + style │
|
|
│ └────┬─────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────┐ │
|
|
│ │ Diff │ Compara con buffer anterior │
|
|
│ └────┬─────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────┐ │
|
|
│ │ Terminal │ Solo envía cambios │
|
|
│ └────┬─────┘ │
|
|
│ │ │
|
|
└─────────────────────────┼────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌──────────┐
|
|
│ stdout │ ANSI escape sequences
|
|
└──────────┘
|
|
```
|
|
|
|
## Componentes Core
|
|
|
|
### 1. Cell (`buffer.zig`)
|
|
|
|
La unidad mínima de renderizado. Representa un único carácter en la terminal.
|
|
|
|
```zig
|
|
pub const Cell = struct {
|
|
symbol: Symbol, // UTF-8 grapheme (hasta 4 bytes)
|
|
style: Style, // Foreground, background, modifiers
|
|
|
|
pub fn reset(self: *Cell) void { ... }
|
|
pub fn setChar(self: *Cell, ch: u21) void { ... }
|
|
pub fn setSymbol(self: *Cell, symbol: []const u8) void { ... }
|
|
};
|
|
```
|
|
|
|
### 2. Buffer (`buffer.zig`)
|
|
|
|
Grid bidimensional de Cells. Maneja el estado de renderizado.
|
|
|
|
```zig
|
|
pub const Buffer = struct {
|
|
area: Rect,
|
|
cells: []Cell,
|
|
allocator: Allocator,
|
|
|
|
pub fn init(allocator: Allocator, area: Rect) !Buffer { ... }
|
|
pub fn getCell(self: *Buffer, x: u16, y: u16) ?*Cell { ... }
|
|
pub fn setString(self: *Buffer, x: u16, y: u16, text: []const u8, style: Style) u16 { ... }
|
|
pub fn setSpan(self: *Buffer, x: u16, y: u16, span: Span, width: u16) u16 { ... }
|
|
};
|
|
```
|
|
|
|
### 3. Rect (`buffer.zig`)
|
|
|
|
Representa un área rectangular en la terminal.
|
|
|
|
```zig
|
|
pub const Rect = struct {
|
|
x: u16,
|
|
y: u16,
|
|
width: u16,
|
|
height: u16,
|
|
|
|
pub fn init(x: u16, y: u16, width: u16, height: u16) Rect { ... }
|
|
pub fn inner(self: Rect, margin: u16) Rect { ... }
|
|
pub fn intersection(self: Rect, other: Rect) Rect { ... }
|
|
pub fn isEmpty(self: Rect) bool { ... }
|
|
pub fn left(self: Rect) u16 { ... }
|
|
pub fn right(self: Rect) u16 { ... }
|
|
pub fn top(self: Rect) u16 { ... }
|
|
pub fn bottom(self: Rect) u16 { ... }
|
|
};
|
|
```
|
|
|
|
### 4. Style (`style.zig`)
|
|
|
|
Combinación de colores y modificadores de texto.
|
|
|
|
```zig
|
|
pub const Style = struct {
|
|
foreground: ?Color = null,
|
|
background: ?Color = null,
|
|
add_modifiers: Modifier = .{},
|
|
sub_modifiers: Modifier = .{},
|
|
|
|
pub const default: Style = .{};
|
|
|
|
pub fn fg(self: Style, color: Color) Style { ... }
|
|
pub fn bg(self: Style, color: Color) Style { ... }
|
|
pub fn bold(self: Style) Style { ... }
|
|
pub fn italic(self: Style) Style { ... }
|
|
pub fn patch(self: Style, other: Style) Style { ... }
|
|
};
|
|
```
|
|
|
|
### 5. Color (`style.zig`)
|
|
|
|
Soporte para colores de 16, 256 y RGB.
|
|
|
|
```zig
|
|
pub const Color = union(enum) {
|
|
reset,
|
|
black, red, green, yellow, blue, magenta, cyan, white,
|
|
light_black, light_red, light_green, light_yellow,
|
|
light_blue, light_magenta, light_cyan, light_white,
|
|
indexed: u8, // 256 colores
|
|
rgb: struct { r: u8, g: u8, b: u8 },
|
|
};
|
|
```
|
|
|
|
### 6. Text Types (`text.zig`)
|
|
|
|
Tipos para manejar texto estilizado.
|
|
|
|
```zig
|
|
// Span: texto con estilo único
|
|
pub const Span = struct {
|
|
content: []const u8,
|
|
style: Style,
|
|
};
|
|
|
|
// Line: múltiples spans en una línea
|
|
pub const Line = struct {
|
|
spans: []const Span,
|
|
alignment: Alignment,
|
|
};
|
|
|
|
// Text: múltiples líneas
|
|
pub const Text = struct {
|
|
lines: []const Line,
|
|
alignment: Alignment,
|
|
};
|
|
```
|
|
|
|
### 7. Layout (`layout.zig`)
|
|
|
|
Sistema de distribución de espacio.
|
|
|
|
```zig
|
|
pub const Layout = struct {
|
|
direction: Direction,
|
|
constraints: []const Constraint,
|
|
|
|
pub fn horizontal(constraints: []const Constraint) Layout { ... }
|
|
pub fn vertical(constraints: []const Constraint) Layout { ... }
|
|
pub fn split(self: Layout, area: Rect, result: []Rect) void { ... }
|
|
};
|
|
|
|
pub const Constraint = union(enum) {
|
|
length: u16, // Exactamente N celdas
|
|
min: u16, // Mínimo N celdas
|
|
max: u16, // Máximo N celdas
|
|
percentage: u16, // N% del espacio disponible
|
|
ratio: struct { num: u32, den: u32 },
|
|
fill: u16, // Llenar espacio restante
|
|
};
|
|
```
|
|
|
|
## Sistema de Widgets
|
|
|
|
### Patrón de Widget
|
|
|
|
Todos los widgets implementan el método `render`:
|
|
|
|
```zig
|
|
pub fn render(self: WidgetType, area: Rect, buf: *Buffer) void {
|
|
// 1. Validar área
|
|
if (area.isEmpty()) return;
|
|
|
|
// 2. Renderizar block/wrapper si existe
|
|
const inner = if (self.block) |b| blk: {
|
|
b.render(area, buf);
|
|
break :blk b.inner(area);
|
|
} else area;
|
|
|
|
// 3. Renderizar contenido
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### StatefulWidget Pattern
|
|
|
|
Para widgets con estado mutable:
|
|
|
|
```zig
|
|
pub fn renderStateful(self: WidgetType, area: Rect, buf: *Buffer, state: *State) void {
|
|
// Similar a render, pero puede modificar state
|
|
// Útil para: scroll position, selection, etc.
|
|
}
|
|
```
|
|
|
|
### Fluent Builder Pattern
|
|
|
|
Los widgets usan setters encadenables:
|
|
|
|
```zig
|
|
const list = List.init(items)
|
|
.setBlock(block)
|
|
.setHighlightStyle(style)
|
|
.setHighlightSymbol("> ")
|
|
.setDirection(.top_to_bottom);
|
|
```
|
|
|
|
## Sistema de Símbolos
|
|
|
|
### Estructura
|
|
|
|
```
|
|
symbols/
|
|
├── line.zig # ─ │ ┌ ┐ └ ┘ ├ ┤ etc.
|
|
├── border.zig # Sets de bordes: plain, rounded, double, thick
|
|
├── block.zig # █ ▀ ▄ ▌ ▐ etc.
|
|
├── bar.zig # ▏▎▍▌▋▊▉█ (barras horizontales)
|
|
├── braille.zig # Patrones braille (256 combinaciones)
|
|
├── half_block.zig # ▀ ▄ para resolución 1x2
|
|
├── scrollbar.zig # Símbolos para scrollbars
|
|
└── marker.zig # Marcadores para charts: dot, block, braille, etc.
|
|
```
|
|
|
|
### Braille Grid (2x4 dots per cell)
|
|
|
|
```
|
|
┌───┬───┐
|
|
│ 0 │ 3 │ Bit layout:
|
|
├───┼───┤
|
|
│ 1 │ 4 │ byte = Σ (2^bit) para cada dot activo
|
|
├───┼───┤
|
|
│ 2 │ 5 │ Base: U+2800 (braille blank)
|
|
├───┼───┤ Resultado: chr(0x2800 + byte)
|
|
│ 6 │ 7 │
|
|
└───┴───┘
|
|
```
|
|
|
|
## Backend ANSI
|
|
|
|
### Escape Sequences Soportadas
|
|
|
|
| Función | Secuencia |
|
|
|---------|-----------|
|
|
| Clear screen | `\x1b[2J` |
|
|
| Move cursor | `\x1b[{row};{col}H` |
|
|
| Hide cursor | `\x1b[?25l` |
|
|
| Show cursor | `\x1b[?25h` |
|
|
| Reset style | `\x1b[0m` |
|
|
| Bold | `\x1b[1m` |
|
|
| Dim | `\x1b[2m` |
|
|
| Italic | `\x1b[3m` |
|
|
| Underline | `\x1b[4m` |
|
|
| FG color (16) | `\x1b[{30-37}m` |
|
|
| BG color (16) | `\x1b[{40-47}m` |
|
|
| FG color (256) | `\x1b[38;5;{n}m` |
|
|
| BG color (256) | `\x1b[48;5;{n}m` |
|
|
| FG color (RGB) | `\x1b[38;2;{r};{g};{b}m` |
|
|
| BG color (RGB) | `\x1b[48;2;{r};{g};{b}m` |
|
|
| Alternate screen | `\x1b[?1049h` |
|
|
| Main screen | `\x1b[?1049l` |
|
|
|
|
## Algoritmos Clave
|
|
|
|
### Bresenham's Line Algorithm (Canvas)
|
|
|
|
Usado para dibujar líneas en el canvas:
|
|
|
|
```zig
|
|
fn drawLine(x0: i32, y0: i32, x1: i32, y1: i32, color: Color) void {
|
|
var dx = @abs(x1 - x0);
|
|
var dy = @abs(y1 - y0);
|
|
var sx: i32 = if (x0 < x1) 1 else -1;
|
|
var sy: i32 = if (y0 < y1) 1 else -1;
|
|
var err = dx - dy;
|
|
|
|
while (true) {
|
|
self.set(x0, y0, color);
|
|
if (x0 == x1 and y0 == y1) break;
|
|
const e2 = 2 * err;
|
|
if (e2 > -dy) { err -= dy; x0 += sx; }
|
|
if (e2 < dx) { err += dx; y0 += sy; }
|
|
}
|
|
}
|
|
```
|
|
|
|
### Zeller's Congruence (Calendar)
|
|
|
|
Para calcular el día de la semana:
|
|
|
|
```zig
|
|
pub fn dayOfWeek(year: i16, month: u4, day: u5) u3 {
|
|
var y = year;
|
|
var m = month;
|
|
if (m < 3) { m += 12; y -= 1; }
|
|
const q = day;
|
|
const k = @mod(y, 100);
|
|
const j = @divFloor(y, 100);
|
|
var h = q + @divFloor(13 * (m + 1), 5) + k + @divFloor(k, 4) + @divFloor(j, 4) - 2 * j;
|
|
return @intCast(@mod(@mod(h, 7) + 6, 7)); // 0=Sunday
|
|
}
|
|
```
|
|
|
|
## Consideraciones de Memoria
|
|
|
|
### Stack Allocation
|
|
|
|
La mayoría de estructuras usan stack allocation con tamaños fijos:
|
|
|
|
- `CalendarEventStore`: máximo 32 eventos
|
|
- `Symbol`: máximo 4 bytes UTF-8
|
|
- Arrays de constraints: tamaño fijo en compilación
|
|
|
|
### Heap Allocation
|
|
|
|
Solo se usa heap para:
|
|
|
|
- `Buffer.cells`: array de celdas (puede ser grande)
|
|
- Strings dinámicos pasados por el usuario
|
|
|
|
### Sin GC
|
|
|
|
Zig no tiene garbage collector. Los widgets no poseen memoria, solo referencias. El usuario es responsable de la lifetime de los datos.
|
|
|
|
## Testing
|
|
|
|
### Estrategia
|
|
|
|
1. **Unit tests** en cada módulo
|
|
2. **Render tests** comparando buffers
|
|
3. **Property-based** donde aplica (ej: Rect.intersection es conmutativa)
|
|
|
|
### Ejecutar tests
|
|
|
|
```bash
|
|
zig build test # Todos los tests
|
|
zig build test --summary all # Con resumen detallado
|
|
```
|
|
|
|
## Extensibilidad
|
|
|
|
### Crear un Widget Personalizado
|
|
|
|
```zig
|
|
const MyWidget = struct {
|
|
data: []const u8,
|
|
style: Style,
|
|
block: ?Block = null,
|
|
|
|
pub fn init(data: []const u8) MyWidget {
|
|
return .{ .data = data, .style = Style.default };
|
|
}
|
|
|
|
pub fn setStyle(self: MyWidget, s: Style) MyWidget {
|
|
var w = self;
|
|
w.style = s;
|
|
return w;
|
|
}
|
|
|
|
pub fn setBlock(self: MyWidget, b: Block) MyWidget {
|
|
var w = self;
|
|
w.block = b;
|
|
return w;
|
|
}
|
|
|
|
pub fn render(self: MyWidget, area: Rect, buf: *Buffer) void {
|
|
if (area.isEmpty()) return;
|
|
|
|
const inner = if (self.block) |b| blk: {
|
|
b.render(area, buf);
|
|
break :blk b.inner(area);
|
|
} else area;
|
|
|
|
_ = buf.setString(inner.left(), inner.top(), self.data, self.style);
|
|
}
|
|
};
|
|
```
|
|
|
|
## Performance
|
|
|
|
### Optimizaciones Actuales
|
|
|
|
1. **Diff-based rendering**: Solo se envían cambios a la terminal
|
|
2. **Pre-computed symbols**: Braille patterns pre-calculados
|
|
3. **Inline functions**: Funciones críticas marcadas como inline
|
|
4. **Saturating arithmetic**: Uso de `-|` para evitar overflow checks
|
|
|
|
### Áreas de Mejora Futuras
|
|
|
|
1. Buffer pooling para reutilización
|
|
2. Lazy widget evaluation
|
|
3. Dirty region tracking
|
|
4. SIMD para operaciones de buffer
|