//! 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 + inyección visual) /// Retorna el número de filas disponibles pub fn getRowCount(self: *Self) usize { const count_info = self.state.getDisplayCount(); return count_info.value; } /// Escribe el valor de una celda en el buffer proporcionado. pub fn getCellValueInto(self: *Self, row: usize, col: usize, buf: []u8) []const u8 { if (col >= self.columns.len) return ""; return self.getCellValueFromProvider(row, col, buf); } /// Helper interno: obtiene valor de celda del provider (sin ajuste por inyección) fn getCellValueFromProvider(self: *Self, row: usize, col: usize, buf: []u8) []const u8 { // Convertir índice global a índice de ventana const window_idx = self.state.globalToWindowIndex(row) orelse { // Fila fuera de ventana actual - retornar vacío 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. pub fn getRowId(self: *Self, row: usize) i64 { return self.getRowIdFromProvider(row); } /// Helper interno: obtiene ID de fila del provider (sin ajuste por inyección) fn getRowIdFromProvider(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); }