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:
parent
d657a25ba7
commit
0f16a77ae4
3 changed files with 32 additions and 22 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue