feat(table_core): Add TableDataSource interface (FASE 3)

- TableDataSource: vtable interface for data abstraction
- getCellValueInto: buffer-based pattern to avoid memory issues
- getRowCount, getRowId: basic data access methods
- isCellEditable, invalidate: optional methods
- makeTableDataSource: helper to create from concrete types
- isGhostRow: convenience method for new row detection

This interface enables unified rendering for both memory and
paginated tables while enforcing safe memory patterns.
This commit is contained in:
reugenio 2025-12-27 17:13:27 +01:00
parent 37e3b61aca
commit 473bbdb648

View file

@ -938,6 +938,118 @@ pub fn toggleSort(
};
}
// =============================================================================
// TableDataSource Interface (FASE 3)
// =============================================================================
//
// ## Interfaz TableDataSource
//
// Abstrae el origen de datos para tablas, permitiendo que el mismo widget
// renderice datos desde memoria (AdvancedTable) o desde BD paginada (VirtualAdvancedTable).
//
// ### Protocolo de Memoria
//
// `getCellValueInto` escribe directamente en el buffer proporcionado por el widget.
// Esto elimina problemas de ownership: el widget controla la vida del buffer.
//
// ### Ejemplo de uso:
// ```zig
// var buf: [256]u8 = undefined;
// const value = data_source.getCellValueInto(row, col, &buf);
// // value es un slice de buf, válido mientras buf exista
// ```
/// Interfaz genérica para proveer datos a tablas
/// Usa vtable pattern para polimorfismo en runtime
pub const TableDataSource = struct {
ptr: *anyopaque,
vtable: *const VTable,
pub const VTable = struct {
/// Retorna el número total de filas en el datasource
getRowCount: *const fn (ptr: *anyopaque) usize,
/// Escribe el valor de una celda en el buffer proporcionado
/// Retorna el slice del buffer con el contenido escrito
/// Si la celda no existe o está vacía, retorna ""
getCellValueInto: *const fn (ptr: *anyopaque, row: usize, col: usize, buf: []u8) []const u8,
/// Retorna el ID único de una fila (para selección persistente)
/// NEW_ROW_ID (-1) indica fila nueva no guardada
getRowId: *const fn (ptr: *anyopaque, row: usize) i64,
/// Verifica si una celda es editable (opcional, default true)
isCellEditable: ?*const fn (ptr: *anyopaque, row: usize, col: usize) bool = null,
/// Invalida cache interno (para refresh)
invalidate: ?*const fn (ptr: *anyopaque) void = null,
};
// =========================================================================
// Métodos de conveniencia
// =========================================================================
/// Obtiene el número de filas
pub fn getRowCount(self: TableDataSource) usize {
return self.vtable.getRowCount(self.ptr);
}
/// Escribe valor de celda en buffer
pub fn getCellValueInto(self: TableDataSource, row: usize, col: usize, buf: []u8) []const u8 {
return self.vtable.getCellValueInto(self.ptr, row, col, buf);
}
/// Obtiene ID de fila
pub fn getRowId(self: TableDataSource, row: usize) i64 {
return self.vtable.getRowId(self.ptr, row);
}
/// Verifica si celda es editable
pub fn isCellEditable(self: TableDataSource, row: usize, col: usize) bool {
if (self.vtable.isCellEditable) |func| {
return func(self.ptr, row, col);
}
return true; // Default: todas editables
}
/// Invalida cache
pub fn invalidate(self: TableDataSource) void {
if (self.vtable.invalidate) |func| {
func(self.ptr);
}
}
/// Verifica si la fila es la ghost row (nueva)
pub fn isGhostRow(self: TableDataSource, row: usize) bool {
return self.getRowId(row) == NEW_ROW_ID;
}
};
/// Helper para crear TableDataSource desde un tipo concreto
/// El tipo T debe tener los métodos: getRowCount, getCellValueInto, getRowId
pub fn makeTableDataSource(comptime T: type, impl: *T) TableDataSource {
const vtable = comptime blk: {
var vt: TableDataSource.VTable = .{
.getRowCount = @ptrCast(&T.getRowCount),
.getCellValueInto = @ptrCast(&T.getCellValueInto),
.getRowId = @ptrCast(&T.getRowId),
};
// Métodos opcionales
if (@hasDecl(T, "isCellEditable")) {
vt.isCellEditable = @ptrCast(&T.isCellEditable);
}
if (@hasDecl(T, "invalidate")) {
vt.invalidate = @ptrCast(&T.invalidate);
}
break :blk vt;
};
return .{
.ptr = impl,
.vtable = &vtable,
};
}
// =============================================================================
// Tests
// =============================================================================