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
|
// Tests
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue