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:
parent
37e3b61aca
commit
473bbdb648
1 changed files with 112 additions and 0 deletions
|
|
@ -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
|
||||
// =============================================================================
|
||||
|
|
|
|||
Loading…
Reference in a new issue