refactor(states): Embed CellEditState in AdvancedTableState and VirtualAdvancedTableState
FASE 2 del refactor de tablas: - AdvancedTableState: Embed cell_edit, delegate editing methods - VirtualAdvancedTableState: Embed cell_edit, replace editing_cell/edit_buffer - Update advanced_table.zig to use isEditing() and cell_edit.* - Update virtual_advanced_table.zig to use getEditingCell() - Update cell_editor.zig to use cell_edit.* Reduces code duplication, centralizes editing logic in table_core
This commit is contained in:
parent
6819919060
commit
37e3b61aca
5 changed files with 96 additions and 144 deletions
|
|
@ -175,7 +175,7 @@ pub fn advancedTableRect(
|
|||
|
||||
// Handle keyboard
|
||||
if (has_focus) {
|
||||
if (table_state.editing) {
|
||||
if (table_state.isEditing()) {
|
||||
// Handle editing keyboard
|
||||
handleEditingKeyboard(ctx, table_state, table_schema, &result);
|
||||
|
||||
|
|
@ -444,7 +444,7 @@ fn drawRow(
|
|||
const time_diff = current_time -| table_state.last_click_time;
|
||||
const is_double_click = same_cell and time_diff < table_state.double_click_threshold_ms;
|
||||
|
||||
if (is_double_click and config.allow_edit and col.editable and !table_state.editing) {
|
||||
if (is_double_click and config.allow_edit and col.editable and !table_state.isEditing()) {
|
||||
// Double-click: start editing
|
||||
if (table_state.getRow(row_idx)) |row| {
|
||||
const value = row.get(col.name);
|
||||
|
|
@ -594,7 +594,7 @@ fn drawEditingOverlay(
|
|||
ctx.pushCommand(Command.text(col_x + 4, text_y, edit_text, colors.text_selected));
|
||||
|
||||
// Draw cursor
|
||||
const cursor_x = col_x + 4 + @as(i32, @intCast(table_state.edit_cursor * 8));
|
||||
const cursor_x = col_x + 4 + @as(i32, @intCast(table_state.cell_edit.edit_cursor * 8));
|
||||
ctx.pushCommand(Command.rect(cursor_x, text_y, 1, 8, colors.text_selected));
|
||||
}
|
||||
|
||||
|
|
@ -933,10 +933,10 @@ fn handleEditingKeyboard(
|
|||
// Usar table_core para procesamiento de teclado (DRY)
|
||||
const kb_result = table_core.handleEditingKeyboard(
|
||||
ctx,
|
||||
&table_state.edit_buffer,
|
||||
&table_state.edit_len,
|
||||
&table_state.edit_cursor,
|
||||
&table_state.escape_count,
|
||||
&table_state.cell_edit.edit_buffer,
|
||||
&table_state.cell_edit.edit_len,
|
||||
&table_state.cell_edit.edit_cursor,
|
||||
&table_state.cell_edit.escape_count,
|
||||
original_text,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -90,27 +90,16 @@ pub const AdvancedTableState = struct {
|
|||
last_validation_message_len: usize = 0,
|
||||
|
||||
// =========================================================================
|
||||
// Editing
|
||||
// Editing (usa CellEditState de table_core para composición)
|
||||
// =========================================================================
|
||||
|
||||
/// Is currently editing a cell
|
||||
editing: bool = false,
|
||||
|
||||
/// Edit buffer for current edit
|
||||
edit_buffer: [MAX_EDIT_BUFFER]u8 = undefined,
|
||||
|
||||
/// Length of text in edit buffer
|
||||
edit_len: usize = 0,
|
||||
|
||||
/// Cursor position in edit buffer
|
||||
edit_cursor: usize = 0,
|
||||
/// Estado de edición embebido (Fase 2 refactor)
|
||||
cell_edit: table_core.CellEditState = .{},
|
||||
|
||||
/// Original value before editing (for revert on Escape)
|
||||
/// NOTA: Mantenemos esto porque CellValue es más rico que buffer crudo
|
||||
original_value: ?CellValue = null,
|
||||
|
||||
/// Escape count (1 = revert, 2 = cancel)
|
||||
escape_count: u8 = 0,
|
||||
|
||||
// =========================================================================
|
||||
// Double-click detection
|
||||
// =========================================================================
|
||||
|
|
@ -303,9 +292,7 @@ pub const AdvancedTableState = struct {
|
|||
self.prev_selected_col = -1;
|
||||
|
||||
// Clear editing
|
||||
self.editing = false;
|
||||
self.edit_len = 0;
|
||||
self.escape_count = 0;
|
||||
self.cell_edit.stopEditing();
|
||||
|
||||
// Clear sorting
|
||||
self.sort_column = -1;
|
||||
|
|
@ -689,76 +676,81 @@ pub const AdvancedTableState = struct {
|
|||
}
|
||||
|
||||
// =========================================================================
|
||||
// Editing
|
||||
// Editing (delega a cell_edit embebido)
|
||||
// =========================================================================
|
||||
|
||||
/// Start editing current cell
|
||||
/// Usa la celda seleccionada (selected_row, selected_col)
|
||||
pub fn startEditing(self: *AdvancedTableState, initial_value: []const u8) void {
|
||||
self.editing = true;
|
||||
self.escape_count = 0;
|
||||
|
||||
// Copy initial value to edit buffer
|
||||
const len = @min(initial_value.len, MAX_EDIT_BUFFER);
|
||||
@memcpy(self.edit_buffer[0..len], initial_value[0..len]);
|
||||
self.edit_len = len;
|
||||
self.edit_cursor = len;
|
||||
const row: usize = if (self.selected_row >= 0) @intCast(self.selected_row) else 0;
|
||||
const col: usize = if (self.selected_col >= 0) @intCast(self.selected_col) else 0;
|
||||
self.cell_edit.startEditing(row, col, initial_value, null);
|
||||
}
|
||||
|
||||
/// Stop editing
|
||||
pub fn stopEditing(self: *AdvancedTableState) void {
|
||||
self.editing = false;
|
||||
self.edit_len = 0;
|
||||
self.edit_cursor = 0;
|
||||
self.escape_count = 0;
|
||||
self.cell_edit.stopEditing();
|
||||
self.original_value = null;
|
||||
}
|
||||
|
||||
/// Get current edit text
|
||||
pub fn getEditText(self: *const AdvancedTableState) []const u8 {
|
||||
return self.edit_buffer[0..self.edit_len];
|
||||
return self.cell_edit.getEditText();
|
||||
}
|
||||
|
||||
/// Check if currently editing
|
||||
pub fn isEditing(self: *const AdvancedTableState) bool {
|
||||
return self.cell_edit.editing;
|
||||
}
|
||||
|
||||
/// Insert text at cursor position
|
||||
pub fn insertText(self: *AdvancedTableState, text: []const u8) void {
|
||||
for (text) |c| {
|
||||
if (self.edit_len < MAX_EDIT_BUFFER) {
|
||||
if (self.cell_edit.edit_len < table_core.MAX_EDIT_BUFFER_SIZE) {
|
||||
// Shift text after cursor
|
||||
var i = self.edit_len;
|
||||
while (i > self.edit_cursor) : (i -= 1) {
|
||||
self.edit_buffer[i] = self.edit_buffer[i - 1];
|
||||
var i = self.cell_edit.edit_len;
|
||||
while (i > self.cell_edit.edit_cursor) : (i -= 1) {
|
||||
self.cell_edit.edit_buffer[i] = self.cell_edit.edit_buffer[i - 1];
|
||||
}
|
||||
self.edit_buffer[self.edit_cursor] = c;
|
||||
self.edit_len += 1;
|
||||
self.edit_cursor += 1;
|
||||
self.cell_edit.edit_buffer[self.cell_edit.edit_cursor] = c;
|
||||
self.cell_edit.edit_len += 1;
|
||||
self.cell_edit.edit_cursor += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete character before cursor (backspace)
|
||||
pub fn deleteBackward(self: *AdvancedTableState) void {
|
||||
if (self.edit_cursor > 0) {
|
||||
// Shift text after cursor
|
||||
var i = self.edit_cursor - 1;
|
||||
while (i < self.edit_len - 1) : (i += 1) {
|
||||
self.edit_buffer[i] = self.edit_buffer[i + 1];
|
||||
if (self.cell_edit.edit_cursor > 0) {
|
||||
var i = self.cell_edit.edit_cursor - 1;
|
||||
while (i < self.cell_edit.edit_len - 1) : (i += 1) {
|
||||
self.cell_edit.edit_buffer[i] = self.cell_edit.edit_buffer[i + 1];
|
||||
}
|
||||
self.edit_len -= 1;
|
||||
self.edit_cursor -= 1;
|
||||
self.cell_edit.edit_len -= 1;
|
||||
self.cell_edit.edit_cursor -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete character at cursor (delete)
|
||||
pub fn deleteForward(self: *AdvancedTableState) void {
|
||||
if (self.edit_cursor < self.edit_len) {
|
||||
// Shift text after cursor
|
||||
var i = self.edit_cursor;
|
||||
while (i < self.edit_len - 1) : (i += 1) {
|
||||
self.edit_buffer[i] = self.edit_buffer[i + 1];
|
||||
if (self.cell_edit.edit_cursor < self.cell_edit.edit_len) {
|
||||
var i = self.cell_edit.edit_cursor;
|
||||
while (i < self.cell_edit.edit_len - 1) : (i += 1) {
|
||||
self.cell_edit.edit_buffer[i] = self.cell_edit.edit_buffer[i + 1];
|
||||
}
|
||||
self.edit_len -= 1;
|
||||
self.cell_edit.edit_len -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle Escape key (revert or cancel)
|
||||
pub fn handleEditEscape(self: *AdvancedTableState) table_core.CellEditState.EscapeAction {
|
||||
const action = self.cell_edit.handleEscape();
|
||||
if (action == .cancelled) {
|
||||
self.original_value = null;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Snapshots (for Auto-CRUD)
|
||||
// =========================================================================
|
||||
|
|
@ -1037,10 +1029,10 @@ test "AdvancedTableState editing" {
|
|||
var state = AdvancedTableState.init(std.testing.allocator);
|
||||
defer state.deinit();
|
||||
|
||||
try std.testing.expect(!state.editing);
|
||||
try std.testing.expect(!state.isEditing());
|
||||
|
||||
state.startEditing("Hello");
|
||||
try std.testing.expect(state.editing);
|
||||
try std.testing.expect(state.isEditing());
|
||||
try std.testing.expectEqualStrings("Hello", state.getEditText());
|
||||
|
||||
state.insertText(" World");
|
||||
|
|
@ -1050,7 +1042,7 @@ test "AdvancedTableState editing" {
|
|||
try std.testing.expectEqualStrings("Hello Worl", state.getEditText());
|
||||
|
||||
state.stopEditing();
|
||||
try std.testing.expect(!state.editing);
|
||||
try std.testing.expect(!state.isEditing());
|
||||
}
|
||||
|
||||
test "AdvancedTableState sorting" {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ pub fn drawCellEditor(
|
|||
));
|
||||
|
||||
// Cursor: posición calculada con measureTextToCursor (TTF-aware)
|
||||
const cursor_offset = ctx.measureTextToCursor(text, state.edit_cursor);
|
||||
const cursor_offset = ctx.measureTextToCursor(text, state.cell_edit.edit_cursor);
|
||||
const cursor_x = geom.x + padding + @as(i32, @intCast(cursor_offset));
|
||||
|
||||
// Visibilidad del cursor usando función compartida de Context
|
||||
|
|
@ -109,10 +109,10 @@ pub fn drawCellEditor(
|
|||
const original_text = state.getOriginalValue();
|
||||
const kb_result = table_core.handleEditingKeyboard(
|
||||
ctx,
|
||||
&state.edit_buffer,
|
||||
&state.edit_buffer_len,
|
||||
&state.edit_cursor,
|
||||
&state.escape_count,
|
||||
&state.cell_edit.edit_buffer,
|
||||
&state.cell_edit.edit_len,
|
||||
&state.cell_edit.edit_cursor,
|
||||
&state.cell_edit.escape_count,
|
||||
if (original_text.len > 0) original_text else null,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -147,18 +147,11 @@ pub const VirtualAdvancedTableState = struct {
|
|||
footer_display_len: usize = 0,
|
||||
|
||||
// =========================================================================
|
||||
// Estado de edición CRUD Excel-style
|
||||
// Estado de edición CRUD Excel-style (usa CellEditState de table_core)
|
||||
// =========================================================================
|
||||
|
||||
/// Celda actualmente en edición (null = no editando)
|
||||
editing_cell: ?CellId = null,
|
||||
|
||||
/// Valor original de la celda (para Escape revertir)
|
||||
original_value: [256]u8 = undefined,
|
||||
original_value_len: usize = 0,
|
||||
|
||||
/// Contador de Escapes (1 = revertir celda, 2 = descartar fila)
|
||||
escape_count: u8 = 0,
|
||||
/// Estado de edición embebido (Fase 2 refactor)
|
||||
cell_edit: table_core.CellEditState = .{},
|
||||
|
||||
/// Fila actual tiene cambios sin guardar en BD
|
||||
row_dirty: bool = false,
|
||||
|
|
@ -166,11 +159,6 @@ pub const VirtualAdvancedTableState = struct {
|
|||
/// Última fila editada (para detectar cambio de fila)
|
||||
last_edited_row: ?usize = null,
|
||||
|
||||
/// Buffer de edición (texto actual en el editor)
|
||||
edit_buffer: [256]u8 = undefined,
|
||||
edit_buffer_len: usize = 0,
|
||||
edit_cursor: usize = 0,
|
||||
|
||||
/// Tiempo de última edición (para parpadeo cursor)
|
||||
last_edit_time_ms: u64 = 0,
|
||||
|
||||
|
|
@ -579,96 +567,76 @@ pub const VirtualAdvancedTableState = struct {
|
|||
}
|
||||
|
||||
// =========================================================================
|
||||
// Métodos de edición CRUD Excel-style
|
||||
// Métodos de edición CRUD Excel-style (delega a cell_edit embebido)
|
||||
// =========================================================================
|
||||
|
||||
/// Verifica si hay una celda en edición
|
||||
pub fn isEditing(self: *const Self) bool {
|
||||
return self.editing_cell != null;
|
||||
return self.cell_edit.editing;
|
||||
}
|
||||
|
||||
/// Obtiene la celda actualmente en edición
|
||||
pub fn getEditingCell(self: *const Self) ?CellId {
|
||||
if (!self.cell_edit.editing) return null;
|
||||
return .{ .row = self.cell_edit.edit_row, .col = self.cell_edit.edit_col };
|
||||
}
|
||||
|
||||
/// Inicia edición de una celda
|
||||
/// initial_char: si viene de tecla alfanumérica, el caracter inicial (null = mostrar valor actual)
|
||||
pub fn startEditing(self: *Self, cell: CellId, current_value: []const u8, initial_char: ?u8, current_time_ms: u64) void {
|
||||
// Guardar valor original (para Escape)
|
||||
const len = @min(current_value.len, self.original_value.len);
|
||||
@memcpy(self.original_value[0..len], current_value[0..len]);
|
||||
self.original_value_len = len;
|
||||
|
||||
// Inicializar buffer de edición
|
||||
if (initial_char) |c| {
|
||||
// Tecla alfanumérica: empezar con ese caracter
|
||||
self.edit_buffer[0] = c;
|
||||
self.edit_buffer_len = 1;
|
||||
self.edit_cursor = 1;
|
||||
} else {
|
||||
// Doble-click/Space: mostrar valor actual
|
||||
@memcpy(self.edit_buffer[0..len], current_value[0..len]);
|
||||
self.edit_buffer_len = len;
|
||||
self.edit_cursor = len;
|
||||
}
|
||||
|
||||
self.editing_cell = cell;
|
||||
self.escape_count = 0;
|
||||
self.cell_edit.startEditing(cell.row, cell.col, current_value, initial_char);
|
||||
self.last_edit_time_ms = current_time_ms;
|
||||
self.cell_value_changed = false;
|
||||
}
|
||||
|
||||
/// Obtiene el texto actual del editor
|
||||
pub fn getEditText(self: *const Self) []const u8 {
|
||||
return self.edit_buffer[0..self.edit_buffer_len];
|
||||
return self.cell_edit.getEditText();
|
||||
}
|
||||
|
||||
/// Establece el texto del editor
|
||||
pub fn setEditText(self: *Self, text: []const u8) void {
|
||||
const len = @min(text.len, self.edit_buffer.len);
|
||||
@memcpy(self.edit_buffer[0..len], text[0..len]);
|
||||
self.edit_buffer_len = len;
|
||||
self.edit_cursor = len;
|
||||
const len = @min(text.len, table_core.MAX_EDIT_BUFFER_SIZE);
|
||||
@memcpy(self.cell_edit.edit_buffer[0..len], text[0..len]);
|
||||
self.cell_edit.edit_len = len;
|
||||
self.cell_edit.edit_cursor = len;
|
||||
}
|
||||
|
||||
/// Obtiene el valor original (antes de editar)
|
||||
pub fn getOriginalValue(self: *const Self) []const u8 {
|
||||
return self.original_value[0..self.original_value_len];
|
||||
return self.cell_edit.getOriginalValue();
|
||||
}
|
||||
|
||||
/// Verifica si el valor ha cambiado respecto al original
|
||||
pub fn hasValueChanged(self: *const Self) bool {
|
||||
const current = self.getEditText();
|
||||
const original = self.getOriginalValue();
|
||||
return !std.mem.eql(u8, current, original);
|
||||
return self.cell_edit.hasChanged();
|
||||
}
|
||||
|
||||
/// Finaliza edición guardando cambios (retorna true si hubo cambios)
|
||||
pub fn commitEdit(self: *Self) bool {
|
||||
if (self.editing_cell == null) return false;
|
||||
if (!self.cell_edit.editing) return false;
|
||||
|
||||
const changed = self.hasValueChanged();
|
||||
const changed = self.cell_edit.hasChanged();
|
||||
if (changed) {
|
||||
self.row_dirty = true;
|
||||
self.cell_value_changed = true;
|
||||
// Solo actualizar última fila editada si hubo cambios reales
|
||||
self.last_edited_row = self.editing_cell.?.row;
|
||||
self.last_edited_row = self.cell_edit.edit_row;
|
||||
}
|
||||
|
||||
self.editing_cell = null;
|
||||
self.escape_count = 0;
|
||||
self.cell_edit.stopEditing();
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// Finaliza edición descartando cambios
|
||||
pub fn cancelEdit(self: *Self) void {
|
||||
self.editing_cell = null;
|
||||
self.escape_count = 0;
|
||||
self.cell_edit.stopEditing();
|
||||
self.cell_value_changed = false;
|
||||
}
|
||||
|
||||
/// Revierte el texto de la celda al valor original (Escape 1)
|
||||
pub fn revertCellText(self: *Self) void {
|
||||
const original = self.getOriginalValue();
|
||||
@memcpy(self.edit_buffer[0..original.len], original);
|
||||
self.edit_buffer_len = original.len;
|
||||
self.edit_cursor = original.len;
|
||||
self.cell_edit.revertToOriginal();
|
||||
}
|
||||
|
||||
/// Maneja la tecla Escape (retorna acción a tomar)
|
||||
|
|
@ -682,20 +650,15 @@ pub const VirtualAdvancedTableState = struct {
|
|||
};
|
||||
|
||||
pub fn handleEscape(self: *Self) EscapeAction {
|
||||
if (self.editing_cell == null) return .none;
|
||||
|
||||
self.escape_count += 1;
|
||||
|
||||
if (self.escape_count == 1) {
|
||||
// Escape 1: Revertir texto a valor original
|
||||
self.revertCellText();
|
||||
return .reverted;
|
||||
} else {
|
||||
// Escape 2+: Descartar cambios de fila
|
||||
self.cancelEdit();
|
||||
self.row_dirty = false;
|
||||
return .discard_row;
|
||||
}
|
||||
const action = self.cell_edit.handleEscape();
|
||||
return switch (action) {
|
||||
.reverted => .reverted,
|
||||
.cancelled => blk: {
|
||||
self.row_dirty = false;
|
||||
break :blk .discard_row;
|
||||
},
|
||||
.none => .none,
|
||||
};
|
||||
}
|
||||
|
||||
/// Verifica si cambió de fila (para auto-save)
|
||||
|
|
@ -713,12 +676,9 @@ pub const VirtualAdvancedTableState = struct {
|
|||
|
||||
/// Resetea el estado de edición completamente
|
||||
pub fn resetEditState(self: *Self) void {
|
||||
self.editing_cell = null;
|
||||
self.escape_count = 0;
|
||||
self.cell_edit.stopEditing();
|
||||
self.row_dirty = false;
|
||||
self.last_edited_row = null;
|
||||
self.edit_buffer_len = 0;
|
||||
self.edit_cursor = 0;
|
||||
self.cell_value_changed = false;
|
||||
self.row_edit_buffer.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ pub fn virtualAdvancedTableRect(
|
|||
|
||||
// Draw CellEditor overlay if editing
|
||||
if (list_state.isEditing()) {
|
||||
const editing = list_state.editing_cell.?;
|
||||
const editing = list_state.getEditingCell().?;
|
||||
|
||||
// Calculate cell geometry for the editing cell
|
||||
if (list_state.getCellGeometry(
|
||||
|
|
@ -306,7 +306,7 @@ pub fn virtualAdvancedTableRect(
|
|||
|
||||
// Handle editor results
|
||||
if (editor_result.committed) {
|
||||
const edited_cell = list_state.editing_cell.?;
|
||||
const edited_cell = list_state.getEditingCell().?;
|
||||
const new_value = list_state.getEditText();
|
||||
|
||||
// Añadir cambio al buffer de fila (NO commit inmediato)
|
||||
|
|
|
|||
Loading…
Reference in a new issue