# zcatgui - Arquitectura y Decisiones de Diseño > Documento de referencia para el desarrollo de zcatgui > Última actualización: 2025-12-09 --- ## 1. Visión del Proyecto ### 1.1 Objetivo Crear una librería GUI immediate-mode para Zig con: 1. **Máxima compatibilidad** - Funciona en cualquier ordenador (viejo HP, nuevo Lenovo, servidor SSH) 2. **Software rendering** - No depende de GPU/drivers 3. **Sistema de macros** - Piedra angular: grabar y reproducir acciones 4. **Cross-platform** - Linux, Windows, macOS 5. **Control total** - Sin "magia" del framework, estado explícito ### 1.2 Filosofía > "Máxima compatibilidad, mínimas dependencias, control total del usuario" - **Simple > Complejo**: microui demostró que 1,100 LOC son suficientes - **Explícito > Implícito**: Estado visible, sin callbacks ocultos - **Funciona siempre**: Software rendering primero, GPU opcional después --- ## 2. Paradigma: Immediate Mode ### 2.1 ¿Qué es Immediate Mode? ``` while (running) { events = pollEvents(); // 1. Obtener input updateState(events); // 2. TÚ actualizas estado commands = drawUI(state); // 3. Generar comandos de dibujo render(commands); // 4. Renderizar } ``` **La UI es una función pura del estado**: `UI = f(Estado)` ### 2.2 Immediate vs Retained Mode | Aspecto | Immediate (zcatgui) | Retained (Fyne) | |---------|---------------------|-----------------| | Estado | Tú lo manejas | Framework lo mantiene | | Callbacks | No hay | Muchos | | Threading | Simple, predecible | fyne.Do() hell (402 usos) | | Debugging | Fácil, estado visible | Difícil, estado oculto | | Testing | Funciones puras | Mock objects complejos | ### 2.3 Por qué Immediate Mode 1. **Sin fyne.Do()**: No hay threading hell 2. **Estado explícito**: Debug simple 3. **Testing trivial**: Funciones puras 4. **Control total**: Sin sorpresas --- ## 3. Arquitectura de Capas ``` ┌─────────────────────────────────────────────────────────────┐ │ Capa 4: Widgets de alto nivel │ │ (Table, Panel, Select, Modal, etc.) │ ├─────────────────────────────────────────────────────────────┤ │ Capa 3: Sistema de Macros │ │ (Grabación, Reproducción, Inyección de teclas) │ ├─────────────────────────────────────────────────────────────┤ │ Capa 2: Core UI │ │ (Context, Layout, Style, Input, Commands) │ ├─────────────────────────────────────────────────────────────┤ │ Capa 1: Rendering │ │ (Software Rasterizer, Framebuffer, Fonts) │ ├─────────────────────────────────────────────────────────────┤ │ Capa 0: Backend │ │ (SDL2 - ventanas, eventos, display) │ └─────────────────────────────────────────────────────────────┘ ``` ### 3.1 Capa 0: Backend (SDL2) **Responsabilidades:** - Crear/manejar ventana - Capturar eventos (teclado, mouse) - Mostrar framebuffer en pantalla **Por qué SDL2:** - Cross-platform probado - Fácil de usar desde Zig - Tiene software renderer ### 3.2 Capa 1: Rendering **Componentes:** - `Framebuffer`: Array 2D de pixels RGBA - `SoftwareRasterizer`: drawRect, drawLine, drawText - `Font`: Bitmap fonts + TTF opcional **Approach: Command List (estilo microui)** ```zig pub const DrawCommand = union(enum) { rect: RectCommand, text: TextCommand, line: LineCommand, clip: ClipCommand, clip_end, }; ``` ### 3.3 Capa 2: Core UI **Componentes:** - `Context`: Estado global, ID system, command list - `Layout`: Constraints (reutilizar de zcatui) - `Style`: Color, fonts (reutilizar de zcatui) - `Input`: Estado de teclado/mouse ### 3.4 Capa 3: Sistema de Macros **Piedra angular del proyecto.** Ver sección 5 para detalles. ### 3.5 Capa 4: Widgets **Prioritarios (de Simifactu):** 1. Table (editable) 2. Input (text entry) 3. Select (dropdown) 4. Panel (con título) 5. Split (HSplit/VSplit) 6. Button 7. Modal --- ## 4. Decisiones de Diseño Consensuadas ### 4.1 Rendering: Software por Defecto **Decisión**: Software rendering, GPU opcional en el futuro. **Razones:** - Funciona en cualquier ordenador - SSH con X11 forwarding funciona - No depende de drivers GPU - Simple de debugear **Implementación:** ``` Widgets → DrawCommands → Software Rasterizer → Framebuffer → SDL_Texture → Display ``` ### 4.2 Backend: SDL2 **Decisión**: SDL2 como único backend inicial. **Razones:** - Cross-platform probado (Linux/Windows/Mac) - API simple - Tiene software renderer - Fácil integración con Zig **Futuro opcional:** - X11 directo (Linux) - Win32 directo (Windows) ### 4.3 Fonts: Híbrido **Decisión**: Bitmap embebido + TTF opcional. **Razones:** - Bitmap siempre funciona (no dependencias) - TTF para flexibilidad (stb_truetype) ### 4.4 Enfoque Híbrido para Desarrollo **Decisión**: Estudiar DVUI/microui, implementar desde cero. **Razones:** - Aprender haciendo - Control total del código - Sin dependencias no deseadas - Evitar fork con bagaje innecesario ### 4.5 Macros: Teclas Raw **Decisión**: Grabar teclas literales, no comandos abstractos. **Ver sección 5.** --- ## 5. Sistema de Macros ### 5.1 Principio Fundamental **Grabar teclas raw, reproducir teclas raw.** ``` Usuario pulsa: Tab, Tab, Enter, "texto", Escape Grabamos: [Tab, Tab, Enter, t, e, x, t, o, Escape] Reproducimos: Inyectamos exactamente esas teclas ``` ### 5.2 Por qué Teclas Raw (no Comandos) | Enfoque | Pros | Contras | |---------|------|---------| | **Teclas raw** | Simple, mínima memoria, como Vim | Depende del estado inicial | | Comandos abstractos | Más robusto | Complejo, más memoria | **Decisión**: Teclas raw porque: 1. KISS - menos código = menos bugs 2. Vim lo hace así y funciona 3. El estado inicial es controlable ### 5.3 Manejo del Ratón **Casi todo lo que hace el ratón se puede expresar como teclado:** | Acción ratón | Equivalente teclado | |--------------|---------------------| | Click en botón | Tab hasta focus + Enter | | Click en fila 5 | Flechas hasta fila 5 | | Scroll down | PageDown o flechas | | Drag splitter | Ctrl+flechas | **Estrategia:** 1. **Fase 1**: Solo teclado (macros de teclas) 2. **Fase 2**: Mouse → traducir a teclas equivalentes ### 5.4 API ```zig pub const MacroRecorder = struct { events: ArrayList(KeyEvent), recording: bool, pub fn start(self: *MacroRecorder) void { self.events.clearRetainingCapacity(); self.recording = true; } pub fn stop(self: *MacroRecorder) []const KeyEvent { self.recording = false; return self.events.items; } pub fn record(self: *MacroRecorder, key: KeyEvent) void { if (self.recording) { self.events.append(key) catch {}; } } pub fn save(self: *MacroRecorder, path: []const u8) !void { // Serializar a archivo } pub fn load(allocator: Allocator, path: []const u8) !MacroRecorder { // Deserializar de archivo } }; pub const MacroPlayer = struct { pub fn play( events: []const KeyEvent, inject_fn: *const fn(KeyEvent) void, delay_ms: u32, ) void { for (events) |event| { inject_fn(event); if (delay_ms > 0) { std.Thread.sleep(delay_ms * std.time.ns_per_ms); } } } }; ``` ### 5.5 Casos de Uso 1. **Testing automatizado**: Grabar sesión → test 2. **Tutoriales**: Macros que se ejecutan paso a paso 3. **Repetición**: Tarea repetitiva → hotkey 4. **Debugging**: "¿Cómo llegaste a este bug?" → envía macro 5. **Demos**: Grabar demos que se auto-reproducen --- ## 6. Flujo de Datos ### 6.1 Event Loop Principal ```zig pub fn run(app: *App) !void { while (app.running) { // 1. Poll eventos del backend while (backend.pollEvent()) |raw_event| { // 2. Traducir a KeyEvent/MouseEvent const event = translateEvent(raw_event); // 3. Grabar si macro activo if (app.macro_recorder.recording) { app.macro_recorder.record(event); } // 4. Actualizar estado de input app.input.update(event); } // 5. Ejecutar lógica de UI (immediate mode) app.context.beginFrame(); app.drawUI(); // Usuario define esto app.context.endFrame(); // 6. Renderizar commands for (app.context.commands.items) |cmd| { app.rasterizer.execute(cmd); } // 7. Presentar framebuffer backend.present(app.framebuffer); } } ``` ### 6.2 Widget Pattern ```zig pub fn button(ctx: *Context, label: []const u8) bool { // 1. Obtener ID único const id = ctx.getId(label); // 2. Obtener bounds del layout const bounds = ctx.layout.nextRect(); // 3. Verificar interacción const hovered = bounds.contains(ctx.input.mousePos()); const pressed = hovered and ctx.input.mouseDown(.left); const clicked = hovered and ctx.input.mouseReleased(.left); // 4. Determinar estilo const bg_color = if (pressed) pressed_color else if (hovered) hover_color else normal_color; // 5. Emitir comandos de dibujo ctx.pushCommand(.{ .rect = .{ .bounds = bounds, .color = bg_color, }}); ctx.pushCommand(.{ .text = .{ .pos = bounds.center(), .text = label, .color = text_color, }}); // 6. Retornar si fue clickeado return clicked; } ``` --- ## 7. Estructura de Archivos ``` zcatgui/ ├── src/ │ ├── zcatgui.zig # Entry point, re-exports │ │ │ ├── core/ │ │ ├── context.zig # Context, ID system │ │ ├── layout.zig # Constraints (de zcatui) │ │ ├── style.zig # Color, Style (de zcatui) │ │ ├── input.zig # InputState │ │ └── command.zig # DrawCommand │ │ │ ├── widgets/ │ │ ├── button.zig │ │ ├── label.zig │ │ ├── input.zig │ │ ├── select.zig │ │ ├── table.zig # CRÍTICO │ │ ├── panel.zig │ │ ├── split.zig │ │ └── modal.zig │ │ │ ├── render/ │ │ ├── software.zig # Rasterizer │ │ ├── framebuffer.zig # Pixel buffer │ │ └── font.zig # Fonts │ │ │ ├── backend/ │ │ ├── backend.zig # Interface │ │ └── sdl2.zig # SDL2 impl │ │ │ └── macro/ │ ├── event.zig # MacroEvent │ ├── recorder.zig # Grabador │ ├── player.zig # Reproductor │ └── storage.zig # Persistencia │ ├── examples/ ├── docs/ ├── build.zig └── CLAUDE.md ``` --- ## 8. Código Reutilizable de zcatui ### 8.1 Layout System Reutilizar de `zcatui/src/layout.zig`: - `Constraint`: length, min, max, percentage, ratio, fill - `Layout`: vertical, horizontal - `Rect`: x, y, width, height, contains, intersection ### 8.2 Style System Reutilizar de `zcatui/src/style.zig`: - `Color`: rgb, indexed, ANSI colors - `Style`: foreground, background, modifiers **Adaptar para GUI:** - Añadir alpha channel (RGBA) - Añadir border_radius (futuro) - Añadir shadow (futuro) ### 8.3 Conceptos - Focus management pattern - Theme system - Event patterns --- ## 9. Plan de Desarrollo ### Fase 1: Core + Macros (1 semana) **Objetivo**: Event loop funcional con grabación de macros. - [ ] SDL2 backend (ventana, eventos) - [ ] Framebuffer + software rasterizer (rect, text) - [ ] Context básico - [ ] MacroRecorder/MacroPlayer - [ ] Button + Label (para probar) ### Fase 2: Widgets Esenciales (2 semanas) - [ ] Input (text entry) - [ ] Select (dropdown) - [ ] Checkbox - [ ] List - [ ] Layout system (de zcatui) - [ ] Focus management ### Fase 3: Widgets Avanzados (2 semanas) - [ ] Table editable (CRÍTICO) - [ ] Split panels - [ ] Panel con título - [ ] Modal/Popup ### Fase 4: Pulido (1 semana) - [ ] Themes - [ ] TTF fonts - [ ] Documentación - [ ] Examples completos --- ## 10. Métricas de Éxito ### 10.1 Funcionales - [ ] Funciona en Linux sin GPU dedicada - [ ] Funciona via SSH con X11 forwarding - [ ] Macros funcionan: grabar → reproducir - [ ] Table editable comparable a Simifactu ### 10.2 Código - Target: ~5,000-10,000 LOC total - Tests para core y widgets - Examples ejecutables ### 10.3 Performance - 60 FPS con UI típica - Startup < 100ms - Memoria < 50MB --- ## 11. Referencias ### Librerías Estudiadas | Librería | Lo que tomamos | |----------|----------------| | microui | Arquitectura, command list | | DVUI | Patterns Zig, backend abstraction | | Dear ImGui | API design, ID system | | Gio | Constraint system | ### Documentación de Investigación - `docs/research/GIO_UI_ANALYSIS.md` - `docs/research/IMMEDIATE_MODE_LIBS.md` - `docs/research/SIMIFACTU_FYNE_ANALYSIS.md` ### Links Externos - [microui](https://github.com/rxi/microui) - [DVUI](https://github.com/david-vanderson/dvui) - [Dear ImGui](https://github.com/ocornut/imgui) - [Gio](https://gioui.org/)