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