Cambios principales: - Nuevo FocusSystem unificado en core/focus.zig - Separación registration_group / active_group para multi-panel - Focus implícito para primer widget del grupo activo - Table inicializa selected_row/col a 0 cuando tiene datos - Corregido test navKeyPressed (usaba setKeyState en vez de handleKeyEvent) Bug resuelto: tabla no respondía a teclado sin clic previo Causa: selected_col quedaba en -1, selectedCell() retornaba null Documentación: docs/FOCUS_TRANSITION_2025-12-11.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.1 KiB
Sistema de Focus - Resolucion Final (2025-12-11)
ESTADO: RESUELTO Fecha resolucion: 2025-12-11 ~19:00 Probado en: zsimifactu (tabla lista clientes + panel detalle)
RESUMEN EJECUTIVO
El sistema de focus de zcatgui tenia dos bugs que impedian la navegacion por teclado al iniciar una aplicacion:
- Bug 1:
selected_colse inicializaba a -1, causando queselectedCell()retornara null - Bug 2: Sin celda seleccionada valida, los cambios de seleccion no se propagaban
Solucion: Inicializar selected_row y selected_col a 0 cuando la tabla tiene datos.
ANALISIS DEL PROBLEMA
Sintoma
- Al iniciar el programa, la tabla mostraba focus visual (borde azul)
- Las flechas del teclado NO movian la seleccion
- Al hacer clic con raton, todo funcionaba correctamente
Investigacion (debug prints)
TABLE handleKeyboard: nav=down, selected_row=0
DOWN: MOVED to row 1
AFTER tableRect[frame=7]: selection_changed=true, selected_row=1, selected_col=-1
SELECTION_CHANGED[frame=7]: selectedCell() returned null! <-- AQUI EL BUG
SYNC[frame=8]: DM idx=0, table row=1 -> forcing to 0 <-- DM no se actualizo
Causa raiz
TableStateinicializaselected_row = -1yselected_col = -1- El teclado solo modifica
selected_row, noselected_col selectedCell()retornanullsi cualquiera es < 0- Sin celda valida, el DataManager no se notifica del cambio
- En el siguiente frame, el SYNC del DataManager restaura
selected_rowa 0
Por que funcionaba con clic
El clic llama a selectCell(row, col) que inicializa AMBOS valores correctamente.
SOLUCION IMPLEMENTADA
Archivo: zcatgui/src/widgets/table.zig
En tableRectFull(), despues de verificar que hay datos:
// Ensure valid selection if table has data
// Without this, selected_row/col stay at -1 until user clicks,
// which breaks keyboard navigation and selectedCell() returns null
if (state.row_count > 0 and columns.len > 0) {
if (state.selected_row < 0) state.selected_row = 0;
if (state.selected_col < 0) state.selected_col = 0;
}
Por que esta solucion es correcta
- Es logica: si una tabla tiene datos, debe tener una celda seleccionada por defecto
- Se hace en la libreria, no en la aplicacion (principio de resolver en origen)
- No rompe compatibilidad: las apps que ya usaban clic siguen funcionando
- Beneficia a todas las apps que usen zcatgui
OTROS CAMBIOS REALIZADOS (durante investigacion)
1. Focus implicito (conservar)
Archivo: zcatgui/src/core/focus.zig
hasFocus() y getFocused() ahora retornan el primer widget si focused_index == null:
- Permite que widgets respondan al teclado desde el primer frame
endFrame()convierte el focus implicito en explicito
2. Separacion registration_group / active_group (conservar)
Archivos: focus.zig, context.zig
registration_group: donde se registran widgets durante drawactive_group: grupo con focus de teclado (solo cambia con F6/clic)
3. Test de input corregido (conservar)
Archivo: zcatgui/src/core/input.zig
El test navKeyPressed usaba setKeyState() pero debia usar handleKeyEvent().
ESTADO DE PRUEBAS
| Funcionalidad | Estado | Notas |
|---|---|---|
| Focus visual exclusivo | OK | Solo un panel con borde azul |
| F6 cambia entre paneles | OK | |
| Clic cambia focus | OK | |
| Flechas mueven seleccion tabla | OK | Ahora funciona desde inicio |
| Tab entre TextInputs | OK | |
| Seleccion se propaga a DataManager | OK | Panel detalle se actualiza |
| CPU idle ~0% | OK | SDL_WaitEventTimeout |
ARQUITECTURA FINAL DEL SISTEMA DE FOCUS
FocusSystem
|
+---------------+---------------+
| |
FocusGroup 1 FocusGroup 2
(Panel Lista) (Panel Detalle)
| |
Table widget TextInput widgets
| |
- registerFocusable() - registerFocusable()
- hasFocus() -> true/false - hasFocus() -> true/false
Flujo por frame:
beginFrame()- limpia registros de widgetssetRegistrationGroup(1)- panel 1 registra sus widgetssetRegistrationGroup(2)- panel 2 registra sus widgets- Widgets preguntan
hasFocus()durante draw endFrame()- valida focus, procesa Tab pendiente
Invariantes:
- Solo UN grupo tiene
active_groupa la vez - El primer widget del grupo activo tiene focus implicito si
focused_index == null selectedCell()requiereselected_row >= 0Yselected_col >= 0
LECCION APRENDIDA
Siempre inicializar estado completo, no parcial.
El bug surgio porque selected_row se manejaba correctamente pero selected_col se ignoraba.
Cualquier funcion que dependa de multiples campos (como selectedCell()) fallara si alguno no esta inicializado.
Documento cerrado: 2025-12-11 ~19:00 Autores: Arno + Claude (Opus 4.5)