diff --git a/src/widgets/table_core.zig b/src/widgets/table_core.zig index 6b595b9..e5611bb 100644 --- a/src/widgets/table_core.zig +++ b/src/widgets/table_core.zig @@ -380,6 +380,184 @@ pub fn drawCellText( ctx.pushCommand(Command.text(text_x, text_y, text, color)); } +// ============================================================================= +// Renderizado unificado de filas (FASE 4) +// ============================================================================= + +/// Definición de columna para renderizado unificado +pub const ColumnRenderDef = struct { + /// Ancho de la columna en pixels + width: u16, + /// Alineación: 0=left, 1=center, 2=right + text_align: u2 = 0, + /// Columna visible + visible: bool = true, +}; + +/// Colores para renderizado unificado de filas +pub const RowRenderColors = struct { + row_normal: Style.Color, + row_alternate: Style.Color, + selected_row: Style.Color, + selected_row_unfocus: Style.Color, + selected_cell: Style.Color, + selected_cell_unfocus: Style.Color, + text_normal: Style.Color, + text_selected: Style.Color, + border: Style.Color, + + /// Crea RowRenderColors desde TableColors + pub fn fromTableColors(tc: *const TableColors) RowRenderColors { + return .{ + .row_normal = tc.row_normal, + .row_alternate = tc.row_alternate, + .selected_row = tc.selected_row, + .selected_row_unfocus = tc.selected_row_unfocus, + .selected_cell = tc.selected_cell, + .selected_cell_unfocus = tc.selected_cell_unfocus, + .text_normal = tc.text_normal, + .text_selected = tc.text_selected, + .border = tc.border, + }; + } +}; + +/// Configuración para drawRowsWithDataSource +pub const DrawRowsConfig = struct { + /// Bounds del área de contenido + bounds_x: i32, + bounds_y: i32, + bounds_w: u32, + /// Altura de cada fila + row_height: u32, + /// Primera fila a dibujar (índice global) + first_row: usize, + /// Última fila a dibujar (exclusivo) + last_row: usize, + /// Offset horizontal de scroll + scroll_x: i32 = 0, + /// Usar colores alternados + alternating_rows: bool = true, + /// Widget tiene focus + has_focus: bool = false, + /// Fila seleccionada (-1 = ninguna) + selected_row: i32 = -1, + /// Columna activa + active_col: usize = 0, + /// Colores + colors: RowRenderColors, + /// Columnas + columns: []const ColumnRenderDef, +}; + +/// Dibuja las filas de una tabla usando TableDataSource. +/// Esta es la función unificada que usan tanto AdvancedTable como VirtualAdvancedTable. +/// +/// Parámetros: +/// - ctx: Contexto de renderizado +/// - datasource: Fuente de datos (MemoryDataSource o PagedDataSource) +/// - config: Configuración del renderizado +/// - cell_buffer: Buffer para formatear valores de celda (debe persistir durante el frame) +/// +/// Retorna el número de filas dibujadas. +pub fn drawRowsWithDataSource( + ctx: *Context, + datasource: TableDataSource, + config: DrawRowsConfig, + cell_buffer: []u8, +) usize { + var rows_drawn: usize = 0; + var row_y = config.bounds_y; + + var row_idx = config.first_row; + while (row_idx < config.last_row) : (row_idx += 1) { + const is_selected = config.selected_row >= 0 and + @as(usize, @intCast(config.selected_row)) == row_idx; + + // Determinar color de fondo de fila + const is_alternate = config.alternating_rows and row_idx % 2 == 1; + const row_bg: Style.Color = if (is_selected) + if (config.has_focus) config.colors.selected_row else config.colors.selected_row_unfocus + else if (is_alternate) + config.colors.row_alternate + else + config.colors.row_normal; + + // Dibujar fondo de fila + ctx.pushCommand(Command.rect( + config.bounds_x, + row_y, + config.bounds_w, + config.row_height, + row_bg, + )); + + // Dibujar celdas + var col_x = config.bounds_x - config.scroll_x; + for (config.columns, 0..) |col, col_idx| { + if (!col.visible) continue; + + const col_end = col_x + @as(i32, @intCast(col.width)); + + // Solo dibujar si la columna es visible en pantalla + if (col_end > config.bounds_x and + col_x < config.bounds_x + @as(i32, @intCast(config.bounds_w))) + { + const is_active_cell = is_selected and config.active_col == col_idx; + + // Indicador de celda activa + if (is_active_cell) { + drawCellActiveIndicator( + ctx, + col_x, + row_y, + col.width, + config.row_height, + row_bg, + &TableColors{ + .selected_cell = config.colors.selected_cell, + .selected_cell_unfocus = config.colors.selected_cell_unfocus, + .border = config.colors.border, + }, + config.has_focus, + ); + } + + // Obtener texto de la celda + const cell_text = datasource.getCellValueInto(row_idx, col_idx, cell_buffer); + + // Copiar a frame allocator para persistencia durante render + const text_to_draw = ctx.frameAllocator().dupe(u8, cell_text) catch cell_text; + + // Color de texto + const text_color = if (is_selected and config.has_focus) + config.colors.text_selected + else + config.colors.text_normal; + + // Dibujar texto + drawCellText( + ctx, + col_x, + row_y, + col.width, + config.row_height, + text_to_draw, + text_color, + col.text_align, + ); + } + + col_x = col_end; + } + + row_y += @as(i32, @intCast(config.row_height)); + rows_drawn += 1; + } + + return rows_drawn; +} + /// Detecta si un click es doble-click pub fn detectDoubleClick( state: *DoubleClickState, diff --git a/src/widgets/virtual_advanced_table/virtual_advanced_table.zig b/src/widgets/virtual_advanced_table/virtual_advanced_table.zig index 8b5eb2f..15d4711 100644 --- a/src/widgets/virtual_advanced_table/virtual_advanced_table.zig +++ b/src/widgets/virtual_advanced_table/virtual_advanced_table.zig @@ -782,89 +782,71 @@ fn drawRows( ) void { _ = result; - const row_h = config.row_height; + // Crear PagedDataSource para acceso unificado a datos + var datasource = paged_datasource.PagedDataSource.init(list_state, config.columns, null); + const table_ds = datasource.toDataSource(); - // Calculate offset within the window buffer - // scroll_offset es la posición global, window_start es donde empieza el buffer - const window_offset = list_state.scroll_offset -| list_state.window_start; + // Convertir selected_id a selected_row (índice global) + const selected_row: i32 = if (list_state.findSelectedInWindow()) |window_idx| + @intCast(list_state.windowToGlobalIndex(window_idx)) + else + -1; - // Draw each visible row - var row_idx: usize = 0; - while (row_idx < visible_rows) : (row_idx += 1) { - const data_idx = window_offset + row_idx; - if (data_idx >= list_state.current_window.len) break; + // Calcular rango de filas a dibujar + const first_row = list_state.scroll_offset; + const last_row = @min( + list_state.scroll_offset + visible_rows, + list_state.window_start + list_state.current_window.len, + ); - const row_y = content_bounds.y + @as(i32, @intCast(row_idx * row_h)); - const global_idx = list_state.scroll_offset + row_idx; // Índice global real - const row = list_state.current_window[data_idx]; - - // Determine row background - const is_selected = list_state.selected_id != null and row.id == list_state.selected_id.?; - const is_alternate = global_idx % 2 == 1; - - const bg_color: Style.Color = if (is_selected) - if (list_state.has_focus) colors.row_selected else colors.row_selected_unfocus - else if (is_alternate) - colors.row_alternate - else - colors.row_normal; - - // Row background - ctx.pushCommand(Command.rect( - content_bounds.x, - row_y, - content_bounds.w, - row_h, - bg_color, - )); - - // Draw cells (with horizontal scroll offset) - var x: i32 = content_bounds.x - scroll_offset_x; - for (config.columns, 0..) |col, col_idx| { - const col_end = x + @as(i32, @intCast(col.width)); - // Only draw if column is visible - if (col_end > content_bounds.x and x < content_bounds.x + @as(i32, @intCast(content_bounds.w))) { - // Check if this is the active cell - const is_active_cell = is_selected and list_state.active_col == col_idx; - - // Draw active cell indicator BEFORE text - if (is_active_cell) { - // Usar colores contrastantes para el indicador - const tc_colors = table_core.TableColors{ - // Blanco/cyan brillante para máximo contraste - .selected_cell = Style.Color.rgb(100, 200, 255), - .selected_cell_unfocus = Style.Color.rgb(150, 150, 160), - .border = colors.border, - }; - table_core.drawCellActiveIndicator( - ctx, - x, - row_y, - col.width, - row_h, - bg_color, - &tc_colors, - list_state.has_focus, - ); - } - - if (col_idx < row.values.len) { - const text_color = if (is_selected and list_state.has_focus) - colors.text_selected - else - colors.text; - - ctx.pushCommand(Command.text( - x + 4, - row_y + 3, // Centrado vertical mejorado - row.values[col_idx], - text_color, - )); - } - } - x = col_end; - } + // Convertir columnas a ColumnRenderDef + var render_cols: [32]table_core.ColumnRenderDef = undefined; + const num_cols = @min(config.columns.len, 32); + for (config.columns[0..num_cols], 0..) |col, i| { + render_cols[i] = .{ + .width = col.width, + .text_align = 0, // TODO: mapear col.alignment si existe + .visible = true, + }; } + + // Convertir colores + const render_colors = table_core.RowRenderColors{ + .row_normal = colors.row_normal, + .row_alternate = colors.row_alternate, + .selected_row = colors.row_selected, + .selected_row_unfocus = colors.row_selected_unfocus, + .selected_cell = Style.Color.rgb(100, 200, 255), // Cyan brillante + .selected_cell_unfocus = Style.Color.rgb(150, 150, 160), + .text_normal = colors.text, + .text_selected = colors.text_selected, + .border = colors.border, + }; + + // Buffer para valores de celda + var cell_buffer: [256]u8 = undefined; + + // Llamar a la función unificada + _ = table_core.drawRowsWithDataSource( + ctx, + table_ds, + .{ + .bounds_x = content_bounds.x, + .bounds_y = content_bounds.y, + .bounds_w = content_bounds.w, + .row_height = config.row_height, + .first_row = first_row, + .last_row = last_row, + .scroll_x = scroll_offset_x, + .alternating_rows = true, + .has_focus = list_state.has_focus, + .selected_row = selected_row, + .active_col = list_state.active_col, + .colors = render_colors, + .columns = render_cols[0..num_cols], + }, + &cell_buffer, + ); } // =============================================================================