refactor(tables): FASE 4 - Add DataSource adapters
Add MemoryDataSource for AdvancedTable (in-memory ArrayList) and PagedDataSource for VirtualAdvancedTable (paged DB data). Both implement TableDataSource interface from table_core.zig, enabling unified rendering patterns while respecting memory ownership. New files: - advanced_table/datasource.zig - MemoryDataSource - virtual_advanced_table/paged_datasource.zig - PagedDataSource 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
473bbdb648
commit
cf2f91f8bc
4 changed files with 316 additions and 0 deletions
|
|
@ -47,6 +47,10 @@ pub const state = @import("state.zig");
|
||||||
pub const AdvancedTableState = state.AdvancedTableState;
|
pub const AdvancedTableState = state.AdvancedTableState;
|
||||||
pub const AdvancedTableResult = state.AdvancedTableResult;
|
pub const AdvancedTableResult = state.AdvancedTableResult;
|
||||||
|
|
||||||
|
// Re-export datasource
|
||||||
|
pub const datasource = @import("datasource.zig");
|
||||||
|
pub const MemoryDataSource = datasource.MemoryDataSource;
|
||||||
|
|
||||||
// Re-export table_core types
|
// Re-export table_core types
|
||||||
pub const NavigateDirection = table_core.NavigateDirection;
|
pub const NavigateDirection = table_core.NavigateDirection;
|
||||||
|
|
||||||
|
|
|
||||||
126
src/widgets/advanced_table/datasource.zig
Normal file
126
src/widgets/advanced_table/datasource.zig
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
//! MemoryDataSource - Adaptador de TableDataSource para datos en memoria
|
||||||
|
//!
|
||||||
|
//! Wrappea AdvancedTableState + columnas para implementar la interfaz
|
||||||
|
//! TableDataSource de table_core. Permite que AdvancedTable use el mismo
|
||||||
|
//! patrón de renderizado que VirtualAdvancedTable.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const table_core = @import("../table_core.zig");
|
||||||
|
const state_mod = @import("state.zig");
|
||||||
|
const schema_mod = @import("schema.zig");
|
||||||
|
const types = @import("types.zig");
|
||||||
|
|
||||||
|
const AdvancedTableState = state_mod.AdvancedTableState;
|
||||||
|
const ColumnDef = schema_mod.ColumnDef;
|
||||||
|
const CellValue = types.CellValue;
|
||||||
|
const Row = types.Row;
|
||||||
|
const TableDataSource = table_core.TableDataSource;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// MemoryDataSource
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Adaptador que implementa TableDataSource para datos en memoria (ArrayList)
|
||||||
|
/// Usa el patrón getCellValueInto para escritura segura en buffer proporcionado
|
||||||
|
pub const MemoryDataSource = struct {
|
||||||
|
/// Referencia al estado de la tabla
|
||||||
|
state: *AdvancedTableState,
|
||||||
|
|
||||||
|
/// Definiciones de columnas (para mapear índice -> nombre)
|
||||||
|
columns: []const ColumnDef,
|
||||||
|
|
||||||
|
/// Buffer interno para formateo (evita allocaciones)
|
||||||
|
format_buffer: [256]u8 = undefined,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
/// Crea un nuevo MemoryDataSource
|
||||||
|
pub fn init(state: *AdvancedTableState, columns: []const ColumnDef) Self {
|
||||||
|
return .{
|
||||||
|
.state = state,
|
||||||
|
.columns = columns,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Implementación de TableDataSource
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// Retorna el número total de filas
|
||||||
|
pub fn getRowCount(self: *Self) usize {
|
||||||
|
return self.state.getRowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Escribe el valor de una celda en el buffer proporcionado
|
||||||
|
/// Retorna slice del buffer con el contenido
|
||||||
|
pub fn getCellValueInto(self: *Self, row: usize, col: usize, buf: []u8) []const u8 {
|
||||||
|
// Validar índices
|
||||||
|
if (col >= self.columns.len) return "";
|
||||||
|
|
||||||
|
// Obtener fila
|
||||||
|
const row_data = self.state.getRowConst(row) orelse return "";
|
||||||
|
|
||||||
|
// Obtener nombre de columna
|
||||||
|
const col_name = self.columns[col].name;
|
||||||
|
|
||||||
|
// Obtener valor de celda
|
||||||
|
const cell_value = row_data.get(col_name);
|
||||||
|
|
||||||
|
// Formatear en el buffer proporcionado
|
||||||
|
return cell_value.format(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retorna el ID único de una fila
|
||||||
|
/// Para datos en memoria, usamos el índice como ID
|
||||||
|
/// TODO: Si Row tuviera campo 'id', lo usaríamos
|
||||||
|
pub fn getRowId(self: *Self, row: usize) i64 {
|
||||||
|
_ = self;
|
||||||
|
// Por ahora usamos el índice como ID
|
||||||
|
// En el futuro, Row podría tener un campo 'id' explícito
|
||||||
|
return @intCast(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifica si una celda es editable
|
||||||
|
pub fn isCellEditable(self: *Self, row: usize, col: usize) bool {
|
||||||
|
_ = row;
|
||||||
|
|
||||||
|
// Verificar configuración de columna
|
||||||
|
if (col >= self.columns.len) return false;
|
||||||
|
return self.columns[col].editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invalida cache (no aplica para datos en memoria)
|
||||||
|
pub fn invalidate(self: *Self) void {
|
||||||
|
_ = self;
|
||||||
|
// No hay cache que invalidar en datos en memoria
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Conversión a TableDataSource
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// Crea TableDataSource vtable para este adaptador
|
||||||
|
pub fn toDataSource(self: *Self) TableDataSource {
|
||||||
|
return table_core.makeTableDataSource(Self, self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Tests
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
test "MemoryDataSource basic" {
|
||||||
|
// Test básico de que compila y los tipos son correctos
|
||||||
|
const columns = [_]ColumnDef{
|
||||||
|
.{ .name = "id", .title = "ID", .width = 50 },
|
||||||
|
.{ .name = "name", .title = "Name", .width = 200 },
|
||||||
|
};
|
||||||
|
|
||||||
|
var state = AdvancedTableState.init(std.testing.allocator);
|
||||||
|
defer state.deinit();
|
||||||
|
|
||||||
|
var ds = MemoryDataSource.init(&state, &columns);
|
||||||
|
const tds = ds.toDataSource();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(usize, 0), tds.getRowCount());
|
||||||
|
}
|
||||||
184
src/widgets/virtual_advanced_table/paged_datasource.zig
Normal file
184
src/widgets/virtual_advanced_table/paged_datasource.zig
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
//! PagedDataSource - Adaptador de TableDataSource para datos paginados
|
||||||
|
//!
|
||||||
|
//! Wrappea VirtualAdvancedTableState + DataProvider para implementar la interfaz
|
||||||
|
//! TableDataSource de table_core. Permite unificar el pattern de renderizado
|
||||||
|
//! entre AdvancedTable (memoria) y VirtualAdvancedTable (paginado).
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const table_core = @import("../table_core.zig");
|
||||||
|
const state_mod = @import("state.zig");
|
||||||
|
const types = @import("types.zig");
|
||||||
|
const data_provider_mod = @import("data_provider.zig");
|
||||||
|
|
||||||
|
const VirtualAdvancedTableState = state_mod.VirtualAdvancedTableState;
|
||||||
|
const ColumnDef = types.ColumnDef;
|
||||||
|
const RowData = types.RowData;
|
||||||
|
const DataProvider = data_provider_mod.DataProvider;
|
||||||
|
const TableDataSource = table_core.TableDataSource;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// PagedDataSource
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Adaptador que implementa TableDataSource para datos paginados (ventana virtual).
|
||||||
|
/// Lee valores desde current_window en el state, que es propiedad del DataProvider.
|
||||||
|
pub const PagedDataSource = struct {
|
||||||
|
/// Referencia al estado de la tabla (contiene current_window)
|
||||||
|
state: *VirtualAdvancedTableState,
|
||||||
|
|
||||||
|
/// DataProvider subyacente (para getRowId cuando fuera de ventana)
|
||||||
|
provider: ?DataProvider,
|
||||||
|
|
||||||
|
/// Definiciones de columnas (para validar índices)
|
||||||
|
columns: []const ColumnDef,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
/// Crea un nuevo PagedDataSource
|
||||||
|
pub fn init(
|
||||||
|
state: *VirtualAdvancedTableState,
|
||||||
|
columns: []const ColumnDef,
|
||||||
|
provider: ?DataProvider,
|
||||||
|
) Self {
|
||||||
|
return .{
|
||||||
|
.state = state,
|
||||||
|
.columns = columns,
|
||||||
|
.provider = provider,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Implementación de TableDataSource
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// Retorna el número total de filas (filtered count)
|
||||||
|
pub fn getRowCount(self: *Self) usize {
|
||||||
|
// Usar conteo filtrado si está disponible
|
||||||
|
const count_info = self.state.getDisplayCount();
|
||||||
|
return count_info.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Escribe el valor de una celda en el buffer proporcionado.
|
||||||
|
/// El row es índice global, se convierte a índice de ventana.
|
||||||
|
/// Retorna slice del buffer con el contenido.
|
||||||
|
pub fn getCellValueInto(self: *Self, row: usize, col: usize, buf: []u8) []const u8 {
|
||||||
|
// Validar columna
|
||||||
|
if (col >= self.columns.len) return "";
|
||||||
|
|
||||||
|
// Convertir índice global a índice de ventana
|
||||||
|
const window_idx = self.state.globalToWindowIndex(row) orelse {
|
||||||
|
// Fila fuera de ventana actual - retornar vacío
|
||||||
|
// (el caller debería asegurarse de pedir solo filas visibles)
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Obtener fila de la ventana
|
||||||
|
if (window_idx >= self.state.current_window.len) return "";
|
||||||
|
const row_data = self.state.current_window[window_idx];
|
||||||
|
|
||||||
|
// Obtener valor de la columna
|
||||||
|
if (col >= row_data.values.len) return "";
|
||||||
|
const value = row_data.values[col];
|
||||||
|
|
||||||
|
// Copiar al buffer proporcionado
|
||||||
|
const copy_len = @min(value.len, buf.len);
|
||||||
|
@memcpy(buf[0..copy_len], value[0..copy_len]);
|
||||||
|
return buf[0..copy_len];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retorna el ID único de una fila.
|
||||||
|
/// Si está en ventana, usa window data. Si no, consulta al provider.
|
||||||
|
pub fn getRowId(self: *Self, row: usize) i64 {
|
||||||
|
// Intentar obtener de la ventana
|
||||||
|
if (self.state.globalToWindowIndex(row)) |window_idx| {
|
||||||
|
if (window_idx < self.state.current_window.len) {
|
||||||
|
return self.state.current_window[window_idx].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback al provider si está disponible
|
||||||
|
if (self.provider) |provider| {
|
||||||
|
return provider.getRowId(row) orelse -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si no hay provider, usar índice como fallback
|
||||||
|
return @intCast(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifica si una celda es editable.
|
||||||
|
/// TODO: Podría usar metadata de columnas cuando esté disponible.
|
||||||
|
pub fn isCellEditable(self: *Self, row: usize, col: usize) bool {
|
||||||
|
_ = row;
|
||||||
|
// Por ahora, todas las columnas son no editables por defecto
|
||||||
|
// La edición se maneja a nivel de VirtualAdvancedTable
|
||||||
|
if (col >= self.columns.len) return false;
|
||||||
|
return false; // VirtualAdvancedTable maneja edición de forma específica
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invalida cache.
|
||||||
|
/// Delega al state para forzar refetch.
|
||||||
|
pub fn invalidate(self: *Self) void {
|
||||||
|
self.state.invalidateWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Conversión a TableDataSource
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// Crea TableDataSource vtable para este adaptador
|
||||||
|
pub fn toDataSource(self: *Self) TableDataSource {
|
||||||
|
return table_core.makeTableDataSource(Self, self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Tests
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
test "PagedDataSource basic" {
|
||||||
|
// Test básico de que compila y los tipos son correctos
|
||||||
|
const columns = [_]ColumnDef{
|
||||||
|
.{ .name = "id", .title = "ID", .width = 50 },
|
||||||
|
.{ .name = "name", .title = "Name", .width = 200 },
|
||||||
|
};
|
||||||
|
|
||||||
|
var state = VirtualAdvancedTableState{};
|
||||||
|
var ds = PagedDataSource.init(&state, &columns, null);
|
||||||
|
const tds = ds.toDataSource();
|
||||||
|
|
||||||
|
// Sin datos, row count es 0
|
||||||
|
try std.testing.expectEqual(@as(usize, 0), tds.getRowCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "PagedDataSource getCellValueInto" {
|
||||||
|
const columns = [_]ColumnDef{
|
||||||
|
.{ .name = "id", .title = "ID", .width = 50 },
|
||||||
|
.{ .name = "name", .title = "Name", .width = 200 },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Crear datos de prueba
|
||||||
|
const values1 = [_][]const u8{ "1", "Alice" };
|
||||||
|
const values2 = [_][]const u8{ "2", "Bob" };
|
||||||
|
const rows = [_]RowData{
|
||||||
|
.{ .id = 100, .values = &values1 },
|
||||||
|
.{ .id = 101, .values = &values2 },
|
||||||
|
};
|
||||||
|
|
||||||
|
var state = VirtualAdvancedTableState{};
|
||||||
|
state.current_window = &rows;
|
||||||
|
state.window_start = 10; // Ventana empieza en índice global 10
|
||||||
|
state.filtered_count = .{ .value = 100, .state = .ready };
|
||||||
|
|
||||||
|
var ds = PagedDataSource.init(&state, &columns, null);
|
||||||
|
|
||||||
|
// Buffer para valores
|
||||||
|
var buf: [256]u8 = undefined;
|
||||||
|
|
||||||
|
// Fila en ventana (global 10 = window 0)
|
||||||
|
const val1 = ds.getCellValueInto(10, 1, &buf);
|
||||||
|
try std.testing.expectEqualStrings("Alice", val1);
|
||||||
|
|
||||||
|
// Fila fuera de ventana
|
||||||
|
const val2 = ds.getCellValueInto(0, 0, &buf);
|
||||||
|
try std.testing.expectEqualStrings("", val2);
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ pub const types = @import("types.zig");
|
||||||
pub const data_provider = @import("data_provider.zig");
|
pub const data_provider = @import("data_provider.zig");
|
||||||
pub const state_mod = @import("state.zig");
|
pub const state_mod = @import("state.zig");
|
||||||
pub const cell_editor = @import("cell_editor.zig");
|
pub const cell_editor = @import("cell_editor.zig");
|
||||||
|
pub const paged_datasource = @import("paged_datasource.zig");
|
||||||
|
|
||||||
// Tipos principales
|
// Tipos principales
|
||||||
pub const RowData = types.RowData;
|
pub const RowData = types.RowData;
|
||||||
|
|
@ -41,6 +42,7 @@ pub const CellId = types.CellId;
|
||||||
pub const CellGeometry = types.CellGeometry;
|
pub const CellGeometry = types.CellGeometry;
|
||||||
|
|
||||||
pub const DataProvider = data_provider.DataProvider;
|
pub const DataProvider = data_provider.DataProvider;
|
||||||
|
pub const PagedDataSource = paged_datasource.PagedDataSource;
|
||||||
pub const CellEditorColors = cell_editor.CellEditorColors;
|
pub const CellEditorColors = cell_editor.CellEditorColors;
|
||||||
pub const CellEditorResult = cell_editor.CellEditorResult;
|
pub const CellEditorResult = cell_editor.CellEditorResult;
|
||||||
pub const NavigateDirection = cell_editor.NavigateDirection;
|
pub const NavigateDirection = cell_editor.NavigateDirection;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue