Files
docs/versioned_docs/version-0.1.1/reference/extensions/focus.md
T
2025-08-10 14:14:04 -05:00

87 lines
4.4 KiB
Markdown

# `osui::extensions::focus`
The `focus` module provides extensions and components for managing keyboard focus within your OSUI application. It enables navigation between widgets using keyboard input, which is crucial for interactive elements like `Input` fields.
## Components
### `AlwaysFocused` (Component)
```rust
component!(AlwaysFocused);
```
A marker component that, when attached to a widget, ensures that the widget remains focused regardless of user navigation. This is useful for global event handlers or root containers that should always receive input.
**Example:**
```rust
use osui::prelude::*;
rsx! {
@AlwaysFocused;
@Handler::new(|_, e: &crossterm::event::Event| {
// This handler will always receive events.
});
Div { "Global Handler" }
}
```
### `Focused` (Component)
```rust
component!(Focused);
```
A marker component that, when attached to a widget, indicates that this widget should be initially focused when the application starts or when focus is determined. The `RelativeFocusExtension` uses this to set initial focus.
**Example:**
```rust
use osui::prelude::*;
rsx! {
FlexCol {
"Username:"
@Focused; // This Input will be focused by default
Input { }
"Password:"
Input { }
}
}
```
## `RelativeFocusExtension`
This extension manages focus between eligible widgets based on their relative positions on the screen. It allows users to navigate the UI using arrow keys (often combined with Shift).
```rust
pub struct RelativeFocusExtension {
cursor: usize, // Internal index of the currently focused widget
rendered: Arc<Mutex<Vec<(usize, u16, u16)>>>, // Stores widget indices and their (x,y) positions
}
```
### Associated Methods
#### `RelativeFocusExtension::new() -> Self`
Creates a new instance of the `RelativeFocusExtension`.
### `Extension` Trait Implementation
#### `init(&mut self, _ctx: &Context)`
When initialized, it checks existing widgets for the `Focused` component and sets the first one found as focused.
#### `event(&mut self, ctx: &Context, event: &dyn Event)`
Listens for `crossterm::event::Event`s, specifically `KeyCode::Right`, `Left`, `Up`, `Down` when combined with `KeyModifiers::SHIFT`.
When such a key is pressed, it calculates the closest eligible widget in the specified direction based on the `rendered` positions and updates `self.cursor` to that widget's index. It then sets the focus state (`set_focused`) for all widgets accordingly.
Widgets with the `AlwaysFocused` component remain focused.
#### `render(&mut self, _ctx: &Context, _scope: &mut RenderScope)`
Clears the internal `rendered` list at the beginning of each frame. This ensures that widget positions are recalculated based on the latest render.
#### `after_render_widget(&mut self, ctx: &Context, scope: &mut RenderScope, widget: &Arc<Widget>)`
After each widget is rendered, this hook captures its absolute position (`RawTransform.x`, `RawTransform.y`) and its index in the `Screen`'s widget list, storing it in the `rendered` list. This data is then used by the `event` method for focus navigation calculations. It runs in a separate thread for performance.
### How Focus Navigation Works
1. **Position Tracking**: During the `after_render_widget` phase, the `RelativeFocusExtension` records the `(x, y)` coordinates of every non-ghost widget that is rendered.
2. **Event Listening**: When the user presses `Shift + Arrow Key`, the `event` method is triggered.
3. **Closest Widget Calculation**: The `find_closest_in_direction` helper function (internal to the module) determines the next best widget to focus. It prioritizes:
* Widgets directly in the line of the arrow key (same row for left/right, same column for up/down).
* If no direct match, it finds the geographically closest widget in the general direction.
4. **Focus Update**: The extension then updates the `focused` state on widgets using `widget.set_focused(true/false)`. Widgets with the `Focused` component (usually `Input` fields) will then react to subsequent key events.
**Note on `Tab` / `BackTab`**: While `RelativeFocusExtension` handles arrow keys, `Paginator` elements handle `Tab` and `Shift+Tab` internally to cycle through their pages. For general `Tab` navigation across *all* focusable elements in the UI, you would implement a custom `Handler` on a root widget that iterates through widgets and sets focus, or extend `RelativeFocusExtension` to handle `Tab` more generically.