fix(tabs): Procesar eventos ANTES de dibujar

Problema: Al hacer clic en un tab, el dibujo visual mostraba el
tab viejo como activo porque is_selected se calculaba ANTES de
procesar el clic.

Solución: Separar en dos passes:
- Pass 1: Detectar clics y actualizar state.selected
- Pass 2: Dibujar con el estado ya actualizado

Esto elimina el "parpadeo" donde el tab visual no coincidía
con el tab lógico durante un frame.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
R.Eugenio 2026-01-05 22:58:00 +01:00
parent f17104c08b
commit cc7c2a00c6

View file

@ -247,9 +247,53 @@ pub fn tabsRect(
total_width += width; total_width += width;
} }
// Draw tabs // ==========================================================================
// PASS 1: Process input events BEFORE drawing (fix visual lag on click)
// ==========================================================================
var tab_x = bar_rect.x; var tab_x = bar_rect.x;
for (tab_list, 0..) |tab, i| {
if (i >= tab_widths.len) break;
const tab_width = tab_widths[i];
const tab_rect = Layout.Rect.init(tab_x, bar_rect.y, tab_width, config.tab_height);
const is_hovered_input = tab_rect.contains(mouse.x, mouse.y) and !tab.disabled;
// Handle close button click (pass 1)
if (config.show_close and tab.closable) {
const close_x = tab_x + @as(i32, @intCast(tab_width - config.close_size - 8));
const close_y = bar_rect.y + @as(i32, @intCast((config.tab_height - config.close_size) / 2));
const close_rect = Layout.Rect.init(close_x, close_y, config.close_size, config.close_size);
const close_hovered = close_rect.contains(mouse.x, mouse.y);
if (close_hovered) {
state.close_hovered = @intCast(i);
}
if (mouse_pressed and close_hovered) {
result.closed = true;
result.closed_index = i;
}
}
// Handle tab click (pass 1) - update state BEFORE drawing
if (mouse_pressed and is_hovered_input and state.close_hovered != @as(i32, @intCast(i))) {
ctx.requestFocus(widget_id);
if (state.selected != i) {
state.selected = i;
result.changed = true;
result.selected = i;
}
}
tab_x += @as(i32, @intCast(tab_width));
}
// ==========================================================================
// PASS 2: Draw tabs with UPDATED state
// ==========================================================================
tab_x = bar_rect.x;
for (tab_list, 0..) |tab, i| { for (tab_list, 0..) |tab, i| {
if (i >= tab_widths.len) break; if (i >= tab_widths.len) break;
@ -328,38 +372,20 @@ pub fn tabsRect(
const text_y = bar_rect.y + @as(i32, @intCast((config.tab_height - 8) / 2)); const text_y = bar_rect.y + @as(i32, @intCast((config.tab_height - 8) / 2));
ctx.pushCommand(Command.text(tab_x + @as(i32, @intCast(config.padding_h)), text_y, tab.label, text_color)); ctx.pushCommand(Command.text(tab_x + @as(i32, @intCast(config.padding_h)), text_y, tab.label, text_color));
// Draw close button // Draw close button (events already handled in Pass 1)
if (config.show_close and tab.closable) { if (config.show_close and tab.closable) {
const close_x = tab_x + @as(i32, @intCast(tab_width - config.close_size - 8)); const close_x = tab_x + @as(i32, @intCast(tab_width - config.close_size - 8));
const close_y = bar_rect.y + @as(i32, @intCast((config.tab_height - config.close_size) / 2)); const close_y = bar_rect.y + @as(i32, @intCast((config.tab_height - config.close_size) / 2));
const close_rect = Layout.Rect.init(close_x, close_y, config.close_size, config.close_size); const close_rect = Layout.Rect.init(close_x, close_y, config.close_size, config.close_size);
const close_hovered = close_rect.contains(mouse.x, mouse.y); const close_hovered = close_rect.contains(mouse.x, mouse.y);
if (close_hovered) {
state.close_hovered = @intCast(i);
}
const close_color = if (close_hovered) colors.close_hover else colors.close_color; const close_color = if (close_hovered) colors.close_hover else colors.close_color;
// Draw X // Draw X
ctx.pushCommand(Command.text(close_x + 3, close_y + 2, "x", close_color)); ctx.pushCommand(Command.text(close_x + 3, close_y + 2, "x", close_color));
// Handle close click
if (mouse_pressed and close_hovered) {
result.closed = true;
result.closed_index = i;
}
} }
// Handle tab click // Note: Tab click handling moved to Pass 1 (before drawing)
if (mouse_pressed and is_hovered and state.close_hovered != @as(i32, @intCast(i))) {
ctx.requestFocus(widget_id);
if (state.selected != i) {
state.selected = i;
result.changed = true;
result.selected = i;
}
}
tab_x += @as(i32, @intCast(tab_width)); tab_x += @as(i32, @intCast(tab_width));
} }