fix(panels): Semáforo reubicado - texto + cuadrado a la derecha

- Título: x+28 para dejar espacio al semáforo (antes x+10)
- Semáforo: "Viendo ■" alineado a la derecha (antes ■ izquierda, texto derecha)
- Botones: visual_adjust +2px para compensar baseline TTF
- contrastTextColor como fallback para títulos sin base_color

Layout final: "[N] Título                    Estado ■"

🤖 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 2025-12-31 13:17:37 +01:00
parent d657a25ba7
commit 0f16a77ae4
3 changed files with 32 additions and 22 deletions

View file

@ -665,14 +665,18 @@ pub const Context = struct {
break :blk config.border_color; break :blk config.border_color;
}; };
// Título adaptativo: siempre alta legibilidad (blanco teñido sobre fondo oscuro) // Título adaptativo: siempre alta legibilidad
// - Si hay title_color explícito: usarlo
// - Si hay base_color: derivar con tinte sutil
// - Si no hay ninguno: contrastTextColor sobre focus_bg (blanco/negro según fondo)
const title_color: ?Style.Color = blk: { const title_color: ?Style.Color = blk: {
if (config.title_color) |tc| break :blk tc; if (config.title_color) |tc| break :blk tc;
if (config.base_color) |base| { if (config.base_color) |base| {
const derived = Style.derivePanelFrameColors(base); const derived = Style.derivePanelFrameColors(base);
break :blk derived.title_color; // Siempre legible, focus o no break :blk derived.title_color; // Siempre legible, focus o no
} }
break :blk border_color; // FIX: usar contraste sobre fondo, NO border_color (que es oscuro)
break :blk Style.contrastTextColor(focus_bg);
}; };
// 1. Calculate target color and update transition // 1. Calculate target color and update transition
@ -707,11 +711,12 @@ pub const Context = struct {
self.pushCommand(Command.rectOutline(rect.x, rect.y, rect.w, rect.h, border)); self.pushCommand(Command.rectOutline(rect.x, rect.y, rect.w, rect.h, border));
} }
// 5. Draw title if specified (margen 10,5 para aire visual) // 5. Draw title if specified (margen 28,5 para dejar espacio al semáforo de estado)
// El semáforo de DetailPanelBase se dibuja en (x+8, y+4) con 12x12px
if (config.title) |title| { if (config.title) |title| {
if (title_color) |tc| { if (title_color) |tc| {
self.pushCommand(.{ .text = .{ self.pushCommand(.{ .text = .{
.x = rect.x + 10, .x = rect.x + 28,
.y = rect.y + 5, .y = rect.y + 5,
.text = title, .text = title,
.color = tc, .color = tc,

View file

@ -225,32 +225,37 @@ pub const DetailPanelBase = struct {
// Dibujo: Semaforo // Dibujo: Semaforo
// ========================================================================= // =========================================================================
/// Dibuja el semaforo de estado en la esquina superior izquierda del rect. /// Dibuja el semaforo de estado en la esquina superior derecha del rect.
/// ///
/// Muestra: /// Layout: "[N] Título Viendo ■"
/// - Cuadrado de color (12x12) indicando el estado /// - Texto del estado primero (ej: "Viendo", "Editando", "Guardando...")
/// - Texto del estado en la esquina superior derecha /// - Cuadrado de color (12x12) como remate visual al final
pub fn drawSemaphore(self: *Self, ctx: *Context, rect: Rect) void { pub fn drawSemaphore(self: *Self, ctx: *Context, rect: Rect) void {
const state_color = self.state.getColor(); const state_color = self.state.getColor();
const state_text = self.state.getText(); const state_text = self.state.getText();
// Cuadrado de color (indicador visual) // Posiciones: texto + cuadrado alineados a la derecha
ctx.pushCommand(.{ .rect = .{ // Cuadrado: 12x12 con 8px de margen del borde derecho
.x = rect.x + 8, const square_x = rect.x + @as(i32, @intCast(rect.w)) - 20;
.y = rect.y + 4, // Texto: antes del cuadrado, con 8px de separación
.w = 12, const text_x = rect.x + @as(i32, @intCast(rect.w)) - 100;
.h = 12,
.color = state_color,
} });
// Texto del estado (esquina derecha) // 1. Texto del estado
const text_x = rect.x + @as(i32, @intCast(rect.w)) - 80;
ctx.pushCommand(.{ .text = .{ ctx.pushCommand(.{ .text = .{
.x = text_x, .x = text_x,
.y = rect.y + 4, .y = rect.y + 4,
.text = state_text, .text = state_text,
.color = state_color, .color = state_color,
} }); } });
// 2. Cuadrado de color (indicador visual al final)
ctx.pushCommand(.{ .rect = .{
.x = square_x,
.y = rect.y + 4,
.w = 12,
.h = 12,
.color = state_color,
} });
} }
// ========================================================================= // =========================================================================

View file

@ -132,11 +132,11 @@ pub fn buttonRect(ctx: *Context, bounds: Layout.Rect, text: []const u8, config:
} }
// Draw text centered (con offset +1px cuando está pulsado = "se hunde") // Draw text centered (con offset +1px cuando está pulsado = "se hunde")
// Z-Design V5: usar char_height real + offset -1 para compensar efecto 3D // Z-Design V5: usar char_height real + offset +2 para compensar baseline TTF
const text_width = ctx.measureText(text); const text_width = ctx.measureText(text);
const char_height = ctx.char_height; const char_height = ctx.char_height;
const press_offset: i32 = if (pressed) 1 else 0; const press_offset: i32 = if (pressed) 1 else 0;
const visual_adjust: i32 = -1; // Compensa efecto 3D del bisel que "hunde" visualmente const visual_adjust: i32 = 2; // Compensa baseline de fuente TTF (DroidSans 14pt)
const text_x = bounds.x + @as(i32, @intCast((bounds.w -| text_width) / 2)) + press_offset; const text_x = bounds.x + @as(i32, @intCast((bounds.w -| text_width) / 2)) + press_offset;
const text_y = bounds.y + @as(i32, @intCast((bounds.h -| char_height) / 2)) + press_offset + visual_adjust; const text_y = bounds.y + @as(i32, @intCast((bounds.h -| char_height) / 2)) + press_offset + visual_adjust;
@ -248,11 +248,11 @@ pub fn buttonStatefulRect(
} }
// Draw text centered (con offset +1px cuando está pulsado = "se hunde") // Draw text centered (con offset +1px cuando está pulsado = "se hunde")
// Z-Design V5: usar char_height real + offset -1 para compensar efecto 3D // Z-Design V5: usar char_height real + offset +2 para compensar baseline TTF
const text_width = ctx.measureText(text); const text_width = ctx.measureText(text);
const char_height = ctx.char_height; const char_height = ctx.char_height;
const press_offset: i32 = if (pressed) 1 else 0; const press_offset: i32 = if (pressed) 1 else 0;
const visual_adjust: i32 = -1; // Compensa efecto 3D del bisel que "hunde" visualmente const visual_adjust: i32 = 2; // Compensa baseline de fuente TTF (DroidSans 14pt)
const text_x = bounds.x + @as(i32, @intCast((bounds.w -| text_width) / 2)) + press_offset; const text_x = bounds.x + @as(i32, @intCast((bounds.w -| text_width) / 2)) + press_offset;
const text_y = bounds.y + @as(i32, @intCast((bounds.h -| char_height) / 2)) + press_offset + visual_adjust; const text_y = bounds.y + @as(i32, @intCast((bounds.h -| char_height) / 2)) + press_offset + visual_adjust;