fix(advanced_table): Teclado funciona - result.selected_row/col
Bug: Las flechas no movían la selección en AdvancedTable. Causa raíz: - handleKeyboard seteaba result.selection_changed = true - PERO NO seteaba result.selected_row / result.selected_col - zsimifactu sincroniza selección desde DataManager cada frame - Sin esos valores, DataManager no se actualizaba - Siguiente frame: selección se reseteaba al valor anterior Solución: - Añadir result.selected_row y result.selected_col a todas las teclas de navegación (up, down, left, right, page_up, page_down, home, end) Cambios visuales: - Celda seleccionada: borde + tinte sutil (15%) en lugar de fondo sólido azul 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
654183c0eb
commit
bd95013ffc
4 changed files with 345 additions and 139 deletions
28
CLAUDE.md
28
CLAUDE.md
|
|
@ -21,7 +21,7 @@
|
||||||
### Paso 3: Leer documentación
|
### Paso 3: Leer documentación
|
||||||
```
|
```
|
||||||
REFERENCE.md # ⭐ MANUAL DE REFERENCIA COMPLETO
|
REFERENCE.md # ⭐ MANUAL DE REFERENCIA COMPLETO
|
||||||
docs/BUG_ADVANCEDTABLE_KEYBOARD_2025-12-17.md # 🔴 BUG PENDIENTE - LEER PRIMERO
|
docs/BUG_ADVANCEDTABLE_KEYBOARD_2025-12-17.md # ✅ BUG RESUELTO
|
||||||
docs/ADVANCED_TABLE_MERGE_PLAN.md # Plan merge Table → AdvancedTable
|
docs/ADVANCED_TABLE_MERGE_PLAN.md # Plan merge Table → AdvancedTable
|
||||||
docs/research/DVUI_AUDIT_2025-12-17.md # Auditoría DVUI
|
docs/research/DVUI_AUDIT_2025-12-17.md # Auditoría DVUI
|
||||||
docs/DEVELOPMENT_PLAN.md # Plan de desarrollo por fases
|
docs/DEVELOPMENT_PLAN.md # Plan de desarrollo por fases
|
||||||
|
|
@ -813,35 +813,27 @@ const stdout = std.fs.File.stdout(); // NO std.io.getStdOut()
|
||||||
| 2025-12-17 | v0.18.0 | Paridad Visual DVUI Fase 1: RenderMode dual, esquinas redondeadas, sombras |
|
| 2025-12-17 | v0.18.0 | Paridad Visual DVUI Fase 1: RenderMode dual, esquinas redondeadas, sombras |
|
||||||
| 2025-12-17 | v0.19.0 | Paridad Visual DVUI Fase 2: HoverTransition, Focus Ring AA en 9 widgets |
|
| 2025-12-17 | v0.19.0 | Paridad Visual DVUI Fase 2: HoverTransition, Focus Ring AA en 9 widgets |
|
||||||
| 2025-12-17 | v0.20.0 | AdvancedTable: 8 fases completas (~2,700 LOC) - Schema, CRUD, Sorting, Lookup |
|
| 2025-12-17 | v0.20.0 | AdvancedTable: 8 fases completas (~2,700 LOC) - Schema, CRUD, Sorting, Lookup |
|
||||||
| 2025-12-17 | v0.21.0 | AdvancedTable: +990 LOC (multi-select, search, validation) - 🔴 BUG TECLADO |
|
| 2025-12-17 | v0.21.0 | AdvancedTable: +990 LOC (multi-select, search, validation) |
|
||||||
|
| 2025-12-17 | v0.21.1 | Fix: AdvancedTable teclado - result.selected_row/col en handleKeyboard |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔴 BUG PENDIENTE: AdvancedTable Teclado
|
## ✅ BUG RESUELTO: AdvancedTable Teclado
|
||||||
|
|
||||||
> **Estado:** NO RESUELTO (2025-12-17)
|
> **Estado:** RESUELTO (2025-12-17 19:30)
|
||||||
> **Documentación:** `docs/BUG_ADVANCEDTABLE_KEYBOARD_2025-12-17.md`
|
> **Documentación:** `docs/BUG_ADVANCEDTABLE_KEYBOARD_2025-12-17.md`
|
||||||
|
|
||||||
### Síntoma
|
### Causa raíz
|
||||||
Las flechas ↑↓←→ no mueven la selección en AdvancedTable (zsimifactu WHO panel).
|
`handleKeyboard` seteaba `result.selection_changed = true` pero NO seteaba `result.selected_row` / `result.selected_col`. zsimifactu sincroniza selección desde DataManager cada frame, y sin esos valores no actualizaba DataManager → reset al valor anterior.
|
||||||
|
|
||||||
### Lo que sabemos
|
### Solución
|
||||||
- Click en filas funciona
|
Añadir `result.selected_row` y `result.selected_col` a todas las teclas de navegación (up, down, left, right, page_up, page_down, home, end).
|
||||||
- `has_focus=true` cuando widget tiene foco
|
|
||||||
- `navKeyPressed()` detecta teclas (probado con debug)
|
|
||||||
- Pero `handleKeyboard()` no las procesa
|
|
||||||
|
|
||||||
### Pista clave
|
|
||||||
El widget **Table original** SÍ funciona con teclado. Comparar implementaciones.
|
|
||||||
|
|
||||||
### Próximo paso
|
|
||||||
Añadir debug DENTRO de `handleKeyboard()` (no antes de `if (has_focus)`).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ESTADO ACTUAL
|
## ESTADO ACTUAL
|
||||||
|
|
||||||
**✅ PROYECTO COMPLETADO - v0.21.0** (con bug pendiente en AdvancedTable)
|
**✅ PROYECTO COMPLETADO - v0.21.1**
|
||||||
|
|
||||||
> **Para detalles técnicos completos, ver `REFERENCE.md`** (1370 líneas de documentación)
|
> **Para detalles técnicos completos, ver `REFERENCE.md`** (1370 líneas de documentación)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Bug: AdvancedTable Keyboard Navigation No Funciona
|
# Bug: AdvancedTable Keyboard Navigation No Funciona
|
||||||
|
|
||||||
> **Fecha:** 2025-12-17
|
> **Fecha:** 2025-12-17
|
||||||
> **Estado:** 🔴 NO RESUELTO
|
> **Estado:** ✅ RESUELTO
|
||||||
> **Severidad:** ALTA
|
> **Severidad:** ALTA
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -12,123 +12,105 @@ Las flechas de teclado no mueven la selección en AdvancedTable cuando se usa en
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Lo que sabemos
|
## Causa Raíz (ENCONTRADA)
|
||||||
|
|
||||||
### Funciona
|
El problema era una combinación de dos factores:
|
||||||
- El widget se renderiza correctamente
|
|
||||||
- El click en filas funciona (cambia selección)
|
|
||||||
- El sorting visual funciona (click en headers)
|
|
||||||
- `has_focus=true` cuando se hace click en la tabla
|
|
||||||
- `keyboard_nav=true` en la configuración
|
|
||||||
|
|
||||||
### No funciona
|
### 1. `handleKeyboard` no seteaba `result.selected_row` / `result.selected_col`
|
||||||
- Flechas ↑↓←→ no mueven la selección
|
|
||||||
- No hay mensajes de `selection_changed` después de presionar flechas
|
|
||||||
|
|
||||||
---
|
En el código original:
|
||||||
|
|
||||||
## Investigación realizada
|
|
||||||
|
|
||||||
### 1. Cambio de keyPressed a navKeyPressed
|
|
||||||
**Hipótesis:** `keyPressed()` no incluye key repeats.
|
|
||||||
**Cambio:** Línea 566 de `advanced_table.zig` usa `navKeyPressed()`.
|
|
||||||
**Resultado:** No resuelve el problema.
|
|
||||||
|
|
||||||
### 2. Debug print consumía el evento
|
|
||||||
**Descubrimiento:** Al añadir debug print que llamaba `navKeyPressed()` ANTES del bloque `if (has_focus)`, el evento se consumía y no llegaba a `handleKeyboard`.
|
|
||||||
**Evidencia:** Con debug print, aparecían mensajes `navKeyPressed=.down, has_focus=true, keyboard_nav=true` pero sin movimiento.
|
|
||||||
**Conclusión:** `navKeyPressed()` solo puede leerse UNA VEZ por frame.
|
|
||||||
|
|
||||||
### 3. Sin debug print, nada funciona
|
|
||||||
**Observación:** Al quitar el debug print, ni siquiera hay mensajes en consola.
|
|
||||||
**Implicación:** `handleKeyboard` no se está ejecutando, o `navKeyPressed()` devuelve null.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Código actual relevante
|
|
||||||
|
|
||||||
### advanced_table.zig líneas 165-180
|
|
||||||
```zig
|
```zig
|
||||||
// Handle keyboard
|
.down => {
|
||||||
if (has_focus) {
|
|
||||||
if (table_state.editing) {
|
|
||||||
handleEditingKeyboard(ctx, table_state, table_schema, &result);
|
|
||||||
drawEditingOverlay(ctx, bounds, table_state, table_schema, header_h, state_col_w, colors);
|
|
||||||
} else if (config.keyboard_nav) {
|
|
||||||
handleKeyboard(ctx, table_state, table_schema, visible_rows, &result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### handleKeyboard líneas 570-590
|
|
||||||
```zig
|
|
||||||
if (ctx.input.navKeyPressed()) |nav_key| {
|
|
||||||
switch (nav_key) {
|
|
||||||
.up => {
|
|
||||||
if (table_state.selected_row > 0) {
|
|
||||||
table_state.selectCell(...);
|
|
||||||
result.selection_changed = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.down => {
|
|
||||||
if (table_state.selected_row < row_count - 1) {
|
if (table_state.selected_row < row_count - 1) {
|
||||||
table_state.selectCell(...);
|
table_state.selectCell(...);
|
||||||
result.selection_changed = true;
|
result.selection_changed = true;
|
||||||
|
// FALTABA: result.selected_row = new_row;
|
||||||
|
// FALTABA: result.selected_col = new_col;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// ... etc
|
```
|
||||||
|
|
||||||
|
### 2. zsimifactu sincroniza la selección desde DataManager cada frame
|
||||||
|
|
||||||
|
En `who_list_advanced.zig` líneas 199-205:
|
||||||
|
```zig
|
||||||
|
// Sincronizar selección con DataManager
|
||||||
|
if (dm.getSelectedWhoIndex()) |idx| {
|
||||||
|
const idx_i32: i32 = @intCast(idx);
|
||||||
|
if (self.table_state.selected_row != idx_i32) {
|
||||||
|
self.table_state.selected_row = idx_i32; // ← RESETEA cada frame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### Flujo del bug:
|
||||||
|
|
||||||
## Hipótesis pendientes de verificar
|
1. Usuario presiona flecha abajo
|
||||||
|
2. `handleKeyboard` detecta la tecla y ejecuta `selectCell(new_row, new_col)`
|
||||||
1. **¿`has_focus` es realmente true?**
|
3. `selected_row` cambia de 1 a 2 ✓
|
||||||
- El debug mostró true, pero quizás el focus se pierde entre frames
|
4. `result.selection_changed = true` ✓
|
||||||
|
5. **PERO** `result.selected_row` es `null` ✗
|
||||||
2. **¿`navKeyPressed()` funciona en AdvancedTable?**
|
6. zsimifactu verifica `if (result.selected_row) |row_idx|` → es null → no actualiza DataManager
|
||||||
- Funciona en otros widgets (Table original, TextInput)
|
7. Siguiente frame: zsimifactu sincroniza desde DataManager → `selected_row` vuelve a 1
|
||||||
- Quizás hay algo específico del contexto de AdvancedTable
|
|
||||||
|
|
||||||
3. **¿El evento de teclado llega al widget?**
|
|
||||||
- zsimifactu tiene su propio event handling en main.zig
|
|
||||||
- Quizás los eventos se consumen antes de llegar al widget
|
|
||||||
|
|
||||||
4. **¿Hay diferencia entre Table y AdvancedTable?**
|
|
||||||
- Table original funciona con teclado
|
|
||||||
- Comparar línea por línea el handling de teclado
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Próximos pasos sugeridos
|
## Solución
|
||||||
|
|
||||||
1. **Comparar con Table original** - El widget Table SÍ funciona con teclado. Comparar cómo maneja eventos.
|
Añadir `result.selected_row` y `result.selected_col` a todas las teclas de navegación en `handleKeyboard`:
|
||||||
|
|
||||||
2. **Añadir debug DENTRO de handleKeyboard** - No antes del `if (has_focus)`, sino dentro de la función misma.
|
```zig
|
||||||
|
.down => {
|
||||||
|
if (table_state.selected_row < @as(i32, @intCast(row_count)) - 1) {
|
||||||
|
const new_row: usize = @intCast(table_state.selected_row + 1);
|
||||||
|
const new_col: usize = @intCast(@max(0, table_state.selected_col));
|
||||||
|
table_state.selectCell(new_row, new_col);
|
||||||
|
result.selection_changed = true;
|
||||||
|
result.selected_row = new_row; // ← AÑADIDO
|
||||||
|
result.selected_col = new_col; // ← AÑADIDO
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
3. **Verificar InputState.navKeyPressed()** - Ver si devuelve algo cuando AdvancedTable tiene focus.
|
Teclas corregidas:
|
||||||
|
- `.up`
|
||||||
4. **Revisar el focus system** - Quizás AdvancedTable no está registrándose correctamente en el FocusSystem.
|
- `.down`
|
||||||
|
- `.left`
|
||||||
|
- `.right`
|
||||||
|
- `.page_up`
|
||||||
|
- `.page_down`
|
||||||
|
- `.home` (y Ctrl+Home)
|
||||||
|
- `.end` (y Ctrl+End)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Archivos relevantes
|
## Verificación
|
||||||
|
|
||||||
- `/mnt/cello2/arno/re/recode/zig/zcatgui/src/widgets/advanced_table/advanced_table.zig`
|
Debug añadido temporalmente mostró:
|
||||||
- `/mnt/cello2/arno/re/recode/zig/zcatgui/src/widgets/table/table.zig` (referencia funcional)
|
```
|
||||||
- `/mnt/cello2/arno/re/recode/zig/zcatgui/src/core/input.zig` (navKeyPressed)
|
[ADV-TABLE] DOWN: after selectCell, selected_row=2 ← SÍ cambia
|
||||||
- `/mnt/cello2/arno/re/recode/zig/zsimifactu/src/panels/who_list_advanced.zig`
|
[ADV-TABLE] navKey detected: down, selected_row=1 ← Pero vuelve a 1 en siguiente frame
|
||||||
- `/mnt/cello2/arno/re/recode/zig/zsimifactu/src/main.zig` (event loop)
|
```
|
||||||
|
|
||||||
|
Después del fix, las flechas funcionan correctamente.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Commit actual
|
## Archivos modificados
|
||||||
|
|
||||||
Los cambios de "merge Table → AdvancedTable" están commiteados (af1bb76) pero el bug de teclado **NO está resuelto**.
|
- `src/widgets/advanced_table/advanced_table.zig` - handleKeyboard corregido
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lecciones aprendidas
|
||||||
|
|
||||||
|
1. **El resultado de un widget debe ser completo** - Si `selection_changed = true`, también debe informar QUÉ cambió (`selected_row`, `selected_col`).
|
||||||
|
|
||||||
|
2. **Cuidado con la sincronización bidireccional** - Cuando un widget y un data manager ambos pueden modificar el mismo estado, hay que asegurar que los cambios del widget se propaguen al data manager antes de que éste resetee el estado.
|
||||||
|
|
||||||
|
3. **Debug incremental** - Añadir debug DENTRO de la función problemática (no antes) para ver el flujo real.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Documentado por: Claude Code (Opus 4.5)*
|
*Documentado por: Claude Code (Opus 4.5)*
|
||||||
*Fecha: 2025-12-17 ~19:00*
|
*Fecha resolución: 2025-12-17 ~19:30*
|
||||||
|
|
|
||||||
201
docs/PROPUESTA_WIDGETS_BROWSER.md
Normal file
201
docs/PROPUESTA_WIDGETS_BROWSER.md
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
# Propuesta: Widgets Browser Especializados
|
||||||
|
|
||||||
|
**Fecha:** 2025-12-17
|
||||||
|
**Origen:** Conversación zsimifactu sobre extracción de patrones reutilizables
|
||||||
|
**Estado:** PROPUESTA - Pendiente de implementación
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen Ejecutivo
|
||||||
|
|
||||||
|
Se propone crear widgets especializados para navegación de datos tabulares que encapsulen patrones comunes identificados en zsimifactu. Estos widgets combinarían tabla + controles de navegación + filtros en componentes cohesivos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Widgets Propuestos
|
||||||
|
|
||||||
|
### 1. TableBrowser
|
||||||
|
|
||||||
|
Widget compuesto que integra:
|
||||||
|
- Tabla con scroll y selección
|
||||||
|
- Barra de estado con posición (ej: "15/520")
|
||||||
|
- Botones de navegación (|< < > >|)
|
||||||
|
- Soporte para filtros (opcional)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ [Filtro: ___________] [Tipo: v] │ ← Zona filtros (opcional)
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ Codigo │ Nombre │ Ciudad │ ← Header tabla
|
||||||
|
├────────┼─────────────────┼─────────────────┤
|
||||||
|
│ C0001 │ Empresa ABC │ Valencia │
|
||||||
|
│ C0002 │ Empresa XYZ │ Madrid │
|
||||||
|
│ ... │ ... │ ... │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ [|<] [<] 15/520 [>] [>|] │ ← Navegación integrada
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Caso de uso:** Panel de lista en aplicaciones CRUD (WhoListPanel, DocListPanel, etc.)
|
||||||
|
|
||||||
|
### 2. ConfigBrowser
|
||||||
|
|
||||||
|
Widget especializado para visualizar/editar configuración:
|
||||||
|
- Lista de variables agrupadas por categoría
|
||||||
|
- Editor inline según tipo (checkbox, input, color picker)
|
||||||
|
- Búsqueda/filtro de variables
|
||||||
|
- Indicador de cambios pendientes
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ [Buscar: ___________] │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ ▼ General │
|
||||||
|
│ auto_guardar_cada [15 ] minutos │
|
||||||
|
│ backup_automatico [✓] │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ ▼ Apariencia │
|
||||||
|
│ color_azul_empresa [■] RGB(40,80,120) │
|
||||||
|
│ font_size [14 ] │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ ▶ Comportamiento (click para expandir) │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Caso de uso:** Panel de configuración de aplicación
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Justificación Técnica
|
||||||
|
|
||||||
|
### Patrón Repetido en zsimifactu
|
||||||
|
|
||||||
|
En `WhoListPanel` y futuros paneles de lista se repite:
|
||||||
|
1. Tabla con datos
|
||||||
|
2. Callback para obtener celdas
|
||||||
|
3. Botones navegación (First/Prev/Next/Last)
|
||||||
|
4. Indicador posición
|
||||||
|
5. Manejo de selección → DataManager
|
||||||
|
|
||||||
|
Este código se duplicaría en cada panel de lista (DocListPanel, ProdListPanel, etc.).
|
||||||
|
|
||||||
|
### Beneficios de Encapsular
|
||||||
|
|
||||||
|
| Aspecto | Sin widget | Con TableBrowser |
|
||||||
|
|---------|------------|------------------|
|
||||||
|
| Líneas por panel | ~200 | ~50 |
|
||||||
|
| Bugs de navegación | Duplicados | Corregidos una vez |
|
||||||
|
| Consistencia UX | Manual | Automática |
|
||||||
|
| Nuevos paneles | Copy-paste | Instanciar widget |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Propuesta
|
||||||
|
|
||||||
|
### TableBrowser
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const TableBrowser = struct {
|
||||||
|
table_state: widgets.table.TableState,
|
||||||
|
nav_state: NavState,
|
||||||
|
filter_state: ?FilterState,
|
||||||
|
|
||||||
|
pub const Config = struct {
|
||||||
|
columns: []const Column,
|
||||||
|
show_navigation: bool = true,
|
||||||
|
show_filters: bool = false,
|
||||||
|
row_height: u16 = 18,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Callbacks = struct {
|
||||||
|
getCellData: *const fn (row: usize, col: usize) []const u8,
|
||||||
|
getRowCount: *const fn () usize,
|
||||||
|
onSelectionChanged: ?*const fn (row: usize) void = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(config: Config) TableBrowser;
|
||||||
|
pub fn draw(self: *Self, ctx: *Context, rect: Rect, callbacks: Callbacks) DrawResult;
|
||||||
|
pub fn handleEvent(self: *Self, event: Event) bool;
|
||||||
|
|
||||||
|
// Navegación programática
|
||||||
|
pub fn goFirst(self: *Self) void;
|
||||||
|
pub fn goPrev(self: *Self) void;
|
||||||
|
pub fn goNext(self: *Self) void;
|
||||||
|
pub fn goLast(self: *Self) void;
|
||||||
|
pub fn getPosition(self: *Self) struct { current: usize, total: usize };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### ConfigBrowser
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const ConfigBrowser = struct {
|
||||||
|
pub const Config = struct {
|
||||||
|
variables: []const ConfigVariable, // De zcatconfig
|
||||||
|
show_search: bool = true,
|
||||||
|
group_by_category: bool = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(config: Config) ConfigBrowser;
|
||||||
|
pub fn draw(self: *Self, ctx: *Context, rect: Rect, config_ptr: anytype) DrawResult;
|
||||||
|
pub fn handleEvent(self: *Self, event: Event) bool;
|
||||||
|
|
||||||
|
// Estado
|
||||||
|
pub fn hasChanges(self: *Self) bool;
|
||||||
|
pub fn getChangedVariables(self: *Self) []const []const u8;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integración con zcatconfig
|
||||||
|
|
||||||
|
El `ConfigBrowser` se integraría naturalmente con la nueva librería `zcatconfig`:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const zcatconfig = @import("zcatconfig");
|
||||||
|
const ConfigBrowser = zcatgui.widgets.ConfigBrowser;
|
||||||
|
|
||||||
|
// En el proyecto consumidor:
|
||||||
|
const variables = @import("config/variables.zig");
|
||||||
|
const Config = @import("config/structures.zig").Config;
|
||||||
|
|
||||||
|
var config = Config{};
|
||||||
|
var browser = ConfigBrowser.init(.{
|
||||||
|
.variables = &variables.config_variables,
|
||||||
|
});
|
||||||
|
|
||||||
|
// En draw:
|
||||||
|
const result = browser.draw(ctx, rect, &config);
|
||||||
|
if (result.value_changed) {
|
||||||
|
try zcatconfig.save(&variables.config_variables, Config, &config, allocator, "config.txt");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priorización Sugerida
|
||||||
|
|
||||||
|
| Widget | Prioridad | Razón |
|
||||||
|
|--------|-----------|-------|
|
||||||
|
| TableBrowser | Alta | Necesario para DocListPanel, ProdListPanel |
|
||||||
|
| ConfigBrowser | Media | Panel config es nice-to-have, no bloqueante |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencias
|
||||||
|
|
||||||
|
- **TableBrowser**: Ninguna externa, usa widgets existentes (table, button)
|
||||||
|
- **ConfigBrowser**: Requiere `zcatconfig` como dependencia opcional
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Siguiente Paso
|
||||||
|
|
||||||
|
1. Revisar esta propuesta
|
||||||
|
2. Decidir si proceder con implementación
|
||||||
|
3. Crear issue/tarea en zcatgui
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Documento generado desde conversación zsimifactu 2025-12-17*
|
||||||
|
|
@ -86,6 +86,12 @@ pub fn advancedTableRect(
|
||||||
const colors = custom_colors orelse table_schema.colors orelse &default_colors;
|
const colors = custom_colors orelse table_schema.colors orelse &default_colors;
|
||||||
const config = table_schema.config;
|
const config = table_schema.config;
|
||||||
|
|
||||||
|
// Ensure valid selection if table has data (like Table widget does)
|
||||||
|
if (table_state.getRowCount() > 0 and table_schema.columns.len > 0) {
|
||||||
|
if (table_state.selected_row < 0) table_state.selected_row = 0;
|
||||||
|
if (table_state.selected_col < 0) table_state.selected_col = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Generate unique ID for focus system
|
// Generate unique ID for focus system
|
||||||
const widget_id: u64 = @intFromPtr(table_state);
|
const widget_id: u64 = @intFromPtr(table_state);
|
||||||
|
|
||||||
|
|
@ -393,9 +399,12 @@ fn drawRow(
|
||||||
const is_selected_cell = is_selected_row and table_state.selected_col == @as(i32, @intCast(col_idx));
|
const is_selected_cell = is_selected_row and table_state.selected_col == @as(i32, @intCast(col_idx));
|
||||||
const cell_clicked = cell_rect.contains(mouse.x, mouse.y) and ctx.input.mousePressed(.left);
|
const cell_clicked = cell_rect.contains(mouse.x, mouse.y) and ctx.input.mousePressed(.left);
|
||||||
|
|
||||||
// Cell background for selected cell
|
// Cell indicator for selected cell (outline instead of solid fill)
|
||||||
if (is_selected_cell) {
|
if (is_selected_cell) {
|
||||||
ctx.pushCommand(Command.rect(col_x, bounds.y, col.width, config.row_height, colors.selected_cell));
|
// Subtle background tint
|
||||||
|
ctx.pushCommand(Command.rect(col_x, bounds.y, col.width, config.row_height, blendColor(row_bg, colors.selected_cell, 0.15)));
|
||||||
|
// Border outline
|
||||||
|
ctx.pushCommand(Command.rectOutline(col_x, bounds.y, col.width, config.row_height, colors.selected_cell));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get cell value
|
// Get cell value
|
||||||
|
|
@ -567,74 +576,96 @@ fn handleKeyboard(
|
||||||
switch (nav_key) {
|
switch (nav_key) {
|
||||||
.up => {
|
.up => {
|
||||||
if (table_state.selected_row > 0) {
|
if (table_state.selected_row > 0) {
|
||||||
table_state.selectCell(
|
const new_row: usize = @intCast(table_state.selected_row - 1);
|
||||||
@intCast(table_state.selected_row - 1),
|
const new_col: usize = @intCast(@max(0, table_state.selected_col));
|
||||||
@intCast(@max(0, table_state.selected_col)),
|
table_state.selectCell(new_row, new_col);
|
||||||
);
|
|
||||||
result.selection_changed = true;
|
result.selection_changed = true;
|
||||||
|
result.selected_row = new_row;
|
||||||
|
result.selected_col = new_col;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.down => {
|
.down => {
|
||||||
if (table_state.selected_row < @as(i32, @intCast(row_count)) - 1) {
|
if (table_state.selected_row < @as(i32, @intCast(row_count)) - 1) {
|
||||||
table_state.selectCell(
|
const new_row: usize = @intCast(table_state.selected_row + 1);
|
||||||
@intCast(table_state.selected_row + 1),
|
const new_col: usize = @intCast(@max(0, table_state.selected_col));
|
||||||
@intCast(@max(0, table_state.selected_col)),
|
table_state.selectCell(new_row, new_col);
|
||||||
);
|
|
||||||
result.selection_changed = true;
|
result.selection_changed = true;
|
||||||
|
result.selected_row = new_row;
|
||||||
|
result.selected_col = new_col;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.left => {
|
.left => {
|
||||||
if (table_state.selected_col > 0) {
|
if (table_state.selected_col > 0) {
|
||||||
table_state.selectCell(
|
const new_row: usize = @intCast(@max(0, table_state.selected_row));
|
||||||
@intCast(@max(0, table_state.selected_row)),
|
const new_col: usize = @intCast(table_state.selected_col - 1);
|
||||||
@intCast(table_state.selected_col - 1),
|
table_state.selectCell(new_row, new_col);
|
||||||
);
|
|
||||||
result.selection_changed = true;
|
result.selection_changed = true;
|
||||||
|
result.selected_row = new_row;
|
||||||
|
result.selected_col = new_col;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.right => {
|
.right => {
|
||||||
if (table_state.selected_col < @as(i32, @intCast(col_count)) - 1) {
|
if (table_state.selected_col < @as(i32, @intCast(col_count)) - 1) {
|
||||||
table_state.selectCell(
|
const new_row: usize = @intCast(@max(0, table_state.selected_row));
|
||||||
@intCast(@max(0, table_state.selected_row)),
|
const new_col: usize = @intCast(table_state.selected_col + 1);
|
||||||
@intCast(table_state.selected_col + 1),
|
table_state.selectCell(new_row, new_col);
|
||||||
);
|
|
||||||
result.selection_changed = true;
|
result.selection_changed = true;
|
||||||
|
result.selected_row = new_row;
|
||||||
|
result.selected_col = new_col;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.page_up => {
|
.page_up => {
|
||||||
const new_row = @max(0, table_state.selected_row - @as(i32, @intCast(visible_rows)));
|
const new_row: usize = @intCast(@max(0, table_state.selected_row - @as(i32, @intCast(visible_rows))));
|
||||||
table_state.selectCell(@intCast(new_row), @intCast(@max(0, table_state.selected_col)));
|
const new_col: usize = @intCast(@max(0, table_state.selected_col));
|
||||||
|
table_state.selectCell(new_row, new_col);
|
||||||
result.selection_changed = true;
|
result.selection_changed = true;
|
||||||
|
result.selected_row = new_row;
|
||||||
|
result.selected_col = new_col;
|
||||||
},
|
},
|
||||||
.page_down => {
|
.page_down => {
|
||||||
const new_row = @min(
|
const new_row: usize = @intCast(@min(
|
||||||
@as(i32, @intCast(row_count)) - 1,
|
@as(i32, @intCast(row_count)) - 1,
|
||||||
table_state.selected_row + @as(i32, @intCast(visible_rows)),
|
table_state.selected_row + @as(i32, @intCast(visible_rows)),
|
||||||
);
|
));
|
||||||
table_state.selectCell(@intCast(new_row), @intCast(@max(0, table_state.selected_col)));
|
const new_col: usize = @intCast(@max(0, table_state.selected_col));
|
||||||
|
table_state.selectCell(new_row, new_col);
|
||||||
result.selection_changed = true;
|
result.selection_changed = true;
|
||||||
|
result.selected_row = new_row;
|
||||||
|
result.selected_col = new_col;
|
||||||
},
|
},
|
||||||
.home => {
|
.home => {
|
||||||
|
var new_row: usize = undefined;
|
||||||
|
var new_col: usize = undefined;
|
||||||
if (ctx.input.modifiers.ctrl) {
|
if (ctx.input.modifiers.ctrl) {
|
||||||
// Ctrl+Home: first cell
|
// Ctrl+Home: first cell
|
||||||
table_state.selectCell(0, 0);
|
new_row = 0;
|
||||||
|
new_col = 0;
|
||||||
} else {
|
} else {
|
||||||
// Home: first column
|
// Home: first column
|
||||||
table_state.selectCell(@intCast(@max(0, table_state.selected_row)), 0);
|
new_row = @intCast(@max(0, table_state.selected_row));
|
||||||
|
new_col = 0;
|
||||||
}
|
}
|
||||||
|
table_state.selectCell(new_row, new_col);
|
||||||
result.selection_changed = true;
|
result.selection_changed = true;
|
||||||
|
result.selected_row = new_row;
|
||||||
|
result.selected_col = new_col;
|
||||||
},
|
},
|
||||||
.end => {
|
.end => {
|
||||||
|
var new_row: usize = undefined;
|
||||||
|
var new_col: usize = undefined;
|
||||||
if (ctx.input.modifiers.ctrl) {
|
if (ctx.input.modifiers.ctrl) {
|
||||||
// Ctrl+End: last cell
|
// Ctrl+End: last cell
|
||||||
const last_row = if (row_count > 0) row_count - 1 else 0;
|
new_row = if (row_count > 0) row_count - 1 else 0;
|
||||||
const last_col = col_count - 1;
|
new_col = col_count - 1;
|
||||||
table_state.selectCell(last_row, last_col);
|
|
||||||
} else {
|
} else {
|
||||||
// End: last column
|
// End: last column
|
||||||
table_state.selectCell(@intCast(@max(0, table_state.selected_row)), col_count - 1);
|
new_row = @intCast(@max(0, table_state.selected_row));
|
||||||
|
new_col = col_count - 1;
|
||||||
}
|
}
|
||||||
|
table_state.selectCell(new_row, new_col);
|
||||||
result.selection_changed = true;
|
result.selection_changed = true;
|
||||||
|
result.selected_row = new_row;
|
||||||
|
result.selected_col = new_col;
|
||||||
},
|
},
|
||||||
.tab => {
|
.tab => {
|
||||||
if (config.handle_tab) {
|
if (config.handle_tab) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue