feat(table_core): Brain-in-Core - processTableEvents() unifica toda lógica de teclado
Arquitectura diseñada por Gemini: - TODA la lógica de decisión en table_core.zig - Widgets solo pasan eventos y reaccionan a flags - Cualquier tabla nueva hereda automáticamente Cambios: - TableEventResult: struct con TODOS los flags de acciones - processTableEvents(): función maestra que procesa teclado - Soporta: navegación, CRUD (Ctrl+N/B/Del), ordenación (Ctrl+Shift+1-9), edición - VirtualAdvancedTable refactorizado al patrón Brain-in-Core - Nuevos campos result: insert_row_requested, delete_row_requested, sort_column_index
This commit is contained in:
parent
7642ffe7f7
commit
aed811a102
2 changed files with 359 additions and 76 deletions
|
|
@ -850,6 +850,290 @@ pub fn handleEditingKeyboard(
|
|||
return result;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BRAIN-IN-CORE: Procesamiento Unificado de Eventos de Tabla (FASE C)
|
||||
// =============================================================================
|
||||
//
|
||||
// Arquitectura "Brain-in-Core" (diseñado por Gemini):
|
||||
// - TODA la lógica de decisión vive aquí
|
||||
// - Los widgets solo pasan eventos y reaccionan a los resultados
|
||||
// - Cualquier nueva tabla (CloudTable, etc.) hereda esta potencia automáticamente
|
||||
|
||||
/// Resultado completo del procesamiento de eventos de tabla.
|
||||
/// Contiene flags para TODAS las acciones posibles.
|
||||
pub const TableEventResult = struct {
|
||||
// =========================================================================
|
||||
// Navegación básica (flechas, PageUp/Down)
|
||||
// =========================================================================
|
||||
move_up: bool = false,
|
||||
move_down: bool = false,
|
||||
move_left: bool = false, // Sin Ctrl: cambiar columna
|
||||
move_right: bool = false, // Sin Ctrl: cambiar columna
|
||||
page_up: bool = false,
|
||||
page_down: bool = false,
|
||||
|
||||
// =========================================================================
|
||||
// Navegación a extremos
|
||||
// =========================================================================
|
||||
go_to_first_col: bool = false, // Home sin Ctrl
|
||||
go_to_last_col: bool = false, // End sin Ctrl
|
||||
go_to_first_row: bool = false, // Ctrl+Home: primera fila de datos
|
||||
go_to_last_row: bool = false, // Ctrl+End: última fila de datos
|
||||
|
||||
// =========================================================================
|
||||
// Scroll horizontal (Ctrl+Left/Right)
|
||||
// =========================================================================
|
||||
scroll_left: bool = false,
|
||||
scroll_right: bool = false,
|
||||
|
||||
// =========================================================================
|
||||
// CRUD (Ctrl+N, Ctrl+B, Ctrl+Delete)
|
||||
// =========================================================================
|
||||
insert_row: bool = false, // Ctrl+N: insertar nueva fila
|
||||
delete_row: bool = false, // Ctrl+Delete o Ctrl+B: eliminar fila
|
||||
|
||||
// =========================================================================
|
||||
// Ordenación (Ctrl+Shift+1..9)
|
||||
// =========================================================================
|
||||
sort_by_column: ?usize = null, // Índice de columna (0-based)
|
||||
|
||||
// =========================================================================
|
||||
// Edición (F2, Space, tecla alfanumérica)
|
||||
// =========================================================================
|
||||
start_editing: bool = false, // Iniciar edición de celda activa
|
||||
initial_char: ?u8 = null, // Caracter inicial (si fue tecla alfa)
|
||||
|
||||
// =========================================================================
|
||||
// Tab navigation
|
||||
// =========================================================================
|
||||
tab_out: bool = false, // Tab presionado (pasar focus a otro widget)
|
||||
tab_shift: bool = false, // Fue Shift+Tab (dirección inversa)
|
||||
|
||||
// =========================================================================
|
||||
// Flag general
|
||||
// =========================================================================
|
||||
handled: bool = false, // Se procesó algún evento
|
||||
};
|
||||
|
||||
/// Procesa TODOS los eventos de teclado de una tabla.
|
||||
/// Esta es la función maestra "Brain-in-Core" que centraliza toda la lógica.
|
||||
///
|
||||
/// Parámetros:
|
||||
/// - ctx: Contexto de renderizado (acceso a input)
|
||||
/// - is_editing: Si hay una celda en modo edición (ignora navegación)
|
||||
///
|
||||
/// El widget debe reaccionar a los flags retornados y actualizar su estado.
|
||||
///
|
||||
/// Ejemplo de uso en widget:
|
||||
/// ```zig
|
||||
/// const events = table_core.processTableEvents(ctx, list_state.isEditing());
|
||||
/// if (events.move_up) list_state.moveUp();
|
||||
/// if (events.move_down) list_state.moveDown(visible_rows);
|
||||
/// if (events.go_to_first_row) list_state.goToStart();
|
||||
/// if (events.insert_row) result.insert_row = true;
|
||||
/// // ... etc
|
||||
/// ```
|
||||
pub fn processTableEvents(ctx: *Context, is_editing: bool) TableEventResult {
|
||||
var result = TableEventResult{};
|
||||
|
||||
// Si hay edición activa, el CellEditor maneja las teclas
|
||||
// Solo procesamos Tab para salir del widget
|
||||
if (is_editing) {
|
||||
for (ctx.input.getKeyEvents()) |event| {
|
||||
if (!event.pressed) continue;
|
||||
if (event.key == .tab) {
|
||||
result.tab_out = true;
|
||||
result.tab_shift = event.modifiers.shift;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 1. Navegación con navKeyPressed (soporta key repeat)
|
||||
// =========================================================================
|
||||
if (ctx.input.navKeyPressed()) |key| {
|
||||
const ctrl = ctx.input.modifiers.ctrl;
|
||||
|
||||
switch (key) {
|
||||
.up => {
|
||||
result.move_up = true;
|
||||
result.handled = true;
|
||||
},
|
||||
.down => {
|
||||
result.move_down = true;
|
||||
result.handled = true;
|
||||
},
|
||||
.left => {
|
||||
if (ctrl) {
|
||||
result.scroll_left = true;
|
||||
} else {
|
||||
result.move_left = true;
|
||||
}
|
||||
result.handled = true;
|
||||
},
|
||||
.right => {
|
||||
if (ctrl) {
|
||||
result.scroll_right = true;
|
||||
} else {
|
||||
result.move_right = true;
|
||||
}
|
||||
result.handled = true;
|
||||
},
|
||||
.page_up => {
|
||||
result.page_up = true;
|
||||
result.handled = true;
|
||||
},
|
||||
.page_down => {
|
||||
result.page_down = true;
|
||||
result.handled = true;
|
||||
},
|
||||
.home => {
|
||||
if (ctrl) {
|
||||
result.go_to_first_row = true;
|
||||
result.go_to_first_col = true;
|
||||
} else {
|
||||
result.go_to_first_col = true;
|
||||
}
|
||||
result.handled = true;
|
||||
},
|
||||
.end => {
|
||||
if (ctrl) {
|
||||
result.go_to_last_row = true;
|
||||
result.go_to_last_col = true;
|
||||
} else {
|
||||
result.go_to_last_col = true;
|
||||
}
|
||||
result.handled = true;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 2. Atajos con Ctrl y teclas especiales (getKeyEvents)
|
||||
// =========================================================================
|
||||
for (ctx.input.getKeyEvents()) |event| {
|
||||
if (!event.pressed) continue;
|
||||
|
||||
// F2 o Space: iniciar edición
|
||||
if (event.key == .f2 or event.key == .space) {
|
||||
result.start_editing = true;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Tab: pasar focus al siguiente widget
|
||||
if (event.key == .tab) {
|
||||
result.tab_out = true;
|
||||
result.tab_shift = event.modifiers.shift;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Atajos con Ctrl
|
||||
if (event.modifiers.ctrl) {
|
||||
switch (event.key) {
|
||||
.n => {
|
||||
// Ctrl+N: insertar nueva fila
|
||||
result.insert_row = true;
|
||||
result.handled = true;
|
||||
return result;
|
||||
},
|
||||
.b, .delete => {
|
||||
// Ctrl+B o Ctrl+Delete: eliminar fila
|
||||
result.delete_row = true;
|
||||
result.handled = true;
|
||||
return result;
|
||||
},
|
||||
// Ctrl+Shift+1..9: ordenar por columna
|
||||
.@"1" => {
|
||||
if (event.modifiers.shift) {
|
||||
result.sort_by_column = 0;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
.@"2" => {
|
||||
if (event.modifiers.shift) {
|
||||
result.sort_by_column = 1;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
.@"3" => {
|
||||
if (event.modifiers.shift) {
|
||||
result.sort_by_column = 2;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
.@"4" => {
|
||||
if (event.modifiers.shift) {
|
||||
result.sort_by_column = 3;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
.@"5" => {
|
||||
if (event.modifiers.shift) {
|
||||
result.sort_by_column = 4;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
.@"6" => {
|
||||
if (event.modifiers.shift) {
|
||||
result.sort_by_column = 5;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
.@"7" => {
|
||||
if (event.modifiers.shift) {
|
||||
result.sort_by_column = 6;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
.@"8" => {
|
||||
if (event.modifiers.shift) {
|
||||
result.sort_by_column = 7;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
.@"9" => {
|
||||
if (event.modifiers.shift) {
|
||||
result.sort_by_column = 8;
|
||||
result.handled = true;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 3. Teclas alfanuméricas: iniciar edición con ese caracter
|
||||
// =========================================================================
|
||||
const char_input = ctx.input.getTextInput();
|
||||
if (char_input.len > 0) {
|
||||
result.start_editing = true;
|
||||
result.initial_char = char_input[0];
|
||||
result.handled = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Alias para compatibilidad (DEPRECADO - usar processTableEvents)
|
||||
pub const TableKeyboardResult = TableEventResult;
|
||||
pub const handleTableKeyboard = processTableEvents;
|
||||
|
||||
// =============================================================================
|
||||
// Edición de fila completa (commit al abandonar fila, estilo Excel)
|
||||
// =============================================================================
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ pub const VirtualAdvancedTableResult = struct {
|
|||
sort_requested: bool = false,
|
||||
sort_column: ?[]const u8 = null,
|
||||
sort_direction: SortDirection = .none,
|
||||
/// Índice de columna para ordenar (Ctrl+Shift+1..9, 0-based)
|
||||
sort_column_index: ?usize = null,
|
||||
|
||||
/// El filtro de texto cambió
|
||||
filter_changed: bool = false,
|
||||
|
|
@ -110,6 +112,12 @@ pub const VirtualAdvancedTableResult = struct {
|
|||
/// El usuario canceló edición (Escape 2x = descartar fila)
|
||||
row_discarded: bool = false,
|
||||
|
||||
/// Ctrl+N: el usuario solicitó insertar nueva fila
|
||||
insert_row_requested: bool = false,
|
||||
|
||||
/// Ctrl+Delete o Ctrl+B: el usuario solicitó eliminar fila actual
|
||||
delete_row_requested: bool = false,
|
||||
|
||||
/// Navegación solicitada después de edición
|
||||
navigate_direction: cell_editor.NavigateDirection = .none,
|
||||
|
||||
|
|
@ -969,8 +977,11 @@ fn drawScrollbarH(
|
|||
}
|
||||
|
||||
// =============================================================================
|
||||
// Handle: Keyboard
|
||||
// Handle: Keyboard (Brain-in-Core pattern)
|
||||
// =============================================================================
|
||||
//
|
||||
// Arquitectura: TODA la lógica de decisión está en table_core.processTableEvents()
|
||||
// Este handler solo traduce los flags a acciones sobre el state local.
|
||||
|
||||
fn handleKeyboard(
|
||||
ctx: *Context,
|
||||
|
|
@ -984,87 +995,75 @@ fn handleKeyboard(
|
|||
) void {
|
||||
_ = provider;
|
||||
|
||||
// Si hay edición activa, el CellEditor maneja las teclas
|
||||
if (list_state.isEditing()) return;
|
||||
|
||||
const h_scroll_step: i32 = 40; // Pixels per arrow key press
|
||||
|
||||
// Usar navKeyPressed() para soportar key repeat (tecla mantenida pulsada)
|
||||
if (ctx.input.navKeyPressed()) |key| {
|
||||
switch (key) {
|
||||
.up => list_state.moveUp(),
|
||||
.down => list_state.moveDown(visible_rows),
|
||||
.left => {
|
||||
// Con Ctrl: scroll horizontal
|
||||
// Sin Ctrl: cambiar columna activa
|
||||
if (ctx.input.modifiers.ctrl) {
|
||||
list_state.scrollLeft(h_scroll_step);
|
||||
} else {
|
||||
list_state.moveToPrevCol();
|
||||
}
|
||||
},
|
||||
.right => {
|
||||
if (ctx.input.modifiers.ctrl) {
|
||||
list_state.scrollRight(h_scroll_step, max_scroll_x);
|
||||
} else {
|
||||
list_state.moveToNextCol(num_columns);
|
||||
}
|
||||
},
|
||||
.page_up => list_state.pageUp(visible_rows),
|
||||
.page_down => list_state.pageDown(visible_rows, total_rows),
|
||||
.home => {
|
||||
if (ctx.input.modifiers.ctrl) {
|
||||
list_state.goToStart();
|
||||
list_state.goToFirstCol();
|
||||
} else {
|
||||
list_state.goToFirstCol();
|
||||
}
|
||||
},
|
||||
.end => {
|
||||
if (ctx.input.modifiers.ctrl) {
|
||||
list_state.goToEnd(visible_rows, total_rows);
|
||||
list_state.goToLastCol(num_columns);
|
||||
} else {
|
||||
list_state.goToLastCol(num_columns);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
// =========================================================================
|
||||
// BRAIN-IN-CORE: Delegar toda la lógica de decisión al Core
|
||||
// =========================================================================
|
||||
const events = table_core.processTableEvents(ctx, list_state.isEditing());
|
||||
|
||||
if (!events.handled) return;
|
||||
|
||||
// =========================================================================
|
||||
// Aplicar acciones de navegación
|
||||
// =========================================================================
|
||||
if (events.move_up) list_state.moveUp();
|
||||
if (events.move_down) list_state.moveDown(visible_rows);
|
||||
if (events.move_left) list_state.moveToPrevCol();
|
||||
if (events.move_right) list_state.moveToNextCol(num_columns);
|
||||
if (events.page_up) list_state.pageUp(visible_rows);
|
||||
if (events.page_down) list_state.pageDown(visible_rows, total_rows);
|
||||
if (events.go_to_first_col) list_state.goToFirstCol();
|
||||
if (events.go_to_last_col) list_state.goToLastCol(num_columns);
|
||||
if (events.go_to_first_row) list_state.goToStart();
|
||||
if (events.go_to_last_row) list_state.goToEnd(visible_rows, total_rows);
|
||||
if (events.scroll_left) list_state.scrollLeft(h_scroll_step);
|
||||
if (events.scroll_right) list_state.scrollRight(h_scroll_step, max_scroll_x);
|
||||
|
||||
// =========================================================================
|
||||
// Propagar acciones CRUD al result (el panel las manejará)
|
||||
// =========================================================================
|
||||
if (events.insert_row) {
|
||||
result.insert_row_requested = true;
|
||||
}
|
||||
if (events.delete_row) {
|
||||
result.delete_row_requested = true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Ordenación
|
||||
// =========================================================================
|
||||
if (events.sort_by_column) |col| {
|
||||
if (col < num_columns) {
|
||||
result.sort_requested = true;
|
||||
result.sort_column_index = col;
|
||||
}
|
||||
}
|
||||
|
||||
// F2 o Space: iniciar edición de celda activa
|
||||
// Tab: pasar focus al siguiente widget (solo si NO estamos editando)
|
||||
for (ctx.input.getKeyEvents()) |event| {
|
||||
if (!event.pressed) continue;
|
||||
|
||||
switch (event.key) {
|
||||
.f2, .space => {
|
||||
// Iniciar edición de celda activa
|
||||
if (list_state.getActiveCell()) |cell| {
|
||||
// El panel debe proveer el valor actual via callback
|
||||
// Por ahora iniciamos con texto vacío - el panel debería llamar startEditing
|
||||
result.cell_committed = false; // Flag especial: indica que se solicitó edición
|
||||
result.edited_cell = cell;
|
||||
}
|
||||
},
|
||||
.tab => {
|
||||
// Tab sin edición activa: indica que el panel debe mover focus
|
||||
// IMPORTANTE: Solo si CellEditor no procesó Tab (evita doble procesamiento)
|
||||
if (result.navigate_direction == .none) {
|
||||
result.tab_out = true;
|
||||
result.tab_shift = event.modifiers.shift;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Teclas alfanuméricas: iniciar edición con ese caracter
|
||||
const char_input = ctx.input.getTextInput();
|
||||
if (char_input.len > 0 and !list_state.isEditing()) {
|
||||
// =========================================================================
|
||||
// Inicio de edición
|
||||
// =========================================================================
|
||||
if (events.start_editing) {
|
||||
if (list_state.getActiveCell()) |cell| {
|
||||
// Iniciar edición con el primer caracter
|
||||
list_state.startEditing(cell, "", char_input[0], ctx.current_time_ms);
|
||||
if (events.initial_char) |ch| {
|
||||
// Tecla alfanumérica: iniciar con ese caracter
|
||||
list_state.startEditing(cell, "", ch, ctx.current_time_ms);
|
||||
} else {
|
||||
// F2/Space: señalar al panel que debe iniciar edición
|
||||
result.cell_committed = false;
|
||||
result.edited_cell = cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Tab navigation
|
||||
// =========================================================================
|
||||
if (events.tab_out) {
|
||||
// Solo si CellEditor no procesó Tab (evita doble procesamiento)
|
||||
if (result.navigate_direction == .none) {
|
||||
result.tab_out = true;
|
||||
result.tab_shift = events.tab_shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue