docs: Add TABLES_ARCHITECTURE.md - definitive reference for table widgets
Complete documentation covering: - When to use AdvancedTable vs VirtualAdvancedTable - Architecture diagram (table_core.zig as single source) - How to extend functionality (always in table_core.zig) - Complete API reference for table_core.zig - Code examples and model differences
This commit is contained in:
parent
27b69cfcde
commit
e8b4c98d4a
1 changed files with 372 additions and 0 deletions
372
docs/TABLES_ARCHITECTURE.md
Normal file
372
docs/TABLES_ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,372 @@
|
||||||
|
# Arquitectura de Tablas en zcatgui
|
||||||
|
|
||||||
|
> **Documento de referencia definitivo** para entender, usar y extender los widgets de tabla.
|
||||||
|
> Actualizado: 2025-12-27
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen Ejecutivo
|
||||||
|
|
||||||
|
zcatgui proporciona **dos widgets de tabla** que comparten lógica común:
|
||||||
|
|
||||||
|
| Widget | Uso | Datos |
|
||||||
|
|--------|-----|-------|
|
||||||
|
| **AdvancedTable** | Tablas pequeñas/medianas | En memoria (ArrayList) |
|
||||||
|
| **VirtualAdvancedTable** | Tablas grandes (60k+ filas) | Paginados via DataProvider |
|
||||||
|
|
||||||
|
**Principio DRY:** Toda la lógica común está en `table_core.zig`. Los widgets adaptan esa lógica a su modelo de datos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arquitectura de Capas
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ APLICACIÓN │
|
||||||
|
│ (zsimifactu, etc.) │
|
||||||
|
│ - Usa AdvancedTable o VirtualAdvancedTable según necesidad │
|
||||||
|
│ - Implementa DataProvider para VirtualAdvancedTable │
|
||||||
|
│ - Maneja lógica de negocio (guardar en BD, validar, etc.) │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
│ usa
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ WIDGETS DE TABLA │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────┐ ┌──────────────────────────┐ │
|
||||||
|
│ │ AdvancedTable │ │ VirtualAdvancedTable │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ - Datos en memoria │ │ - Datos paginados │ │
|
||||||
|
│ │ - selected_row/col │ │ - selected_id + active_col│ │
|
||||||
|
│ │ - Ordenación local │ │ - Scroll virtual │ │
|
||||||
|
│ │ - Multi-selección │ │ - FilterBar integrada │ │
|
||||||
|
│ └──────────┬──────────┘ └────────────┬─────────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ambos usan │ │
|
||||||
|
│ └──────────────┬───────────────────┘ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ table_core.zig │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ LÓGICA COMÚN (un solo lugar para modificar): │ │
|
||||||
|
│ │ - calculateNextCell() / calculatePrevCell() [Tab navigation] │ │
|
||||||
|
│ │ - toggleSort() [Ordenación] │ │
|
||||||
|
│ │ - handleEditingKeyboard() [Edición celda] │ │
|
||||||
|
│ │ - detectDoubleClick() [Doble-click] │ │
|
||||||
|
│ │ - drawCellActiveIndicator() [Renderizado] │ │
|
||||||
|
│ │ - drawEditingOverlay() [Overlay edición] │ │
|
||||||
|
│ │ - blendColor(), startsWithIgnoreCase() [Utilidades] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ TIPOS COMPARTIDOS: │ │
|
||||||
|
│ │ - TabNavigateResult, CellPosition │ │
|
||||||
|
│ │ - SortDirection, SortToggleResult │ │
|
||||||
|
│ │ - TableColors, CellRenderInfo, EditState │ │
|
||||||
|
│ │ - EditKeyboardResult, DoubleClickState │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estructura de Archivos
|
||||||
|
|
||||||
|
```
|
||||||
|
src/widgets/
|
||||||
|
├── table_core.zig # LÓGICA COMÚN - modificar aquí
|
||||||
|
│
|
||||||
|
├── advanced_table/ # Tabla con datos en memoria
|
||||||
|
│ ├── advanced_table.zig # Widget principal
|
||||||
|
│ ├── state.zig # Estado (usa table_core)
|
||||||
|
│ ├── types.zig # Tipos específicos
|
||||||
|
│ └── schema.zig # Definición de columnas
|
||||||
|
│
|
||||||
|
└── virtual_advanced_table/ # Tabla con datos paginados
|
||||||
|
├── virtual_advanced_table.zig # Widget principal
|
||||||
|
├── state.zig # Estado (usa table_core)
|
||||||
|
├── types.zig # Tipos específicos
|
||||||
|
└── data_provider.zig # Interface para datos
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cuándo Usar Cada Widget
|
||||||
|
|
||||||
|
### AdvancedTable
|
||||||
|
|
||||||
|
**Usar cuando:**
|
||||||
|
- Datos caben en memoria (< 10,000 filas típicamente)
|
||||||
|
- Necesitas ordenación/filtrado local rápido
|
||||||
|
- Multi-selección de filas (Ctrl+Click, Shift+Click)
|
||||||
|
- Los datos ya están cargados
|
||||||
|
|
||||||
|
**Ejemplo:** Lista de tipos de IVA, categorías, configuraciones.
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const state = AdvancedTableState.init(allocator);
|
||||||
|
defer state.deinit();
|
||||||
|
|
||||||
|
// Cargar datos
|
||||||
|
try state.setRows(&my_rows);
|
||||||
|
|
||||||
|
// En draw():
|
||||||
|
const result = advancedTable(ctx, rect, &state, &schema, colors);
|
||||||
|
if (result.selection_changed) {
|
||||||
|
// Manejar selección
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### VirtualAdvancedTable
|
||||||
|
|
||||||
|
**Usar cuando:**
|
||||||
|
- Miles o millones de filas (60k+ poblaciones, logs, etc.)
|
||||||
|
- Datos vienen de BD/API con paginación
|
||||||
|
- Necesitas FilterBar integrada
|
||||||
|
- Memoria limitada
|
||||||
|
|
||||||
|
**Ejemplo:** Lista de poblaciones, historial de documentos.
|
||||||
|
|
||||||
|
```zig
|
||||||
|
var state = VirtualAdvancedTableState{};
|
||||||
|
const provider = MyDataProvider.init(db);
|
||||||
|
|
||||||
|
// En draw():
|
||||||
|
const result = virtualAdvancedTableRect(ctx, rect, &state, provider.toDataProvider(), config);
|
||||||
|
if (result.selection_changed) {
|
||||||
|
// Manejar selección
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cómo Extender la Funcionalidad
|
||||||
|
|
||||||
|
### Regla de Oro (Norma #7 DRY)
|
||||||
|
|
||||||
|
> **Si la funcionalidad es común a ambas tablas, va en `table_core.zig`.**
|
||||||
|
> Los widgets solo adaptan el resultado a su modelo de datos.
|
||||||
|
|
||||||
|
### Ejemplo: Añadir nueva navegación
|
||||||
|
|
||||||
|
**PASO 1:** Añadir la lógica en `table_core.zig`
|
||||||
|
|
||||||
|
```zig
|
||||||
|
// table_core.zig
|
||||||
|
|
||||||
|
/// Calcula la celda de la siguiente página
|
||||||
|
pub fn calculatePageDown(
|
||||||
|
current_row: usize,
|
||||||
|
current_col: usize,
|
||||||
|
num_rows: usize,
|
||||||
|
page_size: usize,
|
||||||
|
) CellPosition {
|
||||||
|
const new_row = @min(current_row + page_size, num_rows - 1);
|
||||||
|
return .{ .row = new_row, .col = current_col, .result = .navigated };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**PASO 2:** Usar en ambos states (adaptan a su modelo)
|
||||||
|
|
||||||
|
```zig
|
||||||
|
// advanced_table/state.zig
|
||||||
|
pub fn pageDown(self: *AdvancedTableState, page_size: usize) void {
|
||||||
|
const pos = table_core.calculatePageDown(
|
||||||
|
@intCast(self.selected_row),
|
||||||
|
@intCast(self.selected_col),
|
||||||
|
self.getRowCount(),
|
||||||
|
page_size,
|
||||||
|
);
|
||||||
|
self.selected_row = @intCast(pos.row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// virtual_advanced_table/state.zig
|
||||||
|
pub fn pageDown(self: *Self, page_size: usize) void {
|
||||||
|
const current_row = self.getSelectedRow() orelse 0;
|
||||||
|
const pos = table_core.calculatePageDown(current_row, self.active_col, total_rows, page_size);
|
||||||
|
// Adaptar a scroll virtual...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ejemplo: Añadir nuevo renderizado
|
||||||
|
|
||||||
|
```zig
|
||||||
|
// table_core.zig
|
||||||
|
|
||||||
|
/// Dibuja indicador de celda con error de validación
|
||||||
|
pub fn drawCellErrorIndicator(
|
||||||
|
ctx: *Context,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) void {
|
||||||
|
// Borde rojo
|
||||||
|
ctx.pushCommand(Command.rectOutline(x, y, width, height, Color.rgb(255, 0, 0)));
|
||||||
|
// Icono de error en esquina
|
||||||
|
ctx.pushCommand(Command.text(x + width - 12, y + 2, "!", Color.rgb(255, 0, 0)));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API de table_core.zig
|
||||||
|
|
||||||
|
### Navegación Tab
|
||||||
|
|
||||||
|
```zig
|
||||||
|
/// Resultado de navegación
|
||||||
|
pub const TabNavigateResult = enum { navigated, tab_out };
|
||||||
|
|
||||||
|
/// Posición de celda calculada
|
||||||
|
pub const CellPosition = struct { row: usize, col: usize, result: TabNavigateResult };
|
||||||
|
|
||||||
|
/// Calcula siguiente celda (Tab)
|
||||||
|
pub fn calculateNextCell(current_row, current_col, num_cols, num_rows, wrap) CellPosition
|
||||||
|
|
||||||
|
/// Calcula celda anterior (Shift+Tab)
|
||||||
|
pub fn calculatePrevCell(current_row, current_col, num_cols, num_rows, wrap) CellPosition
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ordenación
|
||||||
|
|
||||||
|
```zig
|
||||||
|
pub const SortDirection = enum { none, ascending, descending };
|
||||||
|
|
||||||
|
pub const SortToggleResult = struct { column: ?usize, direction: SortDirection };
|
||||||
|
|
||||||
|
/// Calcula nuevo estado de ordenación al click en columna
|
||||||
|
pub fn toggleSort(current_column, current_direction, clicked_column) SortToggleResult
|
||||||
|
```
|
||||||
|
|
||||||
|
### Edición de Celda
|
||||||
|
|
||||||
|
```zig
|
||||||
|
pub const EditKeyboardResult = struct {
|
||||||
|
committed: bool, // Enter presionado
|
||||||
|
cancelled: bool, // Escape 2x
|
||||||
|
reverted: bool, // Escape 1x (revertir a original)
|
||||||
|
navigate_next: bool, // Tab
|
||||||
|
navigate_prev: bool, // Shift+Tab
|
||||||
|
text_changed: bool, // Texto modificado
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Procesa teclado en modo edición
|
||||||
|
pub fn handleEditingKeyboard(ctx, edit_buffer, edit_len, edit_cursor, escape_count, original_text) EditKeyboardResult
|
||||||
|
```
|
||||||
|
|
||||||
|
### Renderizado
|
||||||
|
|
||||||
|
```zig
|
||||||
|
/// Dibuja indicador de celda activa
|
||||||
|
pub fn drawCellActiveIndicator(ctx, x, y, width, height, row_bg, colors, has_focus) void
|
||||||
|
|
||||||
|
/// Dibuja overlay de edición
|
||||||
|
pub fn drawEditingOverlay(ctx, x, y, width, height, edit_text, cursor_pos, colors) void
|
||||||
|
|
||||||
|
/// Dibuja texto de celda
|
||||||
|
pub fn drawCellText(ctx, x, y, width, height, text, color, text_align) void
|
||||||
|
```
|
||||||
|
|
||||||
|
### Utilidades
|
||||||
|
|
||||||
|
```zig
|
||||||
|
/// Detecta doble-click
|
||||||
|
pub fn detectDoubleClick(state, current_time, row, col) bool
|
||||||
|
|
||||||
|
/// Mezcla dos colores
|
||||||
|
pub fn blendColor(base, overlay, alpha) Color
|
||||||
|
|
||||||
|
/// Compara strings case-insensitive
|
||||||
|
pub fn startsWithIgnoreCase(haystack, needle) bool
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modelo de Datos
|
||||||
|
|
||||||
|
### AdvancedTable
|
||||||
|
|
||||||
|
```zig
|
||||||
|
pub const AdvancedTableState = struct {
|
||||||
|
// Datos
|
||||||
|
rows: ArrayListUnmanaged(Row),
|
||||||
|
|
||||||
|
// Selección (por ÍNDICE)
|
||||||
|
selected_row: i32, // -1 = ninguna
|
||||||
|
selected_col: i32,
|
||||||
|
|
||||||
|
// Multi-selección
|
||||||
|
selected_rows: [128]u8, // Bitset para 1024 filas
|
||||||
|
|
||||||
|
// Edición
|
||||||
|
editing: bool,
|
||||||
|
edit_buffer: [256]u8,
|
||||||
|
edit_cursor: usize,
|
||||||
|
|
||||||
|
// Ordenación
|
||||||
|
sort_column: i32,
|
||||||
|
sort_direction: SortDirection,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### VirtualAdvancedTable
|
||||||
|
|
||||||
|
```zig
|
||||||
|
pub const VirtualAdvancedTableState = struct {
|
||||||
|
// Selección (por ID, no índice)
|
||||||
|
selected_id: ?i64, // ID del registro
|
||||||
|
active_col: usize, // Columna activa
|
||||||
|
|
||||||
|
// Ventana virtual
|
||||||
|
scroll_offset: usize, // Offset en filas
|
||||||
|
current_window: []RowData, // Datos visibles
|
||||||
|
window_start: usize, // Índice inicial de ventana
|
||||||
|
|
||||||
|
// Edición
|
||||||
|
editing_cell: ?CellId,
|
||||||
|
edit_buffer: [256]u8,
|
||||||
|
row_dirty: bool,
|
||||||
|
|
||||||
|
// Filtro
|
||||||
|
filter_buf: [256]u8,
|
||||||
|
active_chips: u16, // Bitset de chips activos
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Los tests de `table_core.zig` verifican la lógica común:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd zcatgui && zig build test
|
||||||
|
```
|
||||||
|
|
||||||
|
Tests incluidos:
|
||||||
|
- `calculateNextCell` - navegación básica, wrap, tab_out
|
||||||
|
- `calculatePrevCell` - navegación básica, wrap, tab_out
|
||||||
|
- `toggleSort` - ciclo de direcciones, cambio de columna
|
||||||
|
- `detectDoubleClick` - detección correcta de doble-click
|
||||||
|
- `blendColor` - mezcla de colores
|
||||||
|
- `startsWithIgnoreCase` - búsqueda case-insensitive
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Historial de Cambios
|
||||||
|
|
||||||
|
| Fecha | Cambio |
|
||||||
|
|-------|--------|
|
||||||
|
| 2025-12-27 | Refactorización DRY: lógica común movida a table_core.zig |
|
||||||
|
| 2025-12-26 | table_core.zig creado con funciones de renderizado compartidas |
|
||||||
|
| 2025-12-17 | VirtualAdvancedTable añadido para tablas grandes |
|
||||||
|
| 2025-12-16 | AdvancedTable implementado con CRUD Excel-style |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Referencias
|
||||||
|
|
||||||
|
- `src/widgets/table_core.zig` - Código fuente de lógica común
|
||||||
|
- `src/widgets/advanced_table/` - AdvancedTable completo
|
||||||
|
- `src/widgets/virtual_advanced_table/` - VirtualAdvancedTable completo
|
||||||
|
- `docs/ADVANCED_TABLE_DESIGN.md` - Diseño original (histórico)
|
||||||
Loading…
Reference in a new issue