# 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 Implementadas (v1.1) 1. **Symbol compacto**: Tipo que almacena UTF-8 directamente (4 bytes max) - Evita conversión codepoint→UTF8 en cada render - `fromCodepoint()` y `fromSlice()` para crear symbols - `slice()` retorna bytes listos para output 2. **Buffer diff**: Sistema de renderizado diferencial - `DiffIterator` compara buffers celda a celda - Solo retorna celdas que cambiaron entre frames - Reduce dramáticamente I/O a terminal 3. **Cell.eql()**: Comparación eficiente de celdas - Compara symbol, fg, bg, modifiers - Base del sistema de diff 4. **writeSymbol()**: Output UTF-8 directo - Escribe bytes sin conversión - Más eficiente que `writeChar()` con encoding 5. **Pre-computed symbols**: Braille patterns pre-calculados (256 patrones) 6. **Saturating arithmetic**: Uso de `-|` y `+|` para evitar overflow checks ### Áreas de Mejora Futuras (v1.2+) 1. Buffer pooling para reutilización de memoria 2. Lazy widget evaluation 3. SIMD para operaciones de buffer masivas 4. Dirty region tracking (solo re-renderizar áreas modificadas)