From ed0e3e8e5b6d0bbe0d5a96b9701aeb5d74b7cd83 Mon Sep 17 00:00:00 2001 From: reugenio Date: Wed, 17 Dec 2025 09:18:53 +0100 Subject: [PATCH] feat: Focus ring con anti-aliasing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/core/command.zig | 37 +++++++++++++++++++++++++++++++++++++ src/widgets/select.zig | 4 ++++ src/widgets/text_input.zig | 4 ++++ 3 files changed, 45 insertions(+) diff --git a/src/core/command.zig b/src/core/command.zig index 0314e9c..3d966b0 100644 --- a/src/core/command.zig +++ b/src/core/command.zig @@ -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 // ============================================================================= diff --git a/src/widgets/select.zig b/src/widgets/select.zig index bb9cb41..e20a900 100644 --- a/src/widgets/select.zig +++ b/src/widgets/select.zig @@ -139,6 +139,10 @@ pub fn selectRect( 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.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 { 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)); diff --git a/src/widgets/text_input.zig b/src/widgets/text_input.zig index c72a35c..93f00f3 100644 --- a/src/widgets/text_input.zig +++ b/src/widgets/text_input.zig @@ -291,6 +291,10 @@ pub fn textInputRect( // Fancy mode: rounded corners 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)); + // Draw focus ring when focused + if (has_focus) { + ctx.pushCommand(Command.focusRing(bounds.x, bounds.y, bounds.w, bounds.h, config.corner_radius)); + } } else { // Simple mode: square corners ctx.pushCommand(Command.rect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color));