From fc4da751ca68c21d75cd248477f3a9133cf5b146 Mon Sep 17 00:00:00 2001 From: Sergio Date: Sun, 10 May 2026 10:51:00 +0000 Subject: [PATCH] =?UTF-8?q?feat(yahweh-widget-text-input):=20focus-aware?= =?UTF-8?q?=20border=20+=20caret=20s=C3=B3lo=20on=20focus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Iter 7 (mini-iter — el text-input ya estaba themed, faltaba el polish de focus visibility). Antes el border era siempre accent_strong y el caret | siempre presente — imposible distinguir cuál input está activo en un form con varios fields. - Border focus-aware: focused → accent_strong; blur → border (slot tenue del theme). - Caret | sólo on focus; sin focus se muestra texto plano (reduce ruido visual). - render usa window.is_focused(focus_handle) para chequear. Sin cambios en API pública. Tests downstream verdes. Limitación: caret estático (no parpadea). Iter futura si emerge la necesidad de animation timer via cx.spawn. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 36 +++++++++++++++++++ .../ui_engine/widgets/text_input/src/lib.rs | 22 +++++++++--- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7648706..09d790a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,42 @@ ratio/diff ver `git show `. ## 2026-05-10 +### feat(yahweh-widget-text-input): focus-aware border + caret sólo on focus +Iter 7 (mini-iter — el text-input ya estaba themed, faltaba sólo +el polish de focus visibility). Antes el border era siempre +`accent_strong` y el caret `|` siempre estaba presente — imposible +distinguir cuál input está activo en un form con varios fields. + +Cambios en `yahweh-widget-text-input`: +- **Border focus-aware**: cuando el input está focused, border = + `theme.accent_strong` (color vivo). Cuando no, border = + `theme.border` (color tenue del chrome). Se obtiene via + `self.focus_handle.is_focused(window)`. +- **Caret `|` sólo on focus**: cuando el input no tiene focus, se + muestra el texto plano sin caret. Reduce el "ruido visual" en + forms con muchos fields. +- `render` ahora usa el `Window` arg (antes `_w`) para chequear + focus. + +Sin cambios en API pública — todo es interno al `render`. El +binario no requiere migración. + +Tests: sin cambios (los tests del crate son struct constructors, +no rendering). Tests downstream del widget (`yahweh-widget-meta-form`, +`nakui-ui`) siguen verdes — el cambio es backward compatible. + +Beneficio operativo: +- Forms con 5+ fields ahora son navegables: el usuario ve cuál + input recibe sus teclas via el border highlighted. +- Cambio de theme afecta también a inputs (ya estaban themed; ahora + además respetan el `accent_strong` específico del preset + cuando focused, vs el `border` cuando no). + +Limitación pendiente: el caret `|` literal no parpadea (no hay +animation timer). Cuando emerja la necesidad, agregar via +`cx.spawn` con un loop de toggle. Por ahora el caret estático on +focus es suficiente signal. + ### feat(yahweh-widget-theme-switcher): control para ciclar themes en runtime Iter 6. Cierra el ciclo del theme: ya teníamos paleta themed + widgets que la consumen, faltaba el control UI para rotar entre diff --git a/crates/modules/ui_engine/widgets/text_input/src/lib.rs b/crates/modules/ui_engine/widgets/text_input/src/lib.rs index 638b40c..806f47c 100644 --- a/crates/modules/ui_engine/widgets/text_input/src/lib.rs +++ b/crates/modules/ui_engine/widgets/text_input/src/lib.rs @@ -122,14 +122,28 @@ impl TextInput { } impl Render for TextInput { - fn render(&mut self, _w: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, w: &mut Window, cx: &mut Context) -> impl IntoElement { let theme = Theme::global(cx).clone(); let is_empty = self.text.is_empty(); + // Border-color depende del focus: focused → accent (señal + // clara de "vas a tipear acá"); blur → border (silencioso). + // Sin esto era imposible saber qué input estaba activo en + // un form con varios fields. + let is_focused = self.focus_handle.is_focused(w); + let border_color = if is_focused { + theme.accent_strong + } else { + theme.border + }; let display: SharedString = if is_empty { self.placeholder.clone() - } else { - // Cursor siempre al final — sin movimiento de cursor. + } else if is_focused { + // Caret al final, sólo cuando el input tiene focus — + // así el usuario ve dónde va a aparecer el siguiente + // char. Inputs sin focus no muestran caret (es ruido). SharedString::from(format!("{}|", self.text)) + } else { + SharedString::from(self.text.clone()) }; let text_color = if is_empty { theme.fg_disabled @@ -147,7 +161,7 @@ impl Render for TextInput { .min_w(px(200.0)) .bg(theme.bg_panel.clone()) .border_1() - .border_color(theme.accent_strong) + .border_color(border_color) .rounded(px(4.0)) .text_size(px(13.0)) .text_color(text_color)