refactor(tables): FASE 4 - Add unified drawRowsWithDataSource
Add unified row rendering function to table_core.zig that uses TableDataSource interface for data access. Changes: - Add ColumnRenderDef, RowRenderColors, DrawRowsConfig types - Add drawRowsWithDataSource() unified rendering function - Update VirtualAdvancedTable.drawRows to use unified function with PagedDataSource Note: AdvancedTable not yet integrated due to additional complexity (state indicators, click handling interleaved with drawing). See PLAN_REFACTOR_TABLES_CONTINUIDAD.md for details. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
cf2f91f8bc
commit
b8199aec38
2 changed files with 239 additions and 79 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
// 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;
|
||||
|
||||
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
|
||||
// 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
|
||||
colors.row_normal;
|
||||
-1;
|
||||
|
||||
// Row background
|
||||
ctx.pushCommand(Command.rect(
|
||||
content_bounds.x,
|
||||
row_y,
|
||||
content_bounds.w,
|
||||
row_h,
|
||||
bg_color,
|
||||
));
|
||||
// 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,
|
||||
);
|
||||
|
||||
// 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;
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
|
||||
// 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),
|
||||
// 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,
|
||||
};
|
||||
table_core.drawCellActiveIndicator(
|
||||
|
||||
// Buffer para valores de celda
|
||||
var cell_buffer: [256]u8 = undefined;
|
||||
|
||||
// Llamar a la función unificada
|
||||
_ = table_core.drawRowsWithDataSource(
|
||||
ctx,
|
||||
x,
|
||||
row_y,
|
||||
col.width,
|
||||
row_h,
|
||||
bg_color,
|
||||
&tc_colors,
|
||||
list_state.has_focus,
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
|
|
|||
Loading…
Reference in a new issue