docs: Documentar sistema de focus completado y widgets adaptados
- Actualizar FOCUS_TRANSITION_2025-12-11.md con patrón de integración - Actualizar CLAUDE.md: sección SISTEMA DE FOCUS - RESUELTO - Widgets adaptados a FocusSystem: - numberentry.zig: registerFocusable, requestFocus, hasFocus - textarea.zig: registerFocusable, requestFocus, hasFocus - select.zig: campo focused, integración completa - radio.zig: reemplazado focus manual por FocusSystem - slider.zig: reemplazado focus manual por FocusSystem - tabs.zig: navegación teclado solo cuando tiene focus 🤖 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
7cde6370d8
commit
3517a6f972
8 changed files with 206 additions and 60 deletions
96
CLAUDE.md
96
CLAUDE.md
|
|
@ -600,6 +600,8 @@ const stdout = std.fs.File.stdout(); // NO std.io.getStdOut()
|
||||||
| 2025-12-09 | v0.14.1 | FASE 9: Gio parity - 12 widgets + gesture system |
|
| 2025-12-09 | v0.14.1 | FASE 9: Gio parity - 12 widgets + gesture system |
|
||||||
| 2025-12-09 | v0.15.0 | FASE 10: Mobile/Web - WASM, Android, iOS backends |
|
| 2025-12-09 | v0.15.0 | FASE 10: Mobile/Web - WASM, Android, iOS backends |
|
||||||
| 2025-12-09 | v0.15.0 | Documentación: REFERENCE.md completo (1370 líneas) |
|
| 2025-12-09 | v0.15.0 | Documentación: REFERENCE.md completo (1370 líneas) |
|
||||||
|
| 2025-12-11 | v0.15.1 | FocusSystem rediseñado: registration_group/active_group, focus implícito |
|
||||||
|
| 2025-12-11 | v0.15.2 | Widgets adaptados a FocusSystem: numberentry, textarea, select, radio, slider, tabs |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -685,69 +687,75 @@ cd /mnt/cello2/arno/re/recode/zig/zcatgui
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## TAREA PENDIENTE PRIORITARIA: SISTEMA DE FOCUS (2025-12-11)
|
## SISTEMA DE FOCUS - RESUELTO (2025-12-11)
|
||||||
|
|
||||||
> **IMPORTANTE**: Esta seccion describe trabajo incompleto que DEBE completarse.
|
El sistema de focus fue rediseñado y ahora funciona correctamente.
|
||||||
> La sesion anterior hizo multiples intentos sin exito. Se requiere un analisis
|
|
||||||
> profundo antes de hacer mas cambios.
|
|
||||||
|
|
||||||
### Documento de transicion (LEER PRIMERO)
|
### Documentación completa
|
||||||
|
|
||||||
```
|
```
|
||||||
/mnt/cello2/arno/re/recode/zig/zcatgui/docs/FOCUS_TRANSITION_2025-12-11.md
|
/mnt/cello2/arno/re/recode/zig/zcatgui/docs/FOCUS_TRANSITION_2025-12-11.md
|
||||||
```
|
```
|
||||||
|
|
||||||
### Resumen del problema
|
### Arquitectura final
|
||||||
|
|
||||||
El sistema de focus fue rediseñado (unificado de dos sistemas a uno) pero **NO FUNCIONA**:
|
```
|
||||||
|
FocusSystem
|
||||||
|
|
|
||||||
|
+---------------+---------------+
|
||||||
|
| |
|
||||||
|
FocusGroup 1 FocusGroup 2
|
||||||
|
(Panel Lista) (Panel Detalle)
|
||||||
|
| |
|
||||||
|
Table widget TextInput widgets
|
||||||
|
| |
|
||||||
|
- registerFocusable() - registerFocusable()
|
||||||
|
- hasFocus() -> true/false - hasFocus() -> true/false
|
||||||
|
```
|
||||||
|
|
||||||
1. Al iniciar app, ambos paneles muestran focus visual
|
### Conceptos clave
|
||||||
2. Teclado no responde hasta hacer clic (flechas, Tab)
|
|
||||||
3. Despues de clic en panel derecho, flechas siguen moviendo tabla izquierda
|
|
||||||
|
|
||||||
### Cambios realizados en esta sesion
|
- **registration_group**: Grupo donde se registran widgets durante draw
|
||||||
|
- **active_group**: Grupo con focus de teclado (solo cambia con F6/clic)
|
||||||
|
- **Focus implícito**: Primer widget del grupo activo tiene focus si `focused_index == null`
|
||||||
|
|
||||||
**Archivos creados**:
|
### API para widgets
|
||||||
- `core/focus.zig` - Nuevo FocusSystem unificado
|
|
||||||
|
|
||||||
**Archivos eliminados**:
|
```zig
|
||||||
- `widgets/focus.zig` - FocusManager viejo
|
// 1. Generar ID único basado en dirección del state
|
||||||
- `core/focus_group.zig` - FocusGroupManager viejo
|
const widget_id: u64 = @intFromPtr(state);
|
||||||
|
|
||||||
**Archivos modificados**:
|
// 2. Registrar en el grupo de focus activo
|
||||||
- `core/context.zig` - Usa FocusSystem, metodos de conveniencia
|
ctx.registerFocusable(widget_id);
|
||||||
- `widgets/table.zig` - Añadido `handle_tab` config, usa ctx.hasFocus()
|
|
||||||
- `widgets/text_input.zig` - Usa ctx.hasFocus(), ctx.requestFocus()
|
|
||||||
- `zcatgui.zig` - Exporta FocusSystem, FocusGroup
|
|
||||||
- `backend/backend.zig` - Nuevo evento window_exposed
|
|
||||||
- `backend/sdl2.zig` - Emite window_exposed
|
|
||||||
|
|
||||||
### Lo que funciona
|
// 3. Al hacer clic, solicitar focus
|
||||||
|
if (clicked) {
|
||||||
|
ctx.requestFocus(widget_id);
|
||||||
|
}
|
||||||
|
|
||||||
- Repintado al volver de Alt+Tab
|
// 4. Verificar si tiene focus
|
||||||
- Navegacion por tabla post-clic
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
- Tab entre TextInputs post-clic
|
state.focused = has_focus;
|
||||||
- Compilacion sin errores
|
```
|
||||||
|
|
||||||
### Lo que NO funciona
|
### Widgets integrados con FocusSystem
|
||||||
|
|
||||||
- Focus inicial automatico
|
| Widget | Estado |
|
||||||
- Teclado antes de primer clic
|
|--------|--------|
|
||||||
- Focus visual exclusivo (ambos paneles lo muestran)
|
| Table | Adaptado - inicializa selected_row/col a 0 |
|
||||||
- Aislamiento de grupos (flechas afectan tabla aunque focus este en otro panel)
|
| TextInput | Correcto |
|
||||||
|
| NumberEntry | Adaptado |
|
||||||
|
| TextArea | Adaptado |
|
||||||
|
| Select | Adaptado |
|
||||||
|
| Radio | Adaptado |
|
||||||
|
| Slider | Adaptado |
|
||||||
|
| Tabs | Adaptado |
|
||||||
|
|
||||||
### Hipotesis del bug
|
### Widgets sin FocusSystem (no lo necesitan)
|
||||||
|
|
||||||
Ver documento de transicion para hipotesis detalladas. Resumen:
|
- **Modales**: Menu, Modal, Tooltip, Toast
|
||||||
1. Cambio de `active_group` durante draw rompe logica
|
- **Acción única**: Button, Checkbox, Switch
|
||||||
2. Focus visual no sincronizado con estado real
|
- **Solo visualización**: Label, Progress, Badge, Icon, Image
|
||||||
3. Table procesa teclado independientemente
|
|
||||||
4. Widgets no se registran en grupo correcto
|
|
||||||
|
|
||||||
### Regla para continuar
|
|
||||||
|
|
||||||
**Analizar primero, planificar despues, implementar al final.**
|
|
||||||
NO hacer cambios incrementales sin entender la causa raiz.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,5 +144,75 @@ Cualquier funcion que dependa de multiples campos (como `selectedCell()`) fallar
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Documento cerrado: 2025-12-11 ~19:00*
|
## WIDGETS ADAPTADOS AL SISTEMA DE FOCUS
|
||||||
|
|
||||||
|
Todos los widgets interactivos fueron revisados y adaptados al nuevo sistema de focus.
|
||||||
|
|
||||||
|
### Patrón de integración
|
||||||
|
|
||||||
|
Cada widget interactivo debe seguir este patrón:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
pub fn widgetRect(ctx: *Context, bounds: Layout.Rect, state: *WidgetState, ...) Result {
|
||||||
|
// 1. Generar ID único basado en dirección del state
|
||||||
|
const widget_id: u64 = @intFromPtr(state);
|
||||||
|
|
||||||
|
// 2. Registrar en el grupo de focus activo
|
||||||
|
ctx.registerFocusable(widget_id);
|
||||||
|
|
||||||
|
// 3. Al hacer clic, solicitar focus
|
||||||
|
if (clicked) {
|
||||||
|
ctx.requestFocus(widget_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Verificar si tiene focus
|
||||||
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
|
state.focused = has_focus;
|
||||||
|
|
||||||
|
// 5. Solo procesar teclado si tiene focus
|
||||||
|
if (has_focus) {
|
||||||
|
// ... manejo de teclas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Widgets adaptados (integrados con FocusSystem)
|
||||||
|
|
||||||
|
| Widget | Archivo | Estado | Notas |
|
||||||
|
|--------|---------|--------|-------|
|
||||||
|
| **Table** | `table.zig` | Adaptado | Fix principal - inicializa selected_row/col a 0 |
|
||||||
|
| **TextInput** | `text_input.zig` | Correcto | Ya usaba el patrón correcto |
|
||||||
|
| **NumberEntry** | `numberentry.zig` | Adaptado | Añadido registerFocusable, requestFocus, hasFocus |
|
||||||
|
| **TextArea** | `textarea.zig` | Adaptado | Añadido registerFocusable, requestFocus, hasFocus |
|
||||||
|
| **Select** | `select.zig` | Adaptado | Añadido campo focused, integración completa |
|
||||||
|
| **Radio** | `radio.zig` | Adaptado | Reemplazado focus manual por FocusSystem |
|
||||||
|
| **Slider** | `slider.zig` | Adaptado | Reemplazado focus manual por FocusSystem |
|
||||||
|
| **Tabs** | `tabs.zig` | Adaptado | Navegación por teclado solo cuando tiene focus |
|
||||||
|
|
||||||
|
### Widgets que NO requieren adaptación
|
||||||
|
|
||||||
|
| Widget | Motivo |
|
||||||
|
|--------|--------|
|
||||||
|
| **Menu** | Modal - cuando está abierto captura toda la entrada |
|
||||||
|
| **Checkbox** | Solo responde a clic, no mantiene focus persistente |
|
||||||
|
| **Switch** | Solo responde a clic, no mantiene focus persistente |
|
||||||
|
| **Button** | Solo responde a clic, no mantiene focus persistente |
|
||||||
|
| **Widgets display-only** | label, badge, progress, icon, image, etc. - no interactivos |
|
||||||
|
| **Overlays** | modal, tooltip, toast - manejan su propia modalidad |
|
||||||
|
|
||||||
|
### Categorización de widgets
|
||||||
|
|
||||||
|
**Widgets con focus persistente** (necesitan FocusSystem):
|
||||||
|
- Widgets de entrada de datos (TextInput, NumberEntry, TextArea)
|
||||||
|
- Widgets de selección con teclado (Table, Select, Radio, Tabs)
|
||||||
|
- Widgets de valor con teclado (Slider)
|
||||||
|
|
||||||
|
**Widgets sin focus persistente** (NO necesitan FocusSystem):
|
||||||
|
- Widgets de acción única (Button, Checkbox, Switch)
|
||||||
|
- Widgets de solo visualización (Label, Progress, Badge, Icon)
|
||||||
|
- Widgets modales/overlay (Menu, Modal, Tooltip, Toast)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Documento actualizado: 2025-12-11*
|
||||||
*Autores: Arno + Claude (Opus 4.5)*
|
*Autores: Arno + Claude (Opus 4.5)*
|
||||||
|
|
|
||||||
|
|
@ -255,14 +255,25 @@ pub fn numberEntryRect(
|
||||||
|
|
||||||
if (bounds.isEmpty()) return result;
|
if (bounds.isEmpty()) return result;
|
||||||
|
|
||||||
|
// Generate unique ID for this widget based on state address
|
||||||
|
const widget_id: u64 = @intFromPtr(state);
|
||||||
|
|
||||||
|
// Register as focusable in the active focus group
|
||||||
|
ctx.registerFocusable(widget_id);
|
||||||
|
|
||||||
const mouse = ctx.input.mousePos();
|
const mouse = ctx.input.mousePos();
|
||||||
const mouse_pressed = ctx.input.mousePressed(.left);
|
const mouse_pressed = ctx.input.mousePressed(.left);
|
||||||
const hovered = bounds.contains(mouse.x, mouse.y);
|
const hovered = bounds.contains(mouse.x, mouse.y);
|
||||||
|
|
||||||
if (hovered and mouse_pressed) {
|
if (hovered and mouse_pressed) {
|
||||||
state.focused = true;
|
// Request focus through the focus system
|
||||||
|
ctx.requestFocus(widget_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this widget has focus
|
||||||
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
|
state.focused = has_focus;
|
||||||
|
|
||||||
// Calculate areas
|
// Calculate areas
|
||||||
const spinner_w: u32 = if (config.spinner) 20 else 0;
|
const spinner_w: u32 = if (config.spinner) 20 else 0;
|
||||||
const prefix_w: u32 = if (config.prefix) |p| @as(u32, @intCast(p.len * 8 + 4)) else 0;
|
const prefix_w: u32 = if (config.prefix) |p| @as(u32, @intCast(p.len * 8 + 4)) else 0;
|
||||||
|
|
@ -272,10 +283,10 @@ pub fn numberEntryRect(
|
||||||
_ = bounds.w -| prefix_w -| suffix_w -| (spinner_w * 2); // input_w available for future use
|
_ = bounds.w -| prefix_w -| suffix_w -| (spinner_w * 2); // input_w available for future use
|
||||||
|
|
||||||
// Colors
|
// Colors
|
||||||
const bg_color = if (state.focused) colors.background_focused else colors.background;
|
const bg_color = if (has_focus) colors.background_focused else colors.background;
|
||||||
const border_color = if (!state.valid)
|
const border_color = if (!state.valid)
|
||||||
colors.border_invalid
|
colors.border_invalid
|
||||||
else if (state.focused)
|
else if (has_focus)
|
||||||
colors.border_focused
|
colors.border_focused
|
||||||
else
|
else
|
||||||
colors.border;
|
colors.border;
|
||||||
|
|
@ -296,7 +307,7 @@ pub fn numberEntryRect(
|
||||||
ctx.pushCommand(Command.text(input_x + 4, text_y, state.text(), text_color));
|
ctx.pushCommand(Command.text(input_x + 4, text_y, state.text(), text_color));
|
||||||
|
|
||||||
// Draw cursor if focused
|
// Draw cursor if focused
|
||||||
if (state.focused) {
|
if (has_focus) {
|
||||||
const cursor_x = input_x + 4 + @as(i32, @intCast(state.cursor * 8));
|
const cursor_x = input_x + 4 + @as(i32, @intCast(state.cursor * 8));
|
||||||
ctx.pushCommand(Command.rect(cursor_x, bounds.y + 4, 2, bounds.h - 8, colors.cursor));
|
ctx.pushCommand(Command.rect(cursor_x, bounds.y + 4, 2, bounds.h - 8, colors.cursor));
|
||||||
}
|
}
|
||||||
|
|
@ -350,7 +361,7 @@ pub fn numberEntryRect(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle text input
|
// Handle text input
|
||||||
if (state.focused) {
|
if (has_focus) {
|
||||||
const text_input = ctx.input.getTextInput();
|
const text_input = ctx.input.getTextInput();
|
||||||
for (text_input) |char| {
|
for (text_input) |char| {
|
||||||
state.insert(char);
|
state.insert(char);
|
||||||
|
|
|
||||||
|
|
@ -192,14 +192,24 @@ pub fn radioGroupRect(
|
||||||
|
|
||||||
if (bounds.isEmpty() or options.len == 0) return result;
|
if (bounds.isEmpty() or options.len == 0) return result;
|
||||||
|
|
||||||
|
// Generate unique ID for this widget based on state address
|
||||||
|
const widget_id: u64 = @intFromPtr(state);
|
||||||
|
|
||||||
|
// Register as focusable in the active focus group
|
||||||
|
ctx.registerFocusable(widget_id);
|
||||||
|
|
||||||
const mouse = ctx.input.mousePos();
|
const mouse = ctx.input.mousePos();
|
||||||
const mouse_pressed = ctx.input.mousePressed(.left);
|
const mouse_pressed = ctx.input.mousePressed(.left);
|
||||||
|
|
||||||
// Check if group area clicked (for focus)
|
// Check if group area clicked (for focus)
|
||||||
if (mouse_pressed and bounds.contains(mouse.x, mouse.y)) {
|
if (mouse_pressed and bounds.contains(mouse.x, mouse.y)) {
|
||||||
state.focused = true;
|
ctx.requestFocus(widget_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this widget has focus
|
||||||
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
|
state.focused = has_focus;
|
||||||
|
|
||||||
// Draw options
|
// Draw options
|
||||||
var pos_x = bounds.x;
|
var pos_x = bounds.x;
|
||||||
var pos_y = bounds.y;
|
var pos_y = bounds.y;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ pub const SelectState = struct {
|
||||||
open: bool = false,
|
open: bool = false,
|
||||||
/// Scroll offset in dropdown (for many items)
|
/// Scroll offset in dropdown (for many items)
|
||||||
scroll_offset: usize = 0,
|
scroll_offset: usize = 0,
|
||||||
|
/// Whether this widget has focus
|
||||||
|
focused: bool = false,
|
||||||
|
|
||||||
/// Get selected index as optional usize
|
/// Get selected index as optional usize
|
||||||
pub fn selectedIndex(self: SelectState) ?usize {
|
pub fn selectedIndex(self: SelectState) ?usize {
|
||||||
|
|
@ -83,6 +85,12 @@ pub fn selectRect(
|
||||||
|
|
||||||
if (bounds.isEmpty()) return result;
|
if (bounds.isEmpty()) return result;
|
||||||
|
|
||||||
|
// Generate unique ID for this widget based on state address
|
||||||
|
const widget_id: u64 = @intFromPtr(state);
|
||||||
|
|
||||||
|
// Register as focusable in the active focus group
|
||||||
|
ctx.registerFocusable(widget_id);
|
||||||
|
|
||||||
const theme = Style.Theme.dark;
|
const theme = Style.Theme.dark;
|
||||||
|
|
||||||
// Check mouse interaction on main button
|
// Check mouse interaction on main button
|
||||||
|
|
@ -90,11 +98,16 @@ pub fn selectRect(
|
||||||
const hovered = bounds.contains(mouse.x, mouse.y) and !config.disabled;
|
const hovered = bounds.contains(mouse.x, mouse.y) and !config.disabled;
|
||||||
const clicked = hovered and ctx.input.mousePressed(.left);
|
const clicked = hovered and ctx.input.mousePressed(.left);
|
||||||
|
|
||||||
// Toggle dropdown on click
|
// Toggle dropdown on click and request focus
|
||||||
if (clicked) {
|
if (clicked) {
|
||||||
|
ctx.requestFocus(widget_id);
|
||||||
state.open = !state.open;
|
state.open = !state.open;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this widget has focus
|
||||||
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
|
state.focused = has_focus;
|
||||||
|
|
||||||
// Determine button colors
|
// Determine button colors
|
||||||
const bg_color = if (config.disabled)
|
const bg_color = if (config.disabled)
|
||||||
theme.button_bg.darken(20)
|
theme.button_bg.darken(20)
|
||||||
|
|
@ -105,7 +118,7 @@ pub fn selectRect(
|
||||||
else
|
else
|
||||||
theme.button_bg;
|
theme.button_bg;
|
||||||
|
|
||||||
const border_color = if (state.open) theme.primary else theme.border;
|
const border_color = if (has_focus or state.open) theme.primary else theme.border;
|
||||||
|
|
||||||
// Draw main button background
|
// Draw main button background
|
||||||
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));
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,12 @@ pub fn sliderRect(
|
||||||
|
|
||||||
if (bounds.isEmpty()) return result;
|
if (bounds.isEmpty()) return result;
|
||||||
|
|
||||||
|
// Generate unique ID for this widget based on state address
|
||||||
|
const widget_id: u64 = @intFromPtr(state);
|
||||||
|
|
||||||
|
// Register as focusable in the active focus group
|
||||||
|
ctx.registerFocusable(widget_id);
|
||||||
|
|
||||||
const mouse = ctx.input.mousePos();
|
const mouse = ctx.input.mousePos();
|
||||||
const mouse_down = ctx.input.mouseDown(.left);
|
const mouse_down = ctx.input.mouseDown(.left);
|
||||||
const mouse_pressed = ctx.input.mousePressed(.left);
|
const mouse_pressed = ctx.input.mousePressed(.left);
|
||||||
|
|
@ -198,11 +204,15 @@ pub fn sliderRect(
|
||||||
|
|
||||||
// Handle drag start
|
// Handle drag start
|
||||||
if (mouse_pressed and bounds_hovered and !config.disabled) {
|
if (mouse_pressed and bounds_hovered and !config.disabled) {
|
||||||
|
ctx.requestFocus(widget_id);
|
||||||
state.dragging = true;
|
state.dragging = true;
|
||||||
state.focused = true;
|
|
||||||
result.drag_started = true;
|
result.drag_started = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this widget has focus
|
||||||
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
|
state.focused = has_focus;
|
||||||
|
|
||||||
// Handle drag end
|
// Handle drag end
|
||||||
if (mouse_released and state.dragging) {
|
if (mouse_released and state.dragging) {
|
||||||
state.dragging = false;
|
state.dragging = false;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ pub const TabsState = struct {
|
||||||
hovered: i32 = -1,
|
hovered: i32 = -1,
|
||||||
/// Close button hovered (-1 for none)
|
/// Close button hovered (-1 for none)
|
||||||
close_hovered: i32 = -1,
|
close_hovered: i32 = -1,
|
||||||
|
/// Whether this widget has focus
|
||||||
|
focused: bool = false,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
|
@ -179,6 +181,12 @@ pub fn tabsRect(
|
||||||
|
|
||||||
if (bounds.isEmpty() or tab_list.len == 0) return result;
|
if (bounds.isEmpty() or tab_list.len == 0) return result;
|
||||||
|
|
||||||
|
// Generate unique ID for this widget based on state address
|
||||||
|
const widget_id: u64 = @intFromPtr(state);
|
||||||
|
|
||||||
|
// Register as focusable in the active focus group
|
||||||
|
ctx.registerFocusable(widget_id);
|
||||||
|
|
||||||
const mouse = ctx.input.mousePos();
|
const mouse = ctx.input.mousePos();
|
||||||
const mouse_pressed = ctx.input.mousePressed(.left);
|
const mouse_pressed = ctx.input.mousePressed(.left);
|
||||||
|
|
||||||
|
|
@ -308,6 +316,7 @@ pub fn tabsRect(
|
||||||
|
|
||||||
// Handle tab click
|
// Handle tab click
|
||||||
if (mouse_pressed and is_hovered and state.close_hovered != @as(i32, @intCast(i))) {
|
if (mouse_pressed and is_hovered and state.close_hovered != @as(i32, @intCast(i))) {
|
||||||
|
ctx.requestFocus(widget_id);
|
||||||
if (state.selected != i) {
|
if (state.selected != i) {
|
||||||
state.selected = i;
|
state.selected = i;
|
||||||
result.changed = true;
|
result.changed = true;
|
||||||
|
|
@ -318,8 +327,12 @@ pub fn tabsRect(
|
||||||
tab_x += @as(i32, @intCast(tab_width));
|
tab_x += @as(i32, @intCast(tab_width));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle keyboard navigation
|
// Check if this widget has focus
|
||||||
if (ctx.input.keyPressed(.left)) {
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
|
state.focused = has_focus;
|
||||||
|
|
||||||
|
// Handle keyboard navigation (only when focused)
|
||||||
|
if (has_focus and ctx.input.keyPressed(.left)) {
|
||||||
// Find previous non-disabled tab
|
// Find previous non-disabled tab
|
||||||
var prev = if (state.selected == 0) tab_list.len - 1 else state.selected - 1;
|
var prev = if (state.selected == 0) tab_list.len - 1 else state.selected - 1;
|
||||||
var attempts: usize = 0;
|
var attempts: usize = 0;
|
||||||
|
|
@ -333,7 +346,7 @@ pub fn tabsRect(
|
||||||
result.selected = prev;
|
result.selected = prev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ctx.input.keyPressed(.right)) {
|
if (has_focus and ctx.input.keyPressed(.right)) {
|
||||||
// Find next non-disabled tab
|
// Find next non-disabled tab
|
||||||
var next = (state.selected + 1) % tab_list.len;
|
var next = (state.selected + 1) % tab_list.len;
|
||||||
var attempts: usize = 0;
|
var attempts: usize = 0;
|
||||||
|
|
|
||||||
|
|
@ -470,19 +470,30 @@ pub fn textAreaRect(
|
||||||
|
|
||||||
if (bounds.isEmpty()) return result;
|
if (bounds.isEmpty()) return result;
|
||||||
|
|
||||||
|
// Generate unique ID for this widget based on buffer memory address
|
||||||
|
const widget_id: u64 = @intFromPtr(state.buffer.ptr);
|
||||||
|
|
||||||
|
// Register as focusable in the active focus group
|
||||||
|
ctx.registerFocusable(widget_id);
|
||||||
|
|
||||||
// Check mouse interaction
|
// Check mouse interaction
|
||||||
const mouse = ctx.input.mousePos();
|
const mouse = ctx.input.mousePos();
|
||||||
const hovered = bounds.contains(mouse.x, mouse.y);
|
const hovered = bounds.contains(mouse.x, mouse.y);
|
||||||
const clicked = hovered and ctx.input.mousePressed(.left);
|
const clicked = hovered and ctx.input.mousePressed(.left);
|
||||||
|
|
||||||
if (clicked) {
|
if (clicked) {
|
||||||
state.focused = true;
|
// Request focus through the focus system
|
||||||
|
ctx.requestFocus(widget_id);
|
||||||
result.clicked = true;
|
result.clicked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this widget has focus
|
||||||
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
|
state.focused = has_focus;
|
||||||
|
|
||||||
// Get colors
|
// Get colors
|
||||||
const bg_color = if (state.focused) colors.background.lighten(5) else colors.background;
|
const bg_color = if (has_focus) colors.background.lighten(5) else colors.background;
|
||||||
const border_color = if (state.focused) colors.border_focused else colors.border;
|
const border_color = if (has_focus) colors.border_focused else colors.border;
|
||||||
|
|
||||||
// Draw background
|
// Draw background
|
||||||
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));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue