refactor(tables): FASE 4.5 - AdvancedTable usa drawRowsWithDataSource
- Añadir RowState enum a table_core.zig - Añadir getRowState a TableDataSource.VTable (opcional) - Añadir colores de estado a RowRenderColors - Añadir draw_row_borders a DrawRowsConfig - Añadir getRowState a MemoryDataSource - Nueva función handleRowClicks() separando input de rendering - AdvancedTable usa drawRowsWithDataSource (sin bucle for propio) - Eliminar drawRow() y drawStateIndicator() locales (~160 líneas) Objetivo cumplido: un solo bloque de código para renderizar filas
This commit is contained in:
parent
b8199aec38
commit
08ffcdbac5
3 changed files with 272 additions and 179 deletions
|
|
@ -139,21 +139,62 @@ pub fn advancedTableRect(
|
|||
const first_visible = table_state.scroll_row;
|
||||
const last_visible = @min(first_visible + visible_rows, table_state.getRowCount());
|
||||
|
||||
// Draw visible rows
|
||||
for (first_visible..last_visible) |row_idx| {
|
||||
const row_y = bounds.y + @as(i32, @intCast(header_h)) +
|
||||
@as(i32, @intCast((row_idx - first_visible) * config.row_height));
|
||||
// Manejar clicks en filas (separado del renderizado)
|
||||
handleRowClicks(ctx, bounds, table_state, table_schema, header_h, state_col_w, first_visible, last_visible, &result);
|
||||
|
||||
const row_bounds = Layout.Rect.init(
|
||||
bounds.x,
|
||||
row_y,
|
||||
bounds.w,
|
||||
config.row_height,
|
||||
);
|
||||
|
||||
drawRow(ctx, row_bounds, table_state, table_schema, row_idx, state_col_w, colors, has_focus, &result);
|
||||
// Construir ColumnRenderDefs para la función unificada
|
||||
var col_defs: [64]table_core.ColumnRenderDef = undefined;
|
||||
var col_count: usize = 0;
|
||||
for (table_schema.columns) |col| {
|
||||
if (col_count >= 64) break;
|
||||
col_defs[col_count] = .{
|
||||
.width = col.width,
|
||||
.visible = col.visible,
|
||||
.text_align = 0, // Por ahora left-align
|
||||
};
|
||||
col_count += 1;
|
||||
}
|
||||
|
||||
// Crear MemoryDataSource y dibujar filas con función unificada
|
||||
var memory_ds = MemoryDataSource.init(table_state, table_schema.columns);
|
||||
const data_src = memory_ds.toDataSource();
|
||||
|
||||
// Construir RowRenderColors manualmente (los dos TableColors son tipos diferentes)
|
||||
const render_colors = table_core.RowRenderColors{
|
||||
.row_normal = colors.row_normal,
|
||||
.row_alternate = colors.row_alternate,
|
||||
.selected_row = colors.selected_row,
|
||||
.selected_row_unfocus = colors.selected_row_unfocus,
|
||||
.selected_cell = colors.selected_cell,
|
||||
.selected_cell_unfocus = Style.Color.rgb(80, 80, 90), // Default similar a table_core
|
||||
.text_normal = colors.text_normal,
|
||||
.text_selected = colors.text_selected,
|
||||
.border = colors.border,
|
||||
.state_modified = colors.state_modified,
|
||||
.state_new = colors.state_new,
|
||||
.state_deleted = colors.state_deleted,
|
||||
.state_error = colors.state_error,
|
||||
};
|
||||
|
||||
var cell_buffer: [256]u8 = undefined;
|
||||
_ = table_core.drawRowsWithDataSource(ctx, data_src, .{
|
||||
.bounds_x = bounds.x,
|
||||
.bounds_y = bounds.y + @as(i32, @intCast(header_h)),
|
||||
.bounds_w = bounds.w,
|
||||
.row_height = config.row_height,
|
||||
.first_row = first_visible,
|
||||
.last_row = last_visible,
|
||||
.has_focus = has_focus,
|
||||
.selected_row = table_state.selected_row,
|
||||
.active_col = @intCast(@max(0, table_state.selected_col)),
|
||||
.colors = render_colors,
|
||||
.columns = col_defs[0..col_count],
|
||||
.state_indicator_width = state_col_w,
|
||||
.apply_state_colors = true,
|
||||
.draw_row_borders = true,
|
||||
.alternating_rows = config.alternating_rows,
|
||||
}, &cell_buffer);
|
||||
|
||||
// End clipping
|
||||
ctx.pushCommand(Command.clipEnd());
|
||||
|
||||
|
|
@ -259,6 +300,101 @@ fn invokeCallbacks(
|
|||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Row Click Handling (separado del rendering)
|
||||
// =============================================================================
|
||||
|
||||
/// Maneja clicks en las filas de la tabla (single-click y double-click)
|
||||
/// Retorna si hubo algún cambio de selección o edición iniciada
|
||||
fn handleRowClicks(
|
||||
ctx: *Context,
|
||||
bounds: Layout.Rect,
|
||||
table_state: *AdvancedTableState,
|
||||
table_schema: *const TableSchema,
|
||||
header_h: u32,
|
||||
state_col_w: u32,
|
||||
first_visible: usize,
|
||||
last_visible: usize,
|
||||
result: *AdvancedTableResult,
|
||||
) void {
|
||||
const config = table_schema.config;
|
||||
const mouse = ctx.input.mousePos();
|
||||
|
||||
// Solo procesar si hubo click
|
||||
if (!ctx.input.mousePressed(.left)) return;
|
||||
|
||||
// Verificar si el click está en el área de filas
|
||||
const rows_area_y = bounds.y + @as(i32, @intCast(header_h));
|
||||
if (mouse.y < rows_area_y) return;
|
||||
if (mouse.x < bounds.x or mouse.x >= bounds.x + @as(i32, @intCast(bounds.w))) return;
|
||||
|
||||
// Calcular fila clickeada
|
||||
const relative_y = mouse.y - rows_area_y;
|
||||
if (relative_y < 0) return;
|
||||
const row_offset: usize = @intCast(@divFloor(relative_y, @as(i32, @intCast(config.row_height))));
|
||||
const row_idx = first_visible + row_offset;
|
||||
|
||||
if (row_idx >= last_visible or row_idx >= table_state.getRowCount()) return;
|
||||
|
||||
// Calcular columna clickeada
|
||||
var col_x = bounds.x + @as(i32, @intCast(state_col_w));
|
||||
var clicked_col: ?usize = null;
|
||||
|
||||
for (table_schema.columns, 0..) |col, col_idx| {
|
||||
if (!col.visible) continue;
|
||||
|
||||
const col_end = col_x + @as(i32, @intCast(col.width));
|
||||
if (mouse.x >= col_x and mouse.x < col_end) {
|
||||
clicked_col = col_idx;
|
||||
break;
|
||||
}
|
||||
col_x = col_end;
|
||||
}
|
||||
|
||||
if (clicked_col == null) return;
|
||||
|
||||
const col_idx = clicked_col.?;
|
||||
const is_selected_cell = table_state.selected_row == @as(i32, @intCast(row_idx)) and
|
||||
table_state.selected_col == @as(i32, @intCast(col_idx));
|
||||
|
||||
// Detectar doble-click
|
||||
const current_time = ctx.current_time_ms;
|
||||
const same_cell = table_state.last_click_row == @as(i32, @intCast(row_idx)) and
|
||||
table_state.last_click_col == @as(i32, @intCast(col_idx));
|
||||
const time_diff = current_time -| table_state.last_click_time;
|
||||
const is_double_click = same_cell and time_diff < table_state.double_click_threshold_ms;
|
||||
|
||||
if (is_double_click and config.allow_edit and col_idx < table_schema.columns.len and
|
||||
table_schema.columns[col_idx].editable and !table_state.isEditing())
|
||||
{
|
||||
// Double-click: iniciar edición
|
||||
if (table_state.getRow(row_idx)) |row| {
|
||||
const value = row.get(table_schema.columns[col_idx].name);
|
||||
var format_buf: [128]u8 = undefined;
|
||||
const edit_text = value.format(&format_buf);
|
||||
table_state.startEditing(edit_text);
|
||||
table_state.original_value = value;
|
||||
result.edit_started = true;
|
||||
}
|
||||
// Reset click tracking
|
||||
table_state.last_click_time = 0;
|
||||
table_state.last_click_row = -1;
|
||||
table_state.last_click_col = -1;
|
||||
} else {
|
||||
// Single click: seleccionar celda
|
||||
if (!is_selected_cell) {
|
||||
table_state.selectCell(row_idx, col_idx);
|
||||
result.selection_changed = true;
|
||||
result.selected_row = row_idx;
|
||||
result.selected_col = col_idx;
|
||||
}
|
||||
// Actualizar tracking para posible doble-click
|
||||
table_state.last_click_time = current_time;
|
||||
table_state.last_click_row = @intCast(row_idx);
|
||||
table_state.last_click_col = @intCast(col_idx);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Internal Rendering
|
||||
// =============================================================================
|
||||
|
|
@ -354,167 +490,6 @@ fn drawHeader(
|
|||
}
|
||||
}
|
||||
|
||||
fn drawRow(
|
||||
ctx: *Context,
|
||||
bounds: Layout.Rect,
|
||||
table_state: *AdvancedTableState,
|
||||
table_schema: *const TableSchema,
|
||||
row_idx: usize,
|
||||
state_col_w: u32,
|
||||
colors: *const TableColors,
|
||||
has_focus: bool,
|
||||
result: *AdvancedTableResult,
|
||||
) void {
|
||||
const config = table_schema.config;
|
||||
const is_selected_row = table_state.selected_row == @as(i32, @intCast(row_idx));
|
||||
const row_state = table_state.getRowState(row_idx);
|
||||
|
||||
// Determine row background color
|
||||
var row_bg = if (config.alternating_rows and row_idx % 2 == 1)
|
||||
colors.row_alternate
|
||||
else
|
||||
colors.row_normal;
|
||||
|
||||
// Apply state color overlay
|
||||
row_bg = switch (row_state) {
|
||||
.modified => blendColor(row_bg, colors.state_modified, 0.2),
|
||||
.new => blendColor(row_bg, colors.state_new, 0.2),
|
||||
.deleted => blendColor(row_bg, colors.state_deleted, 0.3),
|
||||
.@"error" => blendColor(row_bg, colors.state_error, 0.3),
|
||||
.normal => row_bg,
|
||||
};
|
||||
|
||||
// Selection overlay - SOLO la fila seleccionada cambia de color
|
||||
// El color depende de si la tabla tiene focus
|
||||
if (is_selected_row) {
|
||||
row_bg = if (has_focus) colors.selected_row else colors.selected_row_unfocus;
|
||||
}
|
||||
// Las filas NO seleccionadas mantienen row_bg (row_normal o row_alternate)
|
||||
|
||||
// Draw row background
|
||||
ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, config.row_height, row_bg));
|
||||
|
||||
var col_x = bounds.x;
|
||||
const mouse = ctx.input.mousePos();
|
||||
|
||||
// State indicator column
|
||||
if (state_col_w > 0) {
|
||||
drawStateIndicator(ctx, col_x, bounds.y, state_col_w, config.row_height, row_state, colors);
|
||||
col_x += @as(i32, @intCast(state_col_w));
|
||||
}
|
||||
|
||||
// Data cells
|
||||
for (table_schema.columns, 0..) |col, col_idx| {
|
||||
if (!col.visible) continue;
|
||||
|
||||
const cell_rect = Layout.Rect.init(col_x, bounds.y, col.width, config.row_height);
|
||||
const is_selected_cell = is_selected_row and table_state.selected_col == @as(i32, @intCast(col_idx));
|
||||
const cell_clicked = cell_rect.contains(mouse.x, mouse.y) and ctx.input.mousePressed(.left);
|
||||
|
||||
// Cell indicator for selected cell - más visible que antes
|
||||
if (is_selected_cell and has_focus) {
|
||||
// Fondo con tinte más visible (0.35 en lugar de 0.15)
|
||||
ctx.pushCommand(Command.rect(col_x, bounds.y, col.width, config.row_height, blendColor(row_bg, colors.selected_cell, 0.35)));
|
||||
// Borde doble para mayor visibilidad
|
||||
ctx.pushCommand(Command.rectOutline(col_x, bounds.y, col.width, config.row_height, colors.selected_cell));
|
||||
ctx.pushCommand(Command.rectOutline(col_x + 1, bounds.y + 1, col.width -| 2, config.row_height -| 2, colors.selected_cell));
|
||||
} else if (is_selected_cell) {
|
||||
// Sin focus: indicación más sutil
|
||||
ctx.pushCommand(Command.rect(col_x, bounds.y, col.width, config.row_height, blendColor(row_bg, colors.selected_cell, 0.15)));
|
||||
ctx.pushCommand(Command.rectOutline(col_x, bounds.y, col.width, config.row_height, colors.border));
|
||||
}
|
||||
|
||||
// Get cell value
|
||||
if (table_state.getRow(row_idx)) |row| {
|
||||
const value = row.get(col.name);
|
||||
var format_buf: [128]u8 = undefined;
|
||||
const formatted = value.format(&format_buf);
|
||||
|
||||
// Copy text to frame arena to ensure it persists until rendering
|
||||
// (format_buf is stack-allocated and goes out of scope)
|
||||
const text = ctx.frameAllocator().dupe(u8, formatted) catch formatted;
|
||||
|
||||
// Draw cell text
|
||||
const text_y = bounds.y + @as(i32, @intCast((config.row_height - 8) / 2));
|
||||
const text_color = if (is_selected_cell) colors.text_selected else colors.text_normal;
|
||||
ctx.pushCommand(Command.text(col_x + 4, text_y, text, text_color));
|
||||
}
|
||||
|
||||
// Handle cell click and double-click
|
||||
if (cell_clicked) {
|
||||
const current_time = ctx.current_time_ms;
|
||||
const same_cell = table_state.last_click_row == @as(i32, @intCast(row_idx)) and
|
||||
table_state.last_click_col == @as(i32, @intCast(col_idx));
|
||||
const time_diff = current_time -| table_state.last_click_time;
|
||||
const is_double_click = same_cell and time_diff < table_state.double_click_threshold_ms;
|
||||
|
||||
if (is_double_click and config.allow_edit and col.editable and !table_state.isEditing()) {
|
||||
// Double-click: start editing
|
||||
if (table_state.getRow(row_idx)) |row| {
|
||||
const value = row.get(col.name);
|
||||
var format_buf: [128]u8 = undefined;
|
||||
const edit_text = value.format(&format_buf);
|
||||
table_state.startEditing(edit_text);
|
||||
table_state.original_value = value;
|
||||
result.edit_started = true;
|
||||
}
|
||||
// Reset click tracking
|
||||
table_state.last_click_time = 0;
|
||||
table_state.last_click_row = -1;
|
||||
table_state.last_click_col = -1;
|
||||
} else {
|
||||
// Single click: select cell
|
||||
if (!is_selected_cell) {
|
||||
table_state.selectCell(row_idx, col_idx);
|
||||
result.selection_changed = true;
|
||||
result.selected_row = row_idx;
|
||||
result.selected_col = col_idx;
|
||||
}
|
||||
// Update click tracking for potential double-click
|
||||
table_state.last_click_time = current_time;
|
||||
table_state.last_click_row = @intCast(row_idx);
|
||||
table_state.last_click_col = @intCast(col_idx);
|
||||
}
|
||||
}
|
||||
|
||||
col_x += @as(i32, @intCast(col.width));
|
||||
}
|
||||
|
||||
// Bottom border
|
||||
ctx.pushCommand(Command.rect(
|
||||
bounds.x,
|
||||
bounds.y + @as(i32, @intCast(config.row_height)) - 1,
|
||||
bounds.w,
|
||||
1,
|
||||
colors.border,
|
||||
));
|
||||
}
|
||||
|
||||
fn drawStateIndicator(
|
||||
ctx: *Context,
|
||||
x: i32,
|
||||
y: i32,
|
||||
w: u32,
|
||||
h: u32,
|
||||
row_state: RowState,
|
||||
colors: *const TableColors,
|
||||
) void {
|
||||
const indicator_size: u32 = 8;
|
||||
const indicator_x = x + @as(i32, @intCast((w - indicator_size) / 2));
|
||||
const indicator_y = y + @as(i32, @intCast((h - indicator_size) / 2));
|
||||
|
||||
const color = switch (row_state) {
|
||||
.modified => colors.indicator_modified,
|
||||
.new => colors.indicator_new,
|
||||
.deleted => colors.indicator_deleted,
|
||||
.@"error" => colors.state_error,
|
||||
.normal => return, // No indicator
|
||||
};
|
||||
|
||||
// Draw circle indicator
|
||||
ctx.pushCommand(Command.rect(indicator_x, indicator_y, indicator_size, indicator_size, color));
|
||||
}
|
||||
|
||||
fn drawScrollbar(
|
||||
ctx: *Context,
|
||||
bounds: Layout.Rect,
|
||||
|
|
|
|||
|
|
@ -95,6 +95,20 @@ pub const MemoryDataSource = struct {
|
|||
// No hay cache que invalidar en datos en memoria
|
||||
}
|
||||
|
||||
/// Retorna el estado de una fila (normal, modified, new, deleted, error)
|
||||
pub fn getRowState(self: *Self, row: usize) table_core.RowState {
|
||||
// Delegar a AdvancedTableState que mantiene el tracking de estados
|
||||
const local_state = self.state.getRowState(row);
|
||||
// Convertir de types.RowState a table_core.RowState
|
||||
return switch (local_state) {
|
||||
.normal => .normal,
|
||||
.modified => .modified,
|
||||
.new => .new,
|
||||
.deleted => .deleted,
|
||||
.@"error" => .@"error",
|
||||
};
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Conversión a TableDataSource
|
||||
// =========================================================================
|
||||
|
|
|
|||
|
|
@ -94,6 +94,16 @@ pub const EditState = struct {
|
|||
edit_cursor: usize = 0,
|
||||
};
|
||||
|
||||
/// Estado de una fila (para indicadores visuales)
|
||||
/// Compatible con advanced_table.types.RowState
|
||||
pub const RowState = enum {
|
||||
normal, // Sin cambios
|
||||
modified, // Editada, pendiente de guardar
|
||||
new, // Fila nueva, no existe en BD
|
||||
deleted, // Marcada para eliminar
|
||||
@"error", // Error de validación
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Estados embebibles (para composición en AdvancedTableState/VirtualAdvancedTableState)
|
||||
// =============================================================================
|
||||
|
|
@ -380,6 +390,36 @@ pub fn drawCellText(
|
|||
ctx.pushCommand(Command.text(text_x, text_y, text, color));
|
||||
}
|
||||
|
||||
/// Dibuja el indicador de estado de fila (círculo/cuadrado pequeño)
|
||||
/// Llamado desde drawRowsWithDataSource cuando state_indicator_width > 0
|
||||
pub fn drawStateIndicator(
|
||||
ctx: *Context,
|
||||
x: i32,
|
||||
y: i32,
|
||||
w: u32,
|
||||
h: u32,
|
||||
row_state: RowState,
|
||||
colors: *const RowRenderColors,
|
||||
) void {
|
||||
// No dibujar nada para estado normal
|
||||
if (row_state == .normal) return;
|
||||
|
||||
const indicator_size: u32 = 8;
|
||||
const indicator_x = x + @as(i32, @intCast((w -| indicator_size) / 2));
|
||||
const indicator_y = y + @as(i32, @intCast((h -| indicator_size) / 2));
|
||||
|
||||
const color = switch (row_state) {
|
||||
.modified => colors.state_modified,
|
||||
.new => colors.state_new,
|
||||
.deleted => colors.state_deleted,
|
||||
.@"error" => colors.state_error,
|
||||
.normal => unreachable, // Ya verificado arriba
|
||||
};
|
||||
|
||||
// Dibujar cuadrado indicador
|
||||
ctx.pushCommand(Command.rect(indicator_x, indicator_y, indicator_size, indicator_size, color));
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Renderizado unificado de filas (FASE 4)
|
||||
// =============================================================================
|
||||
|
|
@ -387,7 +427,7 @@ pub fn drawCellText(
|
|||
/// Definición de columna para renderizado unificado
|
||||
pub const ColumnRenderDef = struct {
|
||||
/// Ancho de la columna en pixels
|
||||
width: u16,
|
||||
width: u32,
|
||||
/// Alineación: 0=left, 1=center, 2=right
|
||||
text_align: u2 = 0,
|
||||
/// Columna visible
|
||||
|
|
@ -396,6 +436,7 @@ pub const ColumnRenderDef = struct {
|
|||
|
||||
/// Colores para renderizado unificado de filas
|
||||
pub const RowRenderColors = struct {
|
||||
// Colores base de fila
|
||||
row_normal: Style.Color,
|
||||
row_alternate: Style.Color,
|
||||
selected_row: Style.Color,
|
||||
|
|
@ -406,6 +447,12 @@ pub const RowRenderColors = struct {
|
|||
text_selected: Style.Color,
|
||||
border: Style.Color,
|
||||
|
||||
// Colores de estado (para blending)
|
||||
state_modified: Style.Color = Style.Color.rgb(255, 200, 100), // Naranja
|
||||
state_new: Style.Color = Style.Color.rgb(100, 200, 100), // Verde
|
||||
state_deleted: Style.Color = Style.Color.rgb(255, 100, 100), // Rojo
|
||||
state_error: Style.Color = Style.Color.rgb(255, 50, 50), // Rojo intenso
|
||||
|
||||
/// Crea RowRenderColors desde TableColors
|
||||
pub fn fromTableColors(tc: *const TableColors) RowRenderColors {
|
||||
return .{
|
||||
|
|
@ -448,6 +495,12 @@ pub const DrawRowsConfig = struct {
|
|||
colors: RowRenderColors,
|
||||
/// Columnas
|
||||
columns: []const ColumnRenderDef,
|
||||
/// Ancho de columna de indicadores de estado (0 = deshabilitada)
|
||||
state_indicator_width: u32 = 0,
|
||||
/// Aplicar blending de color según estado de fila
|
||||
apply_state_colors: bool = false,
|
||||
/// Dibujar borde inferior en cada fila
|
||||
draw_row_borders: bool = false,
|
||||
};
|
||||
|
||||
/// Dibuja las filas de una tabla usando TableDataSource.
|
||||
|
|
@ -474,15 +527,32 @@ pub fn drawRowsWithDataSource(
|
|||
const is_selected = config.selected_row >= 0 and
|
||||
@as(usize, @intCast(config.selected_row)) == row_idx;
|
||||
|
||||
// Determinar color de fondo de fila
|
||||
// Obtener estado de la fila
|
||||
const row_state = datasource.getRowState(row_idx);
|
||||
|
||||
// Determinar color de fondo base
|
||||
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)
|
||||
var row_bg: Style.Color = if (is_alternate)
|
||||
config.colors.row_alternate
|
||||
else
|
||||
config.colors.row_normal;
|
||||
|
||||
// Aplicar blending de color según estado (si está habilitado)
|
||||
if (config.apply_state_colors) {
|
||||
row_bg = switch (row_state) {
|
||||
.modified => blendColor(row_bg, config.colors.state_modified, 0.2),
|
||||
.new => blendColor(row_bg, config.colors.state_new, 0.2),
|
||||
.deleted => blendColor(row_bg, config.colors.state_deleted, 0.3),
|
||||
.@"error" => blendColor(row_bg, config.colors.state_error, 0.3),
|
||||
.normal => row_bg,
|
||||
};
|
||||
}
|
||||
|
||||
// Aplicar selección (override del estado)
|
||||
if (is_selected) {
|
||||
row_bg = if (config.has_focus) config.colors.selected_row else config.colors.selected_row_unfocus;
|
||||
}
|
||||
|
||||
// Dibujar fondo de fila
|
||||
ctx.pushCommand(Command.rect(
|
||||
config.bounds_x,
|
||||
|
|
@ -492,8 +562,16 @@ pub fn drawRowsWithDataSource(
|
|||
row_bg,
|
||||
));
|
||||
|
||||
// Dibujar celdas
|
||||
// Posición X inicial (después de state indicator si existe)
|
||||
var col_x = config.bounds_x - config.scroll_x;
|
||||
|
||||
// Dibujar columna de indicador de estado (si está habilitada)
|
||||
if (config.state_indicator_width > 0) {
|
||||
drawStateIndicator(ctx, config.bounds_x, row_y, config.state_indicator_width, config.row_height, row_state, &config.colors);
|
||||
col_x += @as(i32, @intCast(config.state_indicator_width));
|
||||
}
|
||||
|
||||
// Dibujar celdas de datos
|
||||
for (config.columns, 0..) |col, col_idx| {
|
||||
if (!col.visible) continue;
|
||||
|
||||
|
|
@ -551,6 +629,17 @@ pub fn drawRowsWithDataSource(
|
|||
col_x = col_end;
|
||||
}
|
||||
|
||||
// Dibujar borde inferior de fila (si está habilitado)
|
||||
if (config.draw_row_borders) {
|
||||
ctx.pushCommand(Command.rect(
|
||||
config.bounds_x,
|
||||
row_y + @as(i32, @intCast(config.row_height)) - 1,
|
||||
config.bounds_w,
|
||||
1,
|
||||
config.colors.border,
|
||||
));
|
||||
}
|
||||
|
||||
row_y += @as(i32, @intCast(config.row_height));
|
||||
rows_drawn += 1;
|
||||
}
|
||||
|
|
@ -1159,6 +1248,10 @@ pub const TableDataSource = struct {
|
|||
/// Verifica si una celda es editable (opcional, default true)
|
||||
isCellEditable: ?*const fn (ptr: *anyopaque, row: usize, col: usize) bool = null,
|
||||
|
||||
/// Retorna el estado de una fila (opcional, default .normal)
|
||||
/// Usado para colores de estado (modified, new, deleted, error)
|
||||
getRowState: ?*const fn (ptr: *anyopaque, row: usize) RowState = null,
|
||||
|
||||
/// Invalida cache interno (para refresh)
|
||||
invalidate: ?*const fn (ptr: *anyopaque) void = null,
|
||||
};
|
||||
|
|
@ -1197,6 +1290,14 @@ pub const TableDataSource = struct {
|
|||
}
|
||||
}
|
||||
|
||||
/// Obtiene el estado de una fila
|
||||
pub fn getRowState(self: TableDataSource, row: usize) RowState {
|
||||
if (self.vtable.getRowState) |func| {
|
||||
return func(self.ptr, row);
|
||||
}
|
||||
return .normal; // Default: estado normal
|
||||
}
|
||||
|
||||
/// Verifica si la fila es la ghost row (nueva)
|
||||
pub fn isGhostRow(self: TableDataSource, row: usize) bool {
|
||||
return self.getRowId(row) == NEW_ROW_ID;
|
||||
|
|
@ -1216,6 +1317,9 @@ pub fn makeTableDataSource(comptime T: type, impl: *T) TableDataSource {
|
|||
if (@hasDecl(T, "isCellEditable")) {
|
||||
vt.isCellEditable = @ptrCast(&T.isCellEditable);
|
||||
}
|
||||
if (@hasDecl(T, "getRowState")) {
|
||||
vt.getRowState = @ptrCast(&T.getRowState);
|
||||
}
|
||||
if (@hasDecl(T, "invalidate")) {
|
||||
vt.invalidate = @ptrCast(&T.invalidate);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue