From 65f6782d245ecbde76785caa5ceb71f6b95bcc7a Mon Sep 17 00:00:00 2001 From: reugenio Date: Fri, 26 Dec 2025 14:54:17 +0100 Subject: [PATCH] feat(virtual_advanced_table): Add keyboard/mouse editing triggers (F2, chars, column nav) --- src/widgets/virtual_advanced_table/state.zig | 49 +++++++++++ .../virtual_advanced_table.zig | 82 +++++++++++++++++-- 2 files changed, 122 insertions(+), 9 deletions(-) diff --git a/src/widgets/virtual_advanced_table/state.zig b/src/widgets/virtual_advanced_table/state.zig index 971f31d..9703804 100644 --- a/src/widgets/virtual_advanced_table/state.zig +++ b/src/widgets/virtual_advanced_table/state.zig @@ -23,6 +23,10 @@ pub const VirtualAdvancedTableState = struct { /// Índice de la fila con hover hover_index: ?usize = null, + /// Columna activa (para edición) + /// Cuando el usuario navega con flechas o hace click, se actualiza + active_col: usize = 0, + // ========================================================================= // Scroll y ventana // ========================================================================= @@ -434,6 +438,51 @@ pub const VirtualAdvancedTableState = struct { self.scroll_offset_x = max_scroll; } + /// Mueve a columna anterior + pub fn moveToPrevCol(self: *Self) void { + if (self.active_col > 0) { + self.active_col -= 1; + } + } + + /// Mueve a columna siguiente + pub fn moveToNextCol(self: *Self, max_cols: usize) void { + if (self.active_col + 1 < max_cols) { + self.active_col += 1; + } + } + + /// Va a primera columna + pub fn goToFirstCol(self: *Self) void { + self.active_col = 0; + } + + /// Va a última columna + pub fn goToLastCol(self: *Self, max_cols: usize) void { + if (max_cols > 0) { + self.active_col = max_cols - 1; + } + } + + /// Obtiene la fila global seleccionada + pub fn getSelectedRow(self: *const Self) ?usize { + if (self.selected_id == null) return null; + + // Buscar en la ventana actual + if (self.findSelectedInWindow()) |window_idx| { + return self.windowToGlobalIndex(window_idx); + } + return null; + } + + /// Obtiene la celda activa (fila seleccionada + columna activa) + pub fn getActiveCell(self: *const Self) ?CellId { + if (self.getSelectedRow()) |row| { + return .{ .row = row, .col = self.active_col }; + } + return null; + } + // ========================================================================= // Métodos de edición CRUD Excel-style // ========================================================================= diff --git a/src/widgets/virtual_advanced_table/virtual_advanced_table.zig b/src/widgets/virtual_advanced_table/virtual_advanced_table.zig index 0e5f074..a25e0e0 100644 --- a/src/widgets/virtual_advanced_table/virtual_advanced_table.zig +++ b/src/widgets/virtual_advanced_table/virtual_advanced_table.zig @@ -345,7 +345,7 @@ pub fn virtualAdvancedTableRect( // Handle keyboard if (has_focus) { - handleKeyboard(ctx, list_state, provider, visible_rows, total_rows, max_scroll_x, &result); + handleKeyboard(ctx, list_state, provider, visible_rows, total_rows, max_scroll_x, config.columns.len, &result); } // Handle mouse clicks on rows @@ -906,10 +906,13 @@ fn handleKeyboard( visible_rows: usize, total_rows: usize, max_scroll_x: i32, + num_columns: usize, result: *VirtualAdvancedTableResult, ) void { _ = provider; - _ = result; + + // 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 @@ -918,21 +921,70 @@ fn handleKeyboard( switch (key) { .up => list_state.moveUp(), .down => list_state.moveDown(visible_rows), - .left => list_state.scrollLeft(h_scroll_step), - .right => list_state.scrollRight(h_scroll_step, max_scroll_x), + .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 => { - list_state.goToStart(); - list_state.goToStartX(); + if (ctx.input.modifiers.ctrl) { + list_state.goToStart(); + list_state.goToFirstCol(); + } else { + list_state.goToFirstCol(); + } }, .end => { - list_state.goToEnd(visible_rows, total_rows); - list_state.goToEndX(max_scroll_x); + if (ctx.input.modifiers.ctrl) { + list_state.goToEnd(visible_rows, total_rows); + list_state.goToLastCol(num_columns); + } else { + list_state.goToLastCol(num_columns); + } }, else => {}, } } + + // F2 o Space: iniciar edición de celda activa + 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; + } + }, + 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()) { + if (list_state.getActiveCell()) |cell| { + // Iniciar edición con el primer caracter + list_state.startEditing(cell, "", char_input[0]); + } + } } // ============================================================================= @@ -966,8 +1018,20 @@ fn handleMouseClick( if (data_idx < list_state.current_window.len) { list_state.selectById(list_state.current_window[data_idx].id); - // Check for double click + // Detect which column was clicked + const relative_x = mouse.x - bounds.x + list_state.scroll_offset_x; + var col_start: i32 = 0; + for (config.columns, 0..) |col, col_idx| { + const col_end = col_start + @as(i32, @intCast(col.width)); + if (relative_x >= col_start and relative_x < col_end) { + list_state.active_col = col_idx; + break; + } + col_start = col_end; + } + // TODO: implement double click detection with timing + // For now, double click is not supported } } }