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)
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:
use osui::prelude::*;
rsx! {
@AlwaysFocused;
@Handler::new(|_, e: &crossterm::event::Event| {
// This handler will always receive events.
});
Div { "Global Handler" }
}
Focused
(Component)
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:
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).
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
- Position Tracking: During the
after_render_widget
phase, theRelativeFocusExtension
records the(x, y)
coordinates of every non-ghost widget that is rendered. - Event Listening: When the user presses
Shift + Arrow Key
, theevent
method is triggered. - 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.
- Focus Update: The extension then updates the
focused
state on widgets usingwidget.set_focused(true/false)
. Widgets with theFocused
component (usuallyInput
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.