Bug 1: text_input bytes corruptos al editar celda con texto pre-seleccionado - Causa: slice getTextInput() se corrompía tras deleteSelection - Fix: Copiar a buffer local antes de modificar edit_buffer Bug 2: Editor permanecía visible al hacer clic en otra fila - Causa: Falta commit implícito al abandonar fila - Fix: handleMouseClick detecta clic en fila diferente → commitEdit() Diagnóstico: Gemini | Implementación: Claude 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
246 lines
8.9 KiB
Zig
246 lines
8.9 KiB
Zig
//! VirtualAdvancedTable - Funciones de Input
|
|
//!
|
|
//! Manejo de teclado y mouse extraído del archivo principal.
|
|
|
|
const std = @import("std");
|
|
const Context = @import("../../core/context.zig").Context;
|
|
const Layout = @import("../../core/layout.zig");
|
|
const table_core = @import("../table_core/table_core.zig");
|
|
|
|
const types = @import("types.zig");
|
|
const state_mod = @import("state.zig");
|
|
const data_provider = @import("data_provider.zig");
|
|
|
|
pub const VirtualAdvancedTableState = state_mod.VirtualAdvancedTableState;
|
|
pub const VirtualAdvancedTableConfig = types.VirtualAdvancedTableConfig;
|
|
pub const DataProvider = data_provider.DataProvider;
|
|
pub const VirtualAdvancedTableResult = @import("virtual_advanced_table.zig").VirtualAdvancedTableResult;
|
|
|
|
// =============================================================================
|
|
// Helper: Check if refetch needed
|
|
// =============================================================================
|
|
|
|
pub fn needsRefetch(list_state: *VirtualAdvancedTableState, visible_rows: usize, buffer_size: usize) bool {
|
|
// Manual invalidation
|
|
if (list_state.needs_window_refresh) {
|
|
list_state.needs_window_refresh = false;
|
|
return true;
|
|
}
|
|
|
|
// First load
|
|
if (list_state.current_window.len == 0) return true;
|
|
|
|
// Check if scroll is outside current window
|
|
const scroll = list_state.nav.scroll_row;
|
|
const window_end = list_state.window_start + list_state.current_window.len;
|
|
|
|
// Refetch if scroll is near edges of window
|
|
const margin = visible_rows;
|
|
if (scroll < list_state.window_start + margin and list_state.window_start > 0) return true;
|
|
if (scroll + visible_rows + margin > window_end) return true;
|
|
|
|
_ = buffer_size;
|
|
return false;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Auto-scroll horizontal helper
|
|
// =============================================================================
|
|
|
|
pub fn ensureColumnVisible(
|
|
list_state: *VirtualAdvancedTableState,
|
|
columns: []const types.ColumnDef,
|
|
visible_width: u32,
|
|
max_scroll_x: i32,
|
|
) void {
|
|
const active_col = list_state.nav.active_col;
|
|
if (active_col >= columns.len) return;
|
|
|
|
// Calcular posición X de la columna activa
|
|
var col_start: i32 = 0;
|
|
for (columns, 0..) |col, i| {
|
|
if (i == active_col) break;
|
|
col_start += @as(i32, @intCast(col.width));
|
|
}
|
|
const col_end = col_start + @as(i32, @intCast(columns[active_col].width));
|
|
|
|
// Posición visible
|
|
const visible_start = list_state.nav.scroll_x;
|
|
const visible_end = visible_start + @as(i32, @intCast(visible_width));
|
|
|
|
// Ajustar scroll
|
|
if (col_start < visible_start) {
|
|
list_state.nav.scroll_x = col_start;
|
|
} else if (col_end > visible_end) {
|
|
list_state.nav.scroll_x = col_end - @as(i32, @intCast(visible_width));
|
|
}
|
|
|
|
// Clamp
|
|
if (list_state.nav.scroll_x < 0) list_state.nav.scroll_x = 0;
|
|
if (list_state.nav.scroll_x > max_scroll_x) list_state.nav.scroll_x = max_scroll_x;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Handle: Keyboard (Brain-in-Core pattern)
|
|
// =============================================================================
|
|
|
|
pub fn handleKeyboard(
|
|
ctx: *Context,
|
|
list_state: *VirtualAdvancedTableState,
|
|
provider: DataProvider,
|
|
visible_rows: usize,
|
|
total_rows: usize,
|
|
max_scroll_x: i32,
|
|
columns: []const types.ColumnDef,
|
|
visible_width: u32,
|
|
result: *VirtualAdvancedTableResult,
|
|
) void {
|
|
_ = provider;
|
|
|
|
const h_scroll_step: i32 = 40;
|
|
const num_columns = columns.len;
|
|
|
|
// Delegar al Core
|
|
const events = table_core.processTableEvents(ctx, list_state.isEditing());
|
|
|
|
if (!events.handled) return;
|
|
|
|
const prev_col = list_state.nav.active_col;
|
|
|
|
// Aplicar 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);
|
|
|
|
// Auto-scroll horizontal
|
|
if (list_state.nav.active_col != prev_col) {
|
|
ensureColumnVisible(list_state, columns, visible_width, max_scroll_x);
|
|
}
|
|
|
|
// Ctrl+N
|
|
if (events.insert_row) {
|
|
result.insert_row_requested = true;
|
|
list_state.enterInsertionMode();
|
|
}
|
|
|
|
// Ctrl+Delete/B
|
|
if (events.delete_row) {
|
|
result.delete_row_requested = true;
|
|
if (list_state.selected_id) |id| {
|
|
result.deleted_row_id = id;
|
|
}
|
|
}
|
|
|
|
// Ordenación
|
|
if (events.sort_by_column) |col| {
|
|
if (col < num_columns) {
|
|
result.sort_requested = true;
|
|
result.sort_column_index = col;
|
|
}
|
|
}
|
|
|
|
// Edición
|
|
if (events.start_editing) {
|
|
if (list_state.getActiveCell()) |cell| {
|
|
if (events.initial_char) |ch| {
|
|
list_state.startEditing(cell, "", ch, ctx.current_time_ms);
|
|
} else {
|
|
result.cell_committed = false;
|
|
result.edited_cell = cell;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tab sin edición
|
|
if (events.tab_out and !list_state.isEditing()) {
|
|
result.tab_out = true;
|
|
result.tab_shift = events.tab_shift;
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Handle: Mouse Click
|
|
// =============================================================================
|
|
|
|
pub fn handleMouseClick(
|
|
ctx: *Context,
|
|
bounds: Layout.Rect,
|
|
filter_bar_h: u32,
|
|
header_h: u32,
|
|
config: VirtualAdvancedTableConfig,
|
|
list_state: *VirtualAdvancedTableState,
|
|
result: *VirtualAdvancedTableResult,
|
|
) void {
|
|
const mouse = ctx.input.mousePos();
|
|
const content_y = bounds.y + @as(i32, @intCast(filter_bar_h)) + @as(i32, @intCast(header_h));
|
|
|
|
if (mouse.y >= content_y) {
|
|
const relative_y = mouse.y - content_y;
|
|
const screen_row = @as(usize, @intCast(relative_y)) / config.row_height;
|
|
|
|
const window_offset = list_state.nav.scroll_row -| list_state.window_start;
|
|
const data_idx = window_offset + screen_row;
|
|
|
|
if (data_idx < list_state.current_window.len) {
|
|
const global_row = list_state.nav.scroll_row + screen_row;
|
|
|
|
// FIX Bug 2: Si estamos editando y clic en otra fila, hacer commit implícito
|
|
if (list_state.isEditing()) {
|
|
const editing_row = list_state.cell_edit.edit_row;
|
|
if (global_row != editing_row) {
|
|
// Commit implícito: clic fuera de la fila en edición
|
|
if (list_state.commitEdit()) {
|
|
result.cell_committed = true;
|
|
result.row_committed = true;
|
|
} else {
|
|
list_state.cancelEdit();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Detect column
|
|
var clicked_col: usize = 0;
|
|
const relative_x = mouse.x - bounds.x + list_state.nav.scroll_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) {
|
|
clicked_col = col_idx;
|
|
break;
|
|
}
|
|
col_start = col_end;
|
|
}
|
|
|
|
// Double-click detection
|
|
var dc_state = list_state.nav.double_click;
|
|
|
|
const is_double_click = table_core.detectDoubleClick(
|
|
&dc_state,
|
|
ctx.current_time_ms,
|
|
@intCast(global_row),
|
|
@intCast(clicked_col),
|
|
);
|
|
|
|
list_state.nav.double_click = dc_state;
|
|
|
|
if (is_double_click and !list_state.isEditing()) {
|
|
const cell = types.CellId{ .row = global_row, .col = clicked_col };
|
|
result.edited_cell = cell;
|
|
result.double_clicked = true;
|
|
result.double_click_id = list_state.current_window[data_idx].id;
|
|
} else {
|
|
list_state.selectById(list_state.current_window[data_idx].id);
|
|
list_state.nav.active_col = clicked_col;
|
|
}
|
|
}
|
|
}
|
|
}
|