zcatgui/docs/ARCHITECTURE.md
reugenio 59c597fc18 feat: zCatGui v0.1.0 - Initial project setup
Immediate Mode GUI library for Zig with software rendering.

Core features:
- SDL2 backend for cross-platform window/events
- Software rasterizer (works everywhere, including SSH)
- Macro recording system (cornerstone feature, like Vim)
- Command-list rendering (DrawRect, DrawText, etc.)
- Layout system with constraints
- Color/Style system with themes

Project structure:
- src/core/: context, command, input, layout, style
- src/macro/: MacroRecorder, MacroPlayer, MacroStorage
- src/render/: Framebuffer, SoftwareRenderer, Font
- src/backend/: Backend interface, SDL2 implementation
- examples/: hello.zig, macro_demo.zig
- docs/: Architecture, research (Gio, immediate-mode libs, Simifactu)

Build: zig build (requires SDL2-devel)
Tests: 16 tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 01:30:05 +01:00

14 KiB

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)

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

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

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

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