feat: Focus ring AA para todos los widgets focusables
Widgets actualizados: - NumberEntry: esquinas redondeadas + focus ring - Radio: esquinas redondeadas para círculos + focus ring en opción - Slider: esquinas redondeadas en track/thumb + focus ring - Tabs: esquinas redondeadas en tab seleccionado + focus ring - Table: focus ring alrededor de toda la tabla - TextArea: esquinas redondeadas + focus ring Nuevos campos: - TableColors.focus_ring para consistencia Total: +135 LOC en 7 archivos 🤖 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
ed0e3e8e5b
commit
e0cbbf6413
7 changed files with 135 additions and 31 deletions
|
|
@ -292,9 +292,19 @@ pub fn numberEntryRect(
|
||||||
colors.border;
|
colors.border;
|
||||||
const text_color = if (state.valid) colors.text else colors.text_invalid;
|
const text_color = if (state.valid) colors.text else colors.text_invalid;
|
||||||
|
|
||||||
// Draw background
|
// Draw background and border (with rounded corners in fancy mode)
|
||||||
|
const corner_radius: u8 = 3;
|
||||||
|
if (Style.isFancy() and corner_radius > 0) {
|
||||||
|
ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, corner_radius));
|
||||||
|
ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color, corner_radius));
|
||||||
|
// Focus ring
|
||||||
|
if (has_focus) {
|
||||||
|
ctx.pushCommand(Command.focusRing(bounds.x, bounds.y, bounds.w, bounds.h, corner_radius));
|
||||||
|
}
|
||||||
|
} 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));
|
||||||
|
}
|
||||||
|
|
||||||
// Draw prefix
|
// Draw prefix
|
||||||
if (config.prefix) |prefix| {
|
if (config.prefix) |prefix| {
|
||||||
|
|
|
||||||
|
|
@ -239,21 +239,44 @@ pub fn radioGroupRect(
|
||||||
colors.border;
|
colors.border;
|
||||||
|
|
||||||
// Draw outer circle (as rect, since we don't have circle primitive)
|
// Draw outer circle (as rect, since we don't have circle primitive)
|
||||||
|
const corner_radius: u8 = @intCast(@min(config.radio_size / 2, 8));
|
||||||
|
if (Style.isFancy() and corner_radius > 0) {
|
||||||
|
ctx.pushCommand(Command.roundedRectOutline(radio_x, radio_y, config.radio_size, config.radio_size, border_color, corner_radius));
|
||||||
|
ctx.pushCommand(Command.roundedRect(radio_x + 1, radio_y + 1, config.radio_size - 2, config.radio_size - 2, colors.background, corner_radius));
|
||||||
|
// Focus ring around the focused option
|
||||||
|
if (is_focused) {
|
||||||
|
ctx.pushCommand(Command.focusRing(radio_x, radio_y, config.radio_size, config.radio_size, corner_radius));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ctx.pushCommand(Command.rectOutline(radio_x, radio_y, config.radio_size, config.radio_size, border_color));
|
ctx.pushCommand(Command.rectOutline(radio_x, radio_y, config.radio_size, config.radio_size, border_color));
|
||||||
ctx.pushCommand(Command.rect(radio_x + 1, radio_y + 1, config.radio_size - 2, config.radio_size - 2, colors.background));
|
ctx.pushCommand(Command.rect(radio_x + 1, radio_y + 1, config.radio_size - 2, config.radio_size - 2, colors.background));
|
||||||
|
}
|
||||||
|
|
||||||
// Draw fill if selected
|
// Draw fill if selected
|
||||||
if (is_selected) {
|
if (is_selected) {
|
||||||
const fill_margin: u32 = 4;
|
const fill_margin: u32 = 4;
|
||||||
const fill_size = config.radio_size -| (fill_margin * 2);
|
const fill_size = config.radio_size -| (fill_margin * 2);
|
||||||
|
const fill_color = if (opt.disabled) colors.fill.darken(30) else colors.fill;
|
||||||
|
const fill_radius: u8 = @intCast(@min(fill_size / 2, 6));
|
||||||
|
if (Style.isFancy() and fill_radius > 0) {
|
||||||
|
ctx.pushCommand(Command.roundedRect(
|
||||||
|
radio_x + @as(i32, @intCast(fill_margin)),
|
||||||
|
radio_y + @as(i32, @intCast(fill_margin)),
|
||||||
|
fill_size,
|
||||||
|
fill_size,
|
||||||
|
fill_color,
|
||||||
|
fill_radius,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
ctx.pushCommand(Command.rect(
|
ctx.pushCommand(Command.rect(
|
||||||
radio_x + @as(i32, @intCast(fill_margin)),
|
radio_x + @as(i32, @intCast(fill_margin)),
|
||||||
radio_y + @as(i32, @intCast(fill_margin)),
|
radio_y + @as(i32, @intCast(fill_margin)),
|
||||||
fill_size,
|
fill_size,
|
||||||
fill_size,
|
fill_size,
|
||||||
if (opt.disabled) colors.fill.darken(30) else colors.fill,
|
fill_color,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw label
|
// Draw label
|
||||||
const label_x = pos_x + @as(i32, @intCast(config.radio_size + config.label_padding));
|
const label_x = pos_x + @as(i32, @intCast(config.radio_size + config.label_padding));
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,12 @@ pub fn sliderRect(
|
||||||
|
|
||||||
// Draw track background
|
// Draw track background
|
||||||
const track_bg = if (config.disabled) colors.track_bg.darken(20) else colors.track_bg;
|
const track_bg = if (config.disabled) colors.track_bg.darken(20) else colors.track_bg;
|
||||||
|
const track_radius: u8 = @intCast(@min(config.track_thickness / 2, 4));
|
||||||
|
if (Style.isFancy() and track_radius > 0) {
|
||||||
|
ctx.pushCommand(Command.roundedRect(track_rect.x, track_rect.y, track_rect.w, track_rect.h, track_bg, track_radius));
|
||||||
|
} else {
|
||||||
ctx.pushCommand(Command.rect(track_rect.x, track_rect.y, track_rect.w, track_rect.h, track_bg));
|
ctx.pushCommand(Command.rect(track_rect.x, track_rect.y, track_rect.w, track_rect.h, track_bg));
|
||||||
|
}
|
||||||
|
|
||||||
// Draw filled portion
|
// Draw filled portion
|
||||||
const fill_color = if (config.disabled) colors.track_fill.darken(30) else colors.track_fill;
|
const fill_color = if (config.disabled) colors.track_fill.darken(30) else colors.track_fill;
|
||||||
|
|
@ -319,9 +324,16 @@ pub fn sliderRect(
|
||||||
else
|
else
|
||||||
colors.thumb;
|
colors.thumb;
|
||||||
|
|
||||||
ctx.pushCommand(Command.rect(thumb_rect.x, thumb_rect.y, thumb_rect.w, thumb_rect.h, thumb_color));
|
const thumb_radius: u8 = @intCast(@min(config.thumb_size / 2, 8));
|
||||||
|
if (Style.isFancy() and thumb_radius > 0) {
|
||||||
|
ctx.pushCommand(Command.roundedRect(thumb_rect.x, thumb_rect.y, thumb_rect.w, thumb_rect.h, thumb_color, thumb_radius));
|
||||||
// Draw focus ring
|
// Draw focus ring
|
||||||
|
if (state.focused and !config.disabled) {
|
||||||
|
ctx.pushCommand(Command.focusRing(thumb_rect.x, thumb_rect.y, thumb_rect.w, thumb_rect.h, thumb_radius));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.pushCommand(Command.rect(thumb_rect.x, thumb_rect.y, thumb_rect.w, thumb_rect.h, thumb_color));
|
||||||
|
// Draw focus ring (simple mode)
|
||||||
if (state.focused and !config.disabled) {
|
if (state.focused and !config.disabled) {
|
||||||
ctx.pushCommand(Command.rectOutline(
|
ctx.pushCommand(Command.rectOutline(
|
||||||
thumb_rect.x - 2,
|
thumb_rect.x - 2,
|
||||||
|
|
@ -331,6 +343,7 @@ pub fn sliderRect(
|
||||||
colors.focus_ring,
|
colors.focus_ring,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw value if enabled and dragging
|
// Draw value if enabled and dragging
|
||||||
if (config.show_value and state.dragging) {
|
if (config.show_value and state.dragging) {
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,22 @@ pub fn tableRectFull(
|
||||||
// End clipping
|
// End clipping
|
||||||
ctx.pushCommand(Command.clipEnd());
|
ctx.pushCommand(Command.clipEnd());
|
||||||
|
|
||||||
|
// Draw focus ring (outside clip region)
|
||||||
|
if (has_focus) {
|
||||||
|
const Style = @import("../../core/style.zig");
|
||||||
|
if (Style.isFancy()) {
|
||||||
|
ctx.pushCommand(Command.focusRing(bounds.x, bounds.y, bounds.w, bounds.h, 4));
|
||||||
|
} else {
|
||||||
|
ctx.pushCommand(Command.rectOutline(
|
||||||
|
bounds.x - 1,
|
||||||
|
bounds.y - 1,
|
||||||
|
bounds.w + 2,
|
||||||
|
bounds.h + 2,
|
||||||
|
colors.focus_ring,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw scrollbar if needed (outside clip region)
|
// Draw scrollbar if needed (outside clip region)
|
||||||
if (table_state.row_count > visible_rows) {
|
if (table_state.row_count > visible_rows) {
|
||||||
render.drawScrollbar(ctx, bounds, table_state, visible_rows, config, colors);
|
render.drawScrollbar(ctx, bounds, table_state, visible_rows, config, colors);
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,8 @@ pub const TableColors = struct {
|
||||||
validation_error_bg: Style.Color = Style.Color.rgb(80, 40, 40),
|
validation_error_bg: Style.Color = Style.Color.rgb(80, 40, 40),
|
||||||
/// Validation error border
|
/// Validation error border
|
||||||
validation_error_border: Style.Color = Style.Color.rgb(200, 60, 60),
|
validation_error_border: Style.Color = Style.Color.rgb(200, 60, 60),
|
||||||
|
/// Focus ring color (when table has keyboard focus)
|
||||||
|
focus_ring: Style.Color = Style.Color.primary,
|
||||||
};
|
};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
||||||
|
|
@ -267,8 +267,14 @@ pub fn tabsRect(
|
||||||
else
|
else
|
||||||
colors.tab_bg;
|
colors.tab_bg;
|
||||||
|
|
||||||
// Draw tab background
|
// Draw tab background (rounded corners for selected tab in fancy mode)
|
||||||
|
const tab_radius: u8 = if (is_selected) 4 else 0;
|
||||||
|
if (Style.isFancy() and tab_radius > 0) {
|
||||||
|
// Only round top corners for top position, bottom for bottom
|
||||||
|
ctx.pushCommand(Command.roundedRect(tab_rect.x, tab_rect.y, tab_rect.w, tab_rect.h, tab_bg, tab_radius));
|
||||||
|
} else {
|
||||||
ctx.pushCommand(Command.rect(tab_rect.x, tab_rect.y, tab_rect.w, tab_rect.h, tab_bg));
|
ctx.pushCommand(Command.rect(tab_rect.x, tab_rect.y, tab_rect.w, tab_rect.h, tab_bg));
|
||||||
|
}
|
||||||
|
|
||||||
// Draw active indicator
|
// Draw active indicator
|
||||||
if (is_selected) {
|
if (is_selected) {
|
||||||
|
|
@ -331,6 +337,32 @@ pub fn tabsRect(
|
||||||
const has_focus = ctx.hasFocus(widget_id);
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
state.focused = has_focus;
|
state.focused = has_focus;
|
||||||
|
|
||||||
|
// Draw focus ring around the selected tab when focused
|
||||||
|
if (has_focus and state.selected < tab_list.len) {
|
||||||
|
// Calculate position of selected tab
|
||||||
|
var focus_x = bar_rect.x;
|
||||||
|
for (0..state.selected) |i| {
|
||||||
|
if (i < tab_widths.len) {
|
||||||
|
focus_x += @as(i32, @intCast(tab_widths[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const focus_w = if (state.selected < tab_widths.len) tab_widths[state.selected] else 0;
|
||||||
|
if (focus_w > 0) {
|
||||||
|
const focus_rect = Layout.Rect.init(focus_x, bar_rect.y, focus_w, config.tab_height);
|
||||||
|
if (Style.isFancy()) {
|
||||||
|
ctx.pushCommand(Command.focusRing(focus_rect.x, focus_rect.y, focus_rect.w, focus_rect.h, 4));
|
||||||
|
} else {
|
||||||
|
ctx.pushCommand(Command.rectOutline(
|
||||||
|
focus_rect.x - 1,
|
||||||
|
focus_rect.y - 1,
|
||||||
|
focus_rect.w + 2,
|
||||||
|
focus_rect.h + 2,
|
||||||
|
colors.indicator,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle keyboard navigation (only when focused)
|
// Handle keyboard navigation (only when focused)
|
||||||
if (has_focus and ctx.input.keyPressed(.left)) {
|
if (has_focus and ctx.input.keyPressed(.left)) {
|
||||||
// Find previous non-disabled tab
|
// Find previous non-disabled tab
|
||||||
|
|
|
||||||
|
|
@ -86,11 +86,19 @@ pub fn textAreaRect(
|
||||||
const bg_color = if (has_focus) colors.background.lighten(5) else colors.background;
|
const bg_color = if (has_focus) colors.background.lighten(5) else colors.background;
|
||||||
const border_color = if (has_focus) colors.border_focused else colors.border;
|
const border_color = if (has_focus) colors.border_focused else colors.border;
|
||||||
|
|
||||||
// Draw background
|
// Draw background and border (with rounded corners in fancy mode)
|
||||||
|
const corner_radius: u8 = 3;
|
||||||
|
if (Style.isFancy() and corner_radius > 0) {
|
||||||
|
ctx.pushCommand(Command.roundedRect(bounds.x, bounds.y, bounds.w, bounds.h, bg_color, corner_radius));
|
||||||
|
ctx.pushCommand(Command.roundedRectOutline(bounds.x, bounds.y, bounds.w, bounds.h, border_color, corner_radius));
|
||||||
|
// Focus ring
|
||||||
|
if (has_focus) {
|
||||||
|
ctx.pushCommand(Command.focusRing(bounds.x, bounds.y, bounds.w, bounds.h, corner_radius));
|
||||||
|
}
|
||||||
|
} 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));
|
||||||
|
|
||||||
// Draw border
|
|
||||||
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));
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate dimensions
|
// Calculate dimensions
|
||||||
const char_width: u32 = 8;
|
const char_width: u32 = 8;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue