refactor: Unificar sistema de focus - trabajo en progreso
PROBLEMA DETECTADO: - Existían dos sistemas de focus paralelos que no se comunicaban - FocusManager (widgets/focus.zig) usaba IDs u32 - FocusGroupManager (core/focus_group.zig) usaba IDs u64 - Esto causaba que Tab no funcionara y clics no cambiaran focus SOLUCIÓN CONSENSUADA: - Usar SOLO FocusGroupManager como fuente de verdad - Integrar en Context con métodos públicos - Widgets se auto-registran en el grupo activo al dibujarse - Tab navega DENTRO del grupo activo - F6 (u otro) cambia entre grupos/paneles CAMBIOS: - context.zig: Añadidos createFocusGroup(), setActiveFocusGroup(), hasFocus(), requestFocus(), registerFocusable(), handleTabKey() - text_input.zig: Usa @intFromPtr para ID único, se auto-registra - table.zig: Ahora se registra como widget focusable - widgets.zig/zcatgui.zig: Eliminadas referencias antiguas a FocusManager - CLAUDE.md: Documentado el trabajo en progreso ESTADO: EN PROGRESO - Compila pero requiere más testing 🤖 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
88f5a15491
commit
9b6210c76e
7 changed files with 221 additions and 21 deletions
50
CLAUDE.md
50
CLAUDE.md
|
|
@ -685,6 +685,56 @@ cd /mnt/cello2/arno/re/recode/zig/zcatgui
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## TRABAJO EN PROGRESO: UNIFICACIÓN SISTEMA DE FOCUS (2025-12-11)
|
||||||
|
|
||||||
|
### Problema detectado
|
||||||
|
|
||||||
|
zcatgui tenía **dos sistemas de focus paralelos** que no se comunicaban:
|
||||||
|
|
||||||
|
1. **`FocusManager`** en `widgets/focus.zig` - Usaba IDs u32, no integrado con Context
|
||||||
|
2. **`FocusGroupManager`** en `core/focus_group.zig` - Usaba IDs u64, no usado por widgets
|
||||||
|
|
||||||
|
Esto causaba que el Tab no funcionara y los clics no cambiaran el focus correctamente.
|
||||||
|
|
||||||
|
### Solución consensuada
|
||||||
|
|
||||||
|
1. **Eliminar duplicidad**: Usar SOLO `FocusGroupManager` como única fuente de verdad
|
||||||
|
2. **Integrar en Context**: Context expone métodos para crear grupos, registrar widgets, manejar focus
|
||||||
|
3. **Widgets auto-registran**: TextInput, Table, etc. se registran automáticamente en el grupo activo
|
||||||
|
4. **Grupos por panel**: Cada panel de la aplicación tiene su propio grupo de focus
|
||||||
|
|
||||||
|
### Cambios realizados
|
||||||
|
|
||||||
|
**`core/context.zig`**:
|
||||||
|
- Eliminado `FocusManager`, ahora usa `FocusGroupManager`
|
||||||
|
- Añadidos métodos: `createFocusGroup()`, `setActiveFocusGroup()`, `hasFocus()`, `requestFocus()`, `registerFocusable()`, `handleTabKey()`
|
||||||
|
- Los widgets se registran en el grupo activo cuando se dibujan
|
||||||
|
|
||||||
|
**`widgets/text_input.zig`**:
|
||||||
|
- Usa `@intFromPtr(state.buffer.ptr)` para ID único (u64)
|
||||||
|
- Llama `ctx.registerFocusable(widget_id)` al dibujarse
|
||||||
|
- Llama `ctx.requestFocus(widget_id)` al recibir clic
|
||||||
|
- Usa `ctx.hasFocus(widget_id)` para determinar estado visual
|
||||||
|
|
||||||
|
**`widgets/table.zig`**:
|
||||||
|
- Ahora se registra como widget focusable
|
||||||
|
- Usa `@intFromPtr(state)` para ID único
|
||||||
|
|
||||||
|
**`widgets/widgets.zig`** y **`zcatgui.zig`**:
|
||||||
|
- Eliminadas referencias a `focus.zig`, `FocusManager`, `FocusRing`
|
||||||
|
|
||||||
|
### Comportamiento esperado
|
||||||
|
|
||||||
|
- **Tab**: Navega entre widgets DENTRO del grupo de focus activo
|
||||||
|
- **F6** (o similar): Cambia entre grupos de focus (paneles)
|
||||||
|
- **Click**: Activa el grupo que contiene el widget clickeado
|
||||||
|
|
||||||
|
### Estado: EN PROGRESO
|
||||||
|
|
||||||
|
El sistema compila pero requiere más testing y posibles ajustes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## PROYECTOS RELACIONADOS
|
## PROYECTOS RELACIONADOS
|
||||||
|
|
||||||
| Proyecto | Ruta | Descripción |
|
| Proyecto | Ruta | Descripción |
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,24 @@
|
||||||
//! - Command list (draw commands)
|
//! - Command list (draw commands)
|
||||||
//! - Layout state
|
//! - Layout state
|
||||||
//! - ID tracking for widgets
|
//! - ID tracking for widgets
|
||||||
|
//! - Focus management for keyboard navigation
|
||||||
//!
|
//!
|
||||||
//! ## Performance Features
|
//! ## Performance Features
|
||||||
//! - FrameArena for O(1) per-frame allocations
|
//! - FrameArena for O(1) per-frame allocations
|
||||||
//! - Command pooling for zero-allocation hot paths
|
//! - Command pooling for zero-allocation hot paths
|
||||||
//! - Dirty rectangle tracking for minimal redraws
|
//! - Dirty rectangle tracking for minimal redraws
|
||||||
|
//!
|
||||||
|
//! ## Focus Management
|
||||||
|
//! The Context uses FocusGroupManager for organizing widgets into focus groups.
|
||||||
|
//! Each group (typically a panel) contains focusable widgets.
|
||||||
|
//! Tab/Shift+Tab navigates within the active group.
|
||||||
|
//!
|
||||||
|
//! Usage:
|
||||||
|
//! 1. Application creates groups: `ctx.createFocusGroup(group_id)`
|
||||||
|
//! 2. Application sets active group: `ctx.setActiveFocusGroup(group_id)`
|
||||||
|
//! 3. Widgets register themselves: `ctx.registerFocusable(widget_id)` (into active group)
|
||||||
|
//! 4. Widgets check focus: `ctx.hasFocus(widget_id)`
|
||||||
|
//! 5. On click, widgets request focus: `ctx.requestFocus(widget_id)`
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
@ -20,6 +33,9 @@ const Layout = @import("layout.zig");
|
||||||
const Style = @import("style.zig");
|
const Style = @import("style.zig");
|
||||||
const arena_mod = @import("../utils/arena.zig");
|
const arena_mod = @import("../utils/arena.zig");
|
||||||
const FrameArena = arena_mod.FrameArena;
|
const FrameArena = arena_mod.FrameArena;
|
||||||
|
const focus_group = @import("focus_group.zig");
|
||||||
|
const FocusGroup = focus_group.FocusGroup;
|
||||||
|
const FocusGroupManager = focus_group.FocusGroupManager;
|
||||||
|
|
||||||
/// Central context for immediate mode UI
|
/// Central context for immediate mode UI
|
||||||
pub const Context = struct {
|
pub const Context = struct {
|
||||||
|
|
@ -57,6 +73,15 @@ pub const Context = struct {
|
||||||
/// Frame statistics
|
/// Frame statistics
|
||||||
stats: FrameStats,
|
stats: FrameStats,
|
||||||
|
|
||||||
|
/// Focus group manager for keyboard navigation between widgets
|
||||||
|
/// Widgets are organized into groups (typically one per panel)
|
||||||
|
/// Tab navigates within the active group
|
||||||
|
focus_groups: FocusGroupManager,
|
||||||
|
|
||||||
|
/// Tab key state (set by handleTabKey, processed in endFrame)
|
||||||
|
tab_pressed: bool = false,
|
||||||
|
shift_tab_pressed: bool = false,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
/// Frame statistics for performance monitoring
|
/// Frame statistics for performance monitoring
|
||||||
|
|
@ -88,6 +113,7 @@ pub const Context = struct {
|
||||||
.dirty_rects = .{},
|
.dirty_rects = .{},
|
||||||
.full_redraw = true,
|
.full_redraw = true,
|
||||||
.stats = .{},
|
.stats = .{},
|
||||||
|
.focus_groups = FocusGroupManager.init(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,6 +132,7 @@ pub const Context = struct {
|
||||||
.dirty_rects = .{},
|
.dirty_rects = .{},
|
||||||
.full_redraw = true,
|
.full_redraw = true,
|
||||||
.stats = .{},
|
.stats = .{},
|
||||||
|
.focus_groups = FocusGroupManager.init(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,11 +162,27 @@ pub const Context = struct {
|
||||||
self.stats.arena_bytes = 0;
|
self.stats.arena_bytes = 0;
|
||||||
self.stats.dirty_rect_count = 0;
|
self.stats.dirty_rect_count = 0;
|
||||||
|
|
||||||
|
// Note: focus_groups state persists across frames
|
||||||
|
// Tab navigation is processed in endFrame
|
||||||
|
|
||||||
self.frame += 1;
|
self.frame += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// End the current frame
|
/// End the current frame
|
||||||
pub fn endFrame(self: *Self) void {
|
pub fn endFrame(self: *Self) void {
|
||||||
|
// Process Tab/Shift+Tab navigation within active group
|
||||||
|
if (self.tab_pressed or self.shift_tab_pressed) {
|
||||||
|
if (self.focus_groups.getActiveGroup()) |group| {
|
||||||
|
if (self.shift_tab_pressed) {
|
||||||
|
_ = group.focusPrevious();
|
||||||
|
} else {
|
||||||
|
_ = group.focusNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tab_pressed = false;
|
||||||
|
self.shift_tab_pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
self.input.endFrame();
|
self.input.endFrame();
|
||||||
|
|
||||||
// Update final stats
|
// Update final stats
|
||||||
|
|
@ -151,6 +194,91 @@ pub const Context = struct {
|
||||||
self.full_redraw = false;
|
self.full_redraw = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Focus Group Management
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// Create a new focus group (typically one per panel)
|
||||||
|
/// Returns pointer to the group for adding widgets
|
||||||
|
pub fn createFocusGroup(self: *Self, group_id: u64) *FocusGroup {
|
||||||
|
return self.focus_groups.createGroup(group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the active focus group
|
||||||
|
/// Tab navigation will only work within the active group
|
||||||
|
pub fn setActiveFocusGroup(self: *Self, group_id: u64) void {
|
||||||
|
self.focus_groups.setActiveGroup(group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the active focus group
|
||||||
|
pub fn getActiveFocusGroup(self: *Self) ?*FocusGroup {
|
||||||
|
return self.focus_groups.getActiveGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the active group ID
|
||||||
|
pub fn getActiveFocusGroupId(self: *Self) ?u64 {
|
||||||
|
return self.focus_groups.active_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Focus Management Helpers (widget-level)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// Check if a widget has focus
|
||||||
|
/// A widget has focus if it's the focused widget in the active group
|
||||||
|
pub fn hasFocus(self: *Self, widget_id: u64) bool {
|
||||||
|
if (self.focus_groups.getActiveGroup()) |group| {
|
||||||
|
return group.hasFocus(widget_id);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request focus for a widget (e.g., when clicked)
|
||||||
|
/// This also activates the group containing the widget
|
||||||
|
pub fn requestFocus(self: *Self, widget_id: u64) void {
|
||||||
|
// Find which group contains this widget and activate it
|
||||||
|
for (self.focus_groups.groups[0..self.focus_groups.group_count]) |*group| {
|
||||||
|
if (group.setFocus(widget_id)) {
|
||||||
|
self.focus_groups.active_group = group.id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a widget as focusable in the active group
|
||||||
|
/// Call this during draw for each focusable widget
|
||||||
|
pub fn registerFocusable(self: *Self, widget_id: u64) void {
|
||||||
|
if (self.focus_groups.getActiveGroup()) |group| {
|
||||||
|
// Only add if not already in the group
|
||||||
|
if (group.indexOf(widget_id) == null) {
|
||||||
|
group.add(widget_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process Tab key for focus navigation
|
||||||
|
/// Call this when Tab is pressed in the input handler
|
||||||
|
pub fn handleTabKey(self: *Self, shift: bool) void {
|
||||||
|
if (shift) {
|
||||||
|
self.shift_tab_pressed = true;
|
||||||
|
} else {
|
||||||
|
self.tab_pressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a group contains the currently focused widget
|
||||||
|
/// Useful for panels to know if they should show focus highlight
|
||||||
|
pub fn groupHasFocus(self: *Self, group_id: u64) bool {
|
||||||
|
if (self.focus_groups.active_group) |active_id| {
|
||||||
|
if (active_id == group_id) {
|
||||||
|
if (self.focus_groups.getGroup(group_id)) |group| {
|
||||||
|
return group.getFocused() != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the frame allocator (use for per-frame allocations)
|
/// Get the frame allocator (use for per-frame allocations)
|
||||||
pub fn frameAllocator(self: *Self) Allocator {
|
pub fn frameAllocator(self: *Self) Allocator {
|
||||||
return self.frame_arena.allocator();
|
return self.frame_arena.allocator();
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,12 @@ pub const FocusManager = struct {
|
||||||
|
|
||||||
/// Reset for new frame
|
/// Reset for new frame
|
||||||
pub fn beginFrame(self: *Self) void {
|
pub fn beginFrame(self: *Self) void {
|
||||||
|
// Reset focusable list - widgets will re-register during draw
|
||||||
self.focusable_count = 0;
|
self.focusable_count = 0;
|
||||||
self.tab_pressed = false;
|
// Note: tab_pressed/shift_tab_pressed are NOT reset here
|
||||||
self.shift_tab_pressed = false;
|
// They persist from the event loop and are processed in endFrame()
|
||||||
|
|
||||||
// Apply pending focus
|
// Apply pending focus from previous frame's Tab navigation
|
||||||
if (self.pending_focus) |id| {
|
if (self.pending_focus) |id| {
|
||||||
self.focused_id = id;
|
self.focused_id = id;
|
||||||
self.pending_focus = null;
|
self.pending_focus = null;
|
||||||
|
|
@ -78,13 +79,22 @@ pub const FocusManager = struct {
|
||||||
|
|
||||||
/// End of frame: process Tab navigation
|
/// End of frame: process Tab navigation
|
||||||
pub fn endFrame(self: *Self) void {
|
pub fn endFrame(self: *Self) void {
|
||||||
if (self.focusable_count == 0) return;
|
if (self.focusable_count == 0) {
|
||||||
|
// Reset flags even if no focusables
|
||||||
|
self.tab_pressed = false;
|
||||||
|
self.shift_tab_pressed = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (self.tab_pressed) {
|
if (self.tab_pressed) {
|
||||||
self.focusNext();
|
self.focusNext();
|
||||||
} else if (self.shift_tab_pressed) {
|
} else if (self.shift_tab_pressed) {
|
||||||
self.focusPrev();
|
self.focusPrev();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset flags after processing
|
||||||
|
self.tab_pressed = false;
|
||||||
|
self.shift_tab_pressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Focus next widget in order
|
/// Focus next widget in order
|
||||||
|
|
|
||||||
|
|
@ -674,14 +674,24 @@ pub fn tableRectFull(
|
||||||
|
|
||||||
if (bounds.isEmpty() or columns.len == 0) return result;
|
if (bounds.isEmpty() or columns.len == 0) return result;
|
||||||
|
|
||||||
|
// Generate unique ID for this table 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 table_hovered = bounds.contains(mouse.x, mouse.y);
|
const table_hovered = bounds.contains(mouse.x, mouse.y);
|
||||||
|
|
||||||
// Click for focus
|
// Click for focus - use the new focus system
|
||||||
if (table_hovered and ctx.input.mousePressed(.left)) {
|
if (table_hovered and ctx.input.mousePressed(.left)) {
|
||||||
state.focused = true;
|
ctx.requestFocus(widget_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this table has focus (via focus group system)
|
||||||
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
|
state.focused = has_focus;
|
||||||
|
|
||||||
// Calculate dimensions
|
// Calculate dimensions
|
||||||
const header_h = if (config.show_headers) config.header_height else 0;
|
const header_h = if (config.show_headers) config.header_height else 0;
|
||||||
const state_col_w = if (config.show_state_indicators) config.state_indicator_width else 0;
|
const state_col_w = if (config.show_state_indicators) config.state_indicator_width else 0;
|
||||||
|
|
|
||||||
|
|
@ -248,8 +248,11 @@ pub fn textInputRect(
|
||||||
|
|
||||||
if (bounds.isEmpty()) return result;
|
if (bounds.isEmpty()) return result;
|
||||||
|
|
||||||
const id = ctx.getId(state.buffer.ptr[0..1]);
|
// Generate unique ID for this widget based on buffer memory address
|
||||||
_ = id;
|
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();
|
||||||
|
|
@ -257,14 +260,21 @@ pub fn textInputRect(
|
||||||
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 - this also activates the group containing this widget
|
||||||
|
ctx.requestFocus(widget_id);
|
||||||
result.clicked = true;
|
result.clicked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this widget has focus (is focused widget in active group)
|
||||||
|
const has_focus = ctx.hasFocus(widget_id);
|
||||||
|
|
||||||
|
// Sync state.focused for backwards compatibility
|
||||||
|
state.focused = has_focus;
|
||||||
|
|
||||||
// Theme colors
|
// Theme colors
|
||||||
const theme = Style.Theme.dark;
|
const theme = Style.Theme.dark;
|
||||||
const bg_color = if (state.focused) theme.input_bg.lighten(5) else theme.input_bg;
|
const bg_color = if (has_focus) theme.input_bg.lighten(5) else theme.input_bg;
|
||||||
const border_color = if (state.focused) theme.primary else theme.input_border;
|
const border_color = if (has_focus) theme.primary else theme.input_border;
|
||||||
const text_color = theme.input_fg;
|
const text_color = theme.input_fg;
|
||||||
const placeholder_color = theme.secondary;
|
const placeholder_color = theme.secondary;
|
||||||
|
|
||||||
|
|
@ -279,7 +289,7 @@ pub fn textInputRect(
|
||||||
if (inner.isEmpty()) return result;
|
if (inner.isEmpty()) return result;
|
||||||
|
|
||||||
// Handle keyboard input if focused
|
// Handle keyboard input if focused
|
||||||
if (state.focused and !config.readonly) {
|
if (has_focus and !config.readonly) {
|
||||||
// Handle special keys (navigation, deletion)
|
// Handle special keys (navigation, deletion)
|
||||||
for (ctx.input.key_events[0..ctx.input.key_event_count]) |key_event| {
|
for (ctx.input.key_events[0..ctx.input.key_event_count]) |key_event| {
|
||||||
if (key_event.pressed) {
|
if (key_event.pressed) {
|
||||||
|
|
@ -342,7 +352,7 @@ pub fn textInputRect(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw cursor if focused
|
// Draw cursor if focused
|
||||||
if (state.focused and !config.readonly) {
|
if (has_focus and !config.readonly) {
|
||||||
const char_width: u32 = 8;
|
const char_width: u32 = 8;
|
||||||
const cursor_x = inner.x + @as(i32, @intCast(state.cursor * char_width));
|
const cursor_x = inner.x + @as(i32, @intCast(state.cursor * char_width));
|
||||||
const cursor_color = theme.foreground;
|
const cursor_color = theme.foreground;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ pub const text_input = @import("text_input.zig");
|
||||||
pub const checkbox = @import("checkbox.zig");
|
pub const checkbox = @import("checkbox.zig");
|
||||||
pub const select = @import("select.zig");
|
pub const select = @import("select.zig");
|
||||||
pub const list = @import("list.zig");
|
pub const list = @import("list.zig");
|
||||||
pub const focus = @import("focus.zig");
|
|
||||||
pub const table = @import("table.zig");
|
pub const table = @import("table.zig");
|
||||||
pub const split = @import("split.zig");
|
pub const split = @import("split.zig");
|
||||||
pub const panel = @import("panel.zig");
|
pub const panel = @import("panel.zig");
|
||||||
|
|
@ -99,11 +98,6 @@ pub const ListState = list.ListState;
|
||||||
pub const ListConfig = list.ListConfig;
|
pub const ListConfig = list.ListConfig;
|
||||||
pub const ListResult = list.ListResult;
|
pub const ListResult = list.ListResult;
|
||||||
|
|
||||||
// Focus
|
|
||||||
pub const Focus = focus;
|
|
||||||
pub const FocusManager = focus.FocusManager;
|
|
||||||
pub const FocusRing = focus.FocusRing;
|
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
pub const Table = table;
|
pub const Table = table;
|
||||||
pub const TableState = table.TableState;
|
pub const TableState = table.TableState;
|
||||||
|
|
|
||||||
|
|
@ -244,8 +244,6 @@ pub const list = widgets.list.list;
|
||||||
pub const listEx = widgets.list.listEx;
|
pub const listEx = widgets.list.listEx;
|
||||||
pub const ListState = widgets.ListState;
|
pub const ListState = widgets.ListState;
|
||||||
|
|
||||||
pub const FocusManager = widgets.FocusManager;
|
|
||||||
pub const FocusRing = widgets.FocusRing;
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Re-exports for convenience
|
// Re-exports for convenience
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue