//! 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, }); }