zcatgui/src/widgets/virtual_advanced_table/drawing.zig
R.Eugenio 0e913cda55 style(chips): Reducir radio chips para consistencia con botones
Z-Design V2: Chips de filtro con corner_radius: 4 (igual que botones)
en lugar de 11 (pill-like).

Esto hace que chips y botones tengan el mismo lenguaje visual.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 18:25:47 +01:00

495 lines
16 KiB
Zig

//! VirtualAdvancedTable - Funciones de Dibujo
//!
//! Funciones de renderizado extraídas del archivo principal para mejorar
//! modularidad y reducir el tamaño del archivo principal.
const std = @import("std");
const Context = @import("../../core/context.zig").Context;
const Command = @import("../../core/command.zig");
const Layout = @import("../../core/layout.zig");
const Style = @import("../../core/style.zig");
const text_input = @import("../text_input.zig");
const table_core = @import("../table_core/table_core.zig");
const types = @import("types.zig");
const state_mod = @import("state.zig");
const paged_datasource = @import("paged_datasource.zig");
pub const VirtualAdvancedTableState = state_mod.VirtualAdvancedTableState;
pub const VirtualAdvancedTableConfig = types.VirtualAdvancedTableConfig;
pub const FilterBarConfig = types.FilterBarConfig;
pub const VirtualAdvancedTableResult = @import("virtual_advanced_table.zig").VirtualAdvancedTableResult;
// =============================================================================
// Draw: FilterBar
// =============================================================================
pub fn drawFilterBar(
ctx: *Context,
bounds: Layout.Rect,
config: FilterBarConfig,
colors: *const VirtualAdvancedTableConfig.Colors,
list_state: *VirtualAdvancedTableState,
result: *VirtualAdvancedTableResult,
) void {
const padding: i32 = 6;
const chip_h: u32 = 22;
const chip_padding: i32 = 10;
const chip_spacing: i32 = 6;
const chip_radius: u8 = 4; // Z-Design V2: consistente con botones
const clear_btn_w: u32 = 22;
// Background
ctx.pushCommand(Command.rect(
bounds.x,
bounds.y,
bounds.w,
bounds.h,
colors.header_background,
));
// Línea inferior
ctx.pushCommand(Command.rect(
bounds.x,
bounds.y + @as(i32, @intCast(bounds.h)) - 1,
bounds.w,
1,
colors.border,
));
var current_x = bounds.x + padding;
const item_y = bounds.y + @divTrunc(@as(i32, @intCast(bounds.h)) - @as(i32, @intCast(chip_h)), 2);
const item_h = bounds.h -| @as(u32, @intCast(padding * 2));
const mouse = ctx.input.mousePos();
// Draw Chips
if (config.chips.len > 0) {
for (config.chips, 0..) |chip, idx| {
const chip_idx: u4 = @intCast(idx);
const is_active = list_state.isChipActive(chip_idx);
const label_len = chip.label.len;
const chip_w: u32 = @intCast(label_len * 7 + chip_padding * 2);
const chip_bounds = Layout.Rect.init(
current_x,
item_y,
chip_w,
chip_h,
);
const chip_hovered = chip_bounds.contains(mouse.x, mouse.y);
const chip_bg = if (is_active)
colors.row_selected
else if (chip_hovered)
Style.Color.rgb(
colors.header_background.r -| 15,
colors.header_background.g -| 15,
colors.header_background.b -| 15,
)
else
colors.header_background;
const chip_text_color = if (is_active)
colors.text_selected
else
colors.text;
const chip_border = if (is_active)
colors.row_selected
else
colors.border;
ctx.pushCommand(Command.roundedRect(
chip_bounds.x,
chip_bounds.y,
chip_bounds.w,
chip_bounds.h,
chip_bg,
chip_radius,
));
if (!is_active) {
ctx.pushCommand(Command.roundedRectOutline(
chip_bounds.x,
chip_bounds.y,
chip_bounds.w,
chip_bounds.h,
chip_border,
chip_radius,
));
}
ctx.pushCommand(Command.text(
chip_bounds.x + chip_padding,
chip_bounds.y + 4,
chip.label,
chip_text_color,
));
if (chip_hovered and ctx.input.mousePressed(.left)) {
list_state.activateChip(chip_idx, config.chip_mode);
result.chip_changed = true;
result.chip_index = chip_idx;
result.chip_active = list_state.isChipActive(chip_idx);
}
current_x += @as(i32, @intCast(chip_w)) + chip_spacing;
}
current_x += padding;
}
// Draw Search Input
if (config.show_search) {
const clear_space: u32 = if (config.show_clear_button) clear_btn_w + @as(u32, @intCast(padding)) else 0;
const search_end = bounds.x + @as(i32, @intCast(bounds.w)) - padding - @as(i32, @intCast(clear_space));
const search_w: u32 = @intCast(@max(60, search_end - current_x));
const search_bounds = Layout.Rect.init(
current_x,
item_y,
search_w,
item_h,
);
var text_state = text_input.TextInputState{
.buffer = &list_state.filter_buf,
.len = list_state.filter_len,
.cursor = list_state.search_cursor,
.selection_start = list_state.search_selection_start,
.focused = list_state.search_has_focus,
};
const text_result = text_input.textInputRect(ctx, search_bounds, &text_state, .{
.placeholder = config.search_placeholder,
.padding = 3,
});
list_state.filter_len = text_state.len;
list_state.search_cursor = text_state.cursor;
list_state.search_selection_start = text_state.selection_start;
if (text_result.clicked) {
list_state.search_has_focus = true;
}
if (text_result.changed) {
list_state.filter_text_changed = true;
result.filter_changed = true;
result.filter_text = list_state.filter_buf[0..list_state.filter_len];
}
current_x += @as(i32, @intCast(search_w)) + padding;
}
// Draw Clear Button
if (config.show_clear_button and list_state.filter_len > 0) {
const clear_x = bounds.x + @as(i32, @intCast(bounds.w - clear_btn_w)) - padding;
const clear_bounds = Layout.Rect.init(
clear_x,
item_y,
clear_btn_w,
chip_h,
);
const clear_hovered = clear_bounds.contains(mouse.x, mouse.y);
const clear_bg = if (clear_hovered)
Style.Color.rgb(220, 80, 80)
else
Style.Color.rgb(180, 60, 60);
const clear_text = Style.Color.rgb(255, 255, 255);
ctx.pushCommand(Command.roundedRect(
clear_bounds.x,
clear_bounds.y,
clear_bounds.w,
clear_bounds.h,
clear_bg,
chip_radius,
));
ctx.pushCommand(Command.text(
clear_bounds.x + 7,
clear_bounds.y + 4,
"X",
clear_text,
));
if (clear_hovered and ctx.input.mousePressed(.left)) {
list_state.clearFilterText();
list_state.search_cursor = 0;
list_state.search_selection_start = null;
result.filter_changed = true;
result.filter_text = "";
}
}
}
// =============================================================================
// Draw: Header
// =============================================================================
pub fn drawHeaderAt(
ctx: *Context,
bounds: Layout.Rect,
header_y: i32,
config: VirtualAdvancedTableConfig,
colors: *const VirtualAdvancedTableConfig.Colors,
list_state: *VirtualAdvancedTableState,
result: *VirtualAdvancedTableResult,
scroll_offset_x: i32,
) void {
const header_h = config.row_height;
ctx.pushCommand(Command.rect(
bounds.x,
header_y,
bounds.w,
header_h,
colors.header_background,
));
var x: i32 = bounds.x - scroll_offset_x;
for (config.columns) |col| {
const col_end = x + @as(i32, @intCast(col.width));
if (col_end > bounds.x and x < bounds.x + @as(i32, @intCast(bounds.w))) {
ctx.pushCommand(Command.text(
x + 4,
header_y + 3,
col.title,
colors.text,
));
if (list_state.sort_column) |sort_col| {
if (std.mem.eql(u8, sort_col, col.name)) {
const indicator = list_state.sort_direction.symbol();
ctx.pushCommand(Command.text(
x + @as(i32, @intCast(col.width)) - 20,
header_y + 3,
indicator,
colors.text,
));
}
}
if (col.sortable) {
const header_bounds = Layout.Rect.init(x, header_y, col.width, header_h);
const mouse = ctx.input.mousePos();
if (header_bounds.contains(mouse.x, mouse.y) and ctx.input.mousePressed(.left)) {
list_state.toggleSort(col.name);
result.sort_requested = true;
result.sort_column = col.name;
result.sort_direction = list_state.sort_direction;
}
}
ctx.pushCommand(Command.rect(col_end - 1, header_y, 1, header_h, colors.border));
}
x = col_end;
}
ctx.pushCommand(Command.rect(
bounds.x,
header_y + @as(i32, @intCast(header_h)) - 1,
bounds.w,
1,
colors.border,
));
}
// =============================================================================
// Draw: Rows
// =============================================================================
pub fn drawRows(
ctx: *Context,
content_bounds: Layout.Rect,
config: VirtualAdvancedTableConfig,
colors: *const VirtualAdvancedTableConfig.Colors,
list_state: *VirtualAdvancedTableState,
visible_rows: usize,
result: *VirtualAdvancedTableResult,
scroll_offset_x: i32,
) void {
_ = result;
const pds_ptr = ctx.frameAllocator().create(paged_datasource.PagedDataSource) catch {
std.debug.print("[VT-ERROR] No se pudo crear PagedDataSource en frame arena\n", .{});
return;
};
pds_ptr.* = paged_datasource.PagedDataSource.init(list_state, config.columns, null);
const table_ds = pds_ptr.toDataSource();
const selected_row: i32 = if (list_state.findSelectedInWindow()) |window_idx|
@intCast(list_state.windowToGlobalIndex(window_idx))
else
-1;
const first_row = list_state.nav.scroll_row;
const window_rows = list_state.current_window.len;
const last_row = @min(
list_state.nav.scroll_row + visible_rows,
list_state.window_start + window_rows,
);
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,
.visible = true,
};
}
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),
.selected_cell_unfocus = Style.Color.rgb(150, 150, 160),
.text_normal = colors.text,
.text_selected = colors.text_selected,
.border = colors.border,
};
var cell_buffer: [256]u8 = undefined;
const dirty_id: ?i64 = if (list_state.row_edit_buffer.has_changes)
list_state.row_edit_buffer.row_id
else
null;
_ = 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.nav.active_col,
.colors = render_colors,
.columns = render_cols[0..num_cols],
.dirty_row_id = dirty_id,
.edit_buffer = &list_state.row_edit_buffer,
},
&cell_buffer,
);
}
// =============================================================================
// Draw: Footer
// =============================================================================
pub fn drawFooter(
ctx: *Context,
bounds: Layout.Rect,
colors: *const VirtualAdvancedTableConfig.Colors,
list_state: *VirtualAdvancedTableState,
) void {
ctx.pushCommand(Command.rect(
bounds.x,
bounds.y,
bounds.w,
bounds.h,
colors.header_background,
));
var count_buf: [64]u8 = undefined;
const count_info = list_state.getDisplayCount();
const count_str = count_info.format(&count_buf);
var pos_buf: [32]u8 = undefined;
const pos_str = if (list_state.selected_id != null)
if (list_state.findSelectedInWindow()) |idx|
std.fmt.bufPrint(&pos_buf, "{d}", .{list_state.windowToGlobalIndex(idx) + 1}) catch "?"
else
"?"
else
"-";
const display_str = std.fmt.bufPrint(&list_state.footer_display_buf, "{s} de {s}", .{ pos_str, count_str }) catch "...";
list_state.footer_display_len = display_str.len;
ctx.pushCommand(Command.text(
bounds.x + 4,
bounds.y + 2,
list_state.footer_display_buf[0..list_state.footer_display_len],
colors.text,
));
}
// =============================================================================
// Draw: Scrollbar Vertical
// =============================================================================
pub fn drawScrollbar(
ctx: *Context,
bounds: Layout.Rect,
header_h: u32,
footer_h: u32,
list_state: *VirtualAdvancedTableState,
visible_rows: usize,
total_rows: usize,
colors: *const VirtualAdvancedTableConfig.Colors,
) void {
const scrollbar_w: u32 = 14; // Z-Design V2: más ancho para mejor visibilidad
const content_h = bounds.h -| header_h -| footer_h;
table_core.drawVerticalScrollbar(ctx, .{
.track_x = bounds.x + @as(i32, @intCast(bounds.w - scrollbar_w)),
.track_y = bounds.y + @as(i32, @intCast(header_h)),
.width = scrollbar_w,
.height = content_h,
.visible_count = visible_rows,
.total_count = total_rows,
.scroll_pos = list_state.nav.scroll_row,
.track_color = colors.row_alternate,
.thumb_color = colors.border,
});
}
// =============================================================================
// Draw: Scrollbar Horizontal
// =============================================================================
pub fn drawScrollbarH(
ctx: *Context,
bounds: Layout.Rect,
footer_h: u32,
scrollbar_h: u32,
scroll_offset_x: i32,
max_scroll_x: i32,
available_width: u32,
colors: *const VirtualAdvancedTableConfig.Colors,
) void {
const scrollbar_v_w: u32 = 12;
const track_w = bounds.w -| scrollbar_v_w;
const total_width = available_width + @as(u32, @intCast(@max(0, max_scroll_x)));
table_core.drawHorizontalScrollbar(ctx, .{
.track_x = bounds.x,
.track_y = bounds.y + @as(i32, @intCast(bounds.h - footer_h - scrollbar_h)),
.width = track_w,
.height = scrollbar_h,
.visible_width = available_width,
.total_width = total_width,
.scroll_x = scroll_offset_x,
.max_scroll_x = max_scroll_x,
.track_color = colors.row_alternate,
.thumb_color = colors.border,
});
}