//! CellEditor - Editor de celda overlay para edición inline //! //! Dibuja un campo de texto sobre una celda para edición estilo Excel. //! Utiliza el edit_buffer del VirtualAdvancedTableState. const std = @import("std"); const Context = @import("../../core/context.zig").Context; const Command = @import("../../core/command.zig"); const Style = @import("../../core/style.zig"); const Input = @import("../../core/input.zig"); const types = @import("types.zig"); const state_mod = @import("state.zig"); const CellGeometry = types.CellGeometry; const VirtualAdvancedTableState = state_mod.VirtualAdvancedTableState; /// Colores del editor de celda pub const CellEditorColors = struct { background: Style.Color = Style.Color.rgb(255, 255, 255), border: Style.Color = Style.Color.rgb(0, 120, 215), // Azul Windows text: Style.Color = Style.Color.rgb(0, 0, 0), cursor: Style.Color = Style.Color.rgb(0, 0, 0), selection: Style.Color = Style.Color.rgb(173, 214, 255), }; /// Resultado del procesamiento del CellEditor pub const CellEditorResult = struct { /// El usuario presionó Enter o Tab (commit) committed: bool = false, /// El usuario presionó Escape escaped: bool = false, /// El texto cambió text_changed: bool = false, /// Navegación solicitada después de commit navigate: NavigateDirection = .none, pub const NavigateDirection = enum { none, next_cell, // Tab prev_cell, // Shift+Tab next_row, // Enter o ↓ prev_row, // ↑ }; }; /// Dibuja el editor de celda overlay /// Retorna resultado con acciones del usuario pub fn drawCellEditor( ctx: *Context, state: *VirtualAdvancedTableState, geom: CellGeometry, colors: CellEditorColors, ) CellEditorResult { var result = CellEditorResult{}; if (!state.isEditing()) return result; // Padding interno const padding: i32 = 2; // Fondo blanco ctx.pushCommand(Command.rect( geom.x, geom.y, geom.w, geom.h, colors.background, )); // Borde azul (indica edición activa) ctx.pushCommand(Command.rectOutline( geom.x, geom.y, geom.w, geom.h, colors.border, )); // Texto actual const text = state.getEditText(); ctx.pushCommand(Command.text( geom.x + padding, geom.y + padding, text, colors.text, )); // Cursor (línea vertical parpadeante) // Calcular posición X del cursor basado en edit_cursor const cursor_x = geom.x + padding + @as(i32, @intCast(state.edit_cursor * 7)); // ~7px por caracter (monospace) const cursor_visible = (state.frame_count / 30) % 2 == 0; // Parpadeo cada 30 frames if (cursor_visible) { ctx.pushCommand(Command.rect( cursor_x, geom.y + padding, 1, // 1px de ancho geom.h - (padding * 2), colors.cursor, )); } // Procesar input de teclado result = handleCellEditorInput(ctx, state); return result; } /// Procesa input de teclado para el editor fn handleCellEditorInput(ctx: *Context, state: *VirtualAdvancedTableState) CellEditorResult { var result = CellEditorResult{}; // Procesar eventos de tecla for (ctx.input.getKeyEvents()) |event| { if (!event.pressed) continue; switch (event.key) { .escape => { result.escaped = true; return result; }, .enter => { result.committed = true; result.navigate = .next_row; return result; }, .tab => { result.committed = true; if (event.modifiers.shift) { result.navigate = .prev_cell; } else { result.navigate = .next_cell; } return result; }, .up => { result.committed = true; result.navigate = .prev_row; return result; }, .down => { result.committed = true; result.navigate = .next_row; return result; }, .left => { // Mover cursor a la izquierda if (state.edit_cursor > 0) { state.edit_cursor -= 1; } }, .right => { // Mover cursor a la derecha if (state.edit_cursor < state.edit_buffer_len) { state.edit_cursor += 1; } }, .home => { state.edit_cursor = 0; }, .end => { state.edit_cursor = state.edit_buffer_len; }, .backspace => { if (state.edit_cursor > 0 and state.edit_buffer_len > 0) { // Eliminar caracter antes del cursor const pos = state.edit_cursor - 1; // Mover caracteres hacia la izquierda std.mem.copyForwards( u8, state.edit_buffer[pos .. state.edit_buffer_len - 1], state.edit_buffer[pos + 1 .. state.edit_buffer_len], ); state.edit_buffer_len -= 1; state.edit_cursor -= 1; result.text_changed = true; } }, .delete => { if (state.edit_cursor < state.edit_buffer_len) { // Eliminar caracter en el cursor std.mem.copyForwards( u8, state.edit_buffer[state.edit_cursor .. state.edit_buffer_len - 1], state.edit_buffer[state.edit_cursor + 1 .. state.edit_buffer_len], ); state.edit_buffer_len -= 1; result.text_changed = true; } }, else => {}, } } // Procesar texto ingresado (caracteres imprimibles) const text_input = ctx.input.getTextInput(); if (text_input.len > 0) { // Insertar caracteres en la posición del cursor for (text_input) |c| { if (state.edit_buffer_len < state.edit_buffer.len - 1) { // Hacer espacio moviendo caracteres hacia la derecha if (state.edit_cursor < state.edit_buffer_len) { var i = state.edit_buffer_len; while (i > state.edit_cursor) : (i -= 1) { state.edit_buffer[i] = state.edit_buffer[i - 1]; } } state.edit_buffer[state.edit_cursor] = c; state.edit_buffer_len += 1; state.edit_cursor += 1; result.text_changed = true; } } } return result; } // ============================================================================= // Tests // ============================================================================= test "CellEditor types" { const colors = CellEditorColors{}; _ = colors; const result = CellEditorResult{}; _ = result; }