//! 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; } } } }