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>
529 lines
14 KiB
Markdown
529 lines
14 KiB
Markdown
# 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/)
|