From e8b4c98d4af0487ce1b6799e899e88558c63c045 Mon Sep 17 00:00:00 2001 From: reugenio Date: Sat, 27 Dec 2025 01:58:40 +0100 Subject: [PATCH] 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 --- docs/TABLES_ARCHITECTURE.md | 372 ++++++++++++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 docs/TABLES_ARCHITECTURE.md diff --git a/docs/TABLES_ARCHITECTURE.md b/docs/TABLES_ARCHITECTURE.md new file mode 100644 index 0000000..c265e50 --- /dev/null +++ b/docs/TABLES_ARCHITECTURE.md @@ -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)