feat: Focus ring con anti-aliasing

Command helpers:
- focusRing(x, y, w, h, radius) - focus indicator con AA
- focusRingColor() - versión con color custom

Características:
- Dibuja 2px fuera del widget (offset)
- Color semi-transparente (primary alpha 180)
- Radio +2 para mantener proporciones
- Thickness 2px para visibilidad
- Anti-aliasing habilitado

Widgets actualizados:
- TextInput: focus ring cuando has_focus
- Select: focus ring cuando has_focus (no cuando dropdown abierto)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
reugenio 2025-12-17 09:18:53 +01:00
parent 6e1f8b79d7
commit ed0e3e8e5b
3 changed files with 45 additions and 0 deletions

View file

@ -208,6 +208,43 @@ pub fn roundedRectOutline(x: i32, y: i32, w: u32, h: u32, color: Style.Color, ra
} }; } };
} }
/// Create a focus ring command (visual focus indicator with AA)
/// Draws a rounded outline outside the widget bounds with semi-transparent primary color.
/// Use when a widget has keyboard focus to provide clear visual feedback.
pub fn focusRing(x: i32, y: i32, w: u32, h: u32, radius: u8) DrawCommand {
const offset: i32 = 2; // Draw outside widget bounds
const focus_color = Style.Color.primary.withAlpha(180); // Semi-transparent
const focus_radius = if (radius > 0) radius + 2 else 0;
return .{ .rounded_rect_outline = .{
.x = x - offset,
.y = y - offset,
.w = w + @as(u32, @intCast(offset * 2)),
.h = h + @as(u32, @intCast(offset * 2)),
.color = focus_color,
.radius = focus_radius,
.thickness = 2,
.aa = true,
} };
}
/// Create a focus ring with custom color
pub fn focusRingColor(x: i32, y: i32, w: u32, h: u32, radius: u8, color: Style.Color) DrawCommand {
const offset: i32 = 2;
const focus_radius = if (radius > 0) radius + 2 else 0;
return .{ .rounded_rect_outline = .{
.x = x - offset,
.y = y - offset,
.w = w + @as(u32, @intCast(offset * 2)),
.h = h + @as(u32, @intCast(offset * 2)),
.color = color,
.radius = focus_radius,
.thickness = 2,
.aa = true,
} };
}
// ============================================================================= // =============================================================================
// Tests // Tests
// ============================================================================= // =============================================================================

View file

@ -139,6 +139,10 @@ pub fn selectRect(
if (Style.isFancy() and config.corner_radius > 0) { if (Style.isFancy() and config.corner_radius > 0) {
ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius)); ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius));
ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color, config.corner_radius)); ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color, config.corner_radius));
// Draw focus ring when focused (not when dropdown is open)
if (has_focus and !state.open) {
ctx.pushCommand(Command.focusRing(bounds.x, bounds.y, bounds.w, bounds.h, config.corner_radius));
}
} else { } else {
ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color)); ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color));
ctx.pushCommand(Command.rectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color)); ctx.pushCommand(Command.rectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color));

View file

@ -291,6 +291,10 @@ pub fn textInputRect(
// Fancy mode: rounded corners // Fancy mode: rounded corners
ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius)); ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, config.corner_radius));
ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color, config.corner_radius)); ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color, config.corner_radius));
// Draw focus ring when focused
if (has_focus) {
ctx.pushCommand(Command.focusRing(bounds.x, bounds.y, bounds.w, bounds.h, config.corner_radius));
}
} else { } else {
// Simple mode: square corners // Simple mode: square corners
ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color)); ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color));