feat(virtual_advanced_table): Add keyboard/mouse editing triggers (F2, chars, column nav)

This commit is contained in:
reugenio 2025-12-26 14:54:17 +01:00
parent 97ddf28c15
commit 65f6782d24
2 changed files with 122 additions and 9 deletions

View file

@ -23,6 +23,10 @@ pub const VirtualAdvancedTableState = struct {
/// Índice de la fila con hover /// Índice de la fila con hover
hover_index: ?usize = null, 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 // Scroll y ventana
// ========================================================================= // =========================================================================
@ -434,6 +438,51 @@ pub const VirtualAdvancedTableState = struct {
self.scroll_offset_x = max_scroll; 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 // Métodos de edición CRUD Excel-style
// ========================================================================= // =========================================================================

View file

@ -345,7 +345,7 @@ pub fn virtualAdvancedTableRect(
// Handle keyboard // Handle keyboard
if (has_focus) { 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 // Handle mouse clicks on rows
@ -906,10 +906,13 @@ fn handleKeyboard(
visible_rows: usize, visible_rows: usize,
total_rows: usize, total_rows: usize,
max_scroll_x: i32, max_scroll_x: i32,
num_columns: usize,
result: *VirtualAdvancedTableResult, result: *VirtualAdvancedTableResult,
) void { ) void {
_ = provider; _ = 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 const h_scroll_step: i32 = 40; // Pixels per arrow key press
@ -918,21 +921,70 @@ fn handleKeyboard(
switch (key) { switch (key) {
.up => list_state.moveUp(), .up => list_state.moveUp(),
.down => list_state.moveDown(visible_rows), .down => list_state.moveDown(visible_rows),
.left => list_state.scrollLeft(h_scroll_step), .left => {
.right => list_state.scrollRight(h_scroll_step, max_scroll_x), // 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_up => list_state.pageUp(visible_rows),
.page_down => list_state.pageDown(visible_rows, total_rows), .page_down => list_state.pageDown(visible_rows, total_rows),
.home => { .home => {
if (ctx.input.modifiers.ctrl) {
list_state.goToStart(); list_state.goToStart();
list_state.goToStartX(); list_state.goToFirstCol();
} else {
list_state.goToFirstCol();
}
}, },
.end => { .end => {
if (ctx.input.modifiers.ctrl) {
list_state.goToEnd(visible_rows, total_rows); list_state.goToEnd(visible_rows, total_rows);
list_state.goToEndX(max_scroll_x); list_state.goToLastCol(num_columns);
} else {
list_state.goToLastCol(num_columns);
}
}, },
else => {}, 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) { if (data_idx < list_state.current_window.len) {
list_state.selectById(list_state.current_window[data_idx].id); 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 // TODO: implement double click detection with timing
// For now, double click is not supported
} }
} }
} }