Version 0.1.1
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# `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.
|
||||
@@ -0,0 +1,78 @@
|
||||
# `osui::extensions::id`
|
||||
|
||||
The `id` module provides a simple way to assign unique identifiers to OSUI widgets and retrieve them later by that ID. This is useful for direct access to specific UI elements for manipulation or querying.
|
||||
|
||||
## `Id` (Component)
|
||||
|
||||
```rust
|
||||
component!(Id(pub usize));
|
||||
```
|
||||
A tuple struct component that holds a `usize` value, representing a unique identifier for the widget it's attached to.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@Id(101); // Assign ID 101 to this Div
|
||||
Div { "My uniquely identified div" }
|
||||
}
|
||||
```
|
||||
|
||||
## `IdExtension`
|
||||
|
||||
The `IdExtension` provides functionality to look up widgets by their `Id` component.
|
||||
|
||||
```rust
|
||||
pub struct IdExtension(pub Arc<Screen>);
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `IdExtension::new(screen: Arc<Screen>) -> Arc<Self>`
|
||||
Creates a new `IdExtension` instance. It requires an `Arc<Screen>` because it needs to access the screen's list of widgets to perform ID lookups.
|
||||
|
||||
**Arguments:**
|
||||
* `screen`: An `Arc` to the main `Screen` instance.
|
||||
|
||||
**Returns:**
|
||||
An `Arc<IdExtension>`. It's recommended to store extensions that are queried by other parts of your app in an `Arc` so they can be easily shared.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
let id_ext = IdExtension::new(screen.clone());
|
||||
screen.extension(id_ext.clone()); // Register the extension
|
||||
```
|
||||
|
||||
#### `IdExtension::get_element(self: &Arc<IdExtension>, id: usize) -> Option<Arc<Widget>>`
|
||||
Retrieves an `Arc<Widget>` from the `Screen`'s widget list that has an `Id` component matching the provided `id`.
|
||||
|
||||
**Arguments:**
|
||||
* `id`: The `usize` identifier to search for.
|
||||
|
||||
**Returns:**
|
||||
`Some(Arc<Widget>)` if a widget with the matching ID is found, `None` otherwise.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
let id_ext = IdExtension::new(screen.clone());
|
||||
screen.extension(id_ext.clone());
|
||||
|
||||
rsx! {
|
||||
@Id(100);
|
||||
Div { "Hello, ID 100!" }
|
||||
}.draw(&screen);
|
||||
|
||||
// Later, perhaps in an event handler or another part of your app:
|
||||
if let Some(widget_100) = id_ext.get_element(100) {
|
||||
// You can now interact with widget_100 directly, e.g., get its transform, set components etc.
|
||||
// let transform: Transform = widget_100.get().unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
### `Extension` Trait Implementation
|
||||
|
||||
`IdExtension` currently has an empty implementation for the `Extension` trait (`impl Extension for Arc<IdExtension> {}`). This means it doesn't hook into any lifecycle events or event dispatching by default. Its primary purpose is to provide the `get_element` lookup method, which is invoked manually when needed.
|
||||
@@ -0,0 +1,75 @@
|
||||
# `osui::extensions::input_handling`
|
||||
|
||||
The `input_handling` module provides the `InputExtension`, which integrates `crossterm` for low-level terminal input and dispatches these events throughout the OSUI system. This is a fundamental extension required for any interactive OSUI application.
|
||||
|
||||
## `InputExtension`
|
||||
|
||||
Manages raw terminal input and dispatches `crossterm` events.
|
||||
|
||||
```rust
|
||||
pub struct InputExtension;
|
||||
```
|
||||
|
||||
### `Extension` Trait Implementation
|
||||
|
||||
#### `init(&mut self, ctx: &Context)`
|
||||
Called when the extension is initialized.
|
||||
1. Enables `crossterm`'s raw mode (`crossterm::terminal::enable_raw_mode().unwrap()`). Raw mode allows direct, unbuffered input capture, essential for TUI applications.
|
||||
2. Spawns a new thread. This thread continuously listens for `crossterm::event::read()` events.
|
||||
3. Whenever an event is read successfully, it dispatches the event using `ctx.event(&e)`. This makes the `crossterm::event::Event` available to all widgets (via `Element::event` or `Handler<crossterm::event::Event>` components) and other extensions.
|
||||
|
||||
**Why a separate thread?**
|
||||
Reading input from the terminal (`crossterm::event::read()`) is a blocking operation. Spawning it in a separate thread prevents the main rendering loop from freezing while waiting for user input, ensuring the UI remains responsive.
|
||||
|
||||
#### `on_close(&mut self)`
|
||||
Called when `Screen::close()` is invoked.
|
||||
1. Disables `crossterm`'s raw mode (`crossterm::terminal::disable_raw_mode().unwrap()`). This restores the terminal to its normal, buffered input state, which is crucial for a clean exit.
|
||||
|
||||
### `Event` Trait Implementation
|
||||
|
||||
The `crossterm::event::Event` enum itself implements OSUI's `Event` trait, allowing `InputExtension` to dispatch it directly.
|
||||
|
||||
```rust
|
||||
impl crate::extensions::Event for crossterm::event::Event {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
This means you can easily listen for `crossterm` events in your widgets or other extensions using `Handler<crossterm::event::Event>` or by downcasting the `dyn Event` in an `Extension::event` method.
|
||||
|
||||
## Usage
|
||||
|
||||
You must register the `InputExtension` with your `Screen` for keyboard and mouse input to work in your OSUI application.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
|
||||
// Register the InputExtension
|
||||
screen.extension(InputExtension);
|
||||
|
||||
// Other extensions and UI setup
|
||||
screen.extension(RelativeFocusExtension::new()); // Often used with InputExtension for navigation
|
||||
|
||||
rsx! {
|
||||
// A global event handler to exit on Escape key, relying on InputExtension
|
||||
@Handler::new({
|
||||
let screen = screen.clone();
|
||||
move |_, e: &crossterm::event::Event| {
|
||||
if let crossterm::event::Event::Key(crossterm::event::KeyEvent { code: crossterm::event::KeyCode::Esc, .. }) = e {
|
||||
screen.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
// An Input widget that will receive key events from InputExtension
|
||||
Input { }
|
||||
}.draw(&screen);
|
||||
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
`InputExtension` is a foundational component for building interactive terminal user interfaces with OSUI.
|
||||
@@ -0,0 +1,93 @@
|
||||
# `osui::extensions::tick`
|
||||
|
||||
The `tick` module provides the `TickExtension` and `TickEvent`, enabling applications to receive periodic updates at a defined rate. This is useful for animations, game loops, or any time-based logic.
|
||||
|
||||
## `TickEvent` (Event)
|
||||
|
||||
```rust
|
||||
event!(TickEvent(pub u32));
|
||||
```
|
||||
A custom event type dispatched by the `TickExtension`. It contains a `u32` value representing the current tick count since the extension started.
|
||||
|
||||
**Fields:**
|
||||
* `0`: `u32` - The current tick count.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
// Listen for TickEvent
|
||||
rsx! {
|
||||
@Handler::new(|_, e: &TickEvent| {
|
||||
println!("Tick: {}", e.0);
|
||||
});
|
||||
Div { "Listening for ticks" }
|
||||
}
|
||||
```
|
||||
|
||||
## `TickExtension`
|
||||
|
||||
Dispatches `TickEvent`s at a configurable interval.
|
||||
|
||||
```rust
|
||||
pub struct TickExtension(pub u16);
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
* `0`: `u16` - The desired ticks per second (Hz). For example, `TickExtension(30)` would dispatch a `TickEvent` approximately every 33 milliseconds (1000ms / 30Hz).
|
||||
|
||||
### `Extension` Trait Implementation
|
||||
|
||||
#### `init(&mut self, ctx: &Context)`
|
||||
Called when the `TickExtension` is registered with the `Screen`.
|
||||
1. Calculates the `rate_dur` (duration per tick) in milliseconds: `1000 / self.0 as u64`.
|
||||
2. Spawns a new thread.
|
||||
3. In this new thread, it enters an infinite loop:
|
||||
* It dispatches a `TickEvent` with the current tick count using `ctx.event(&TickEvent(tick))`.
|
||||
* The `tick` counter is incremented.
|
||||
* The thread sleeps for the calculated `rate_dur`.
|
||||
|
||||
**Why a separate thread?**
|
||||
Similar to `InputExtension`, `TickExtension` spawns a separate thread because `std::thread::sleep` is a blocking operation. This ensures that the main rendering loop continues to run smoothly, independent of the tick rate.
|
||||
|
||||
## Usage
|
||||
|
||||
To use `TickExtension`, you simply need to create an instance with your desired tick rate and register it with the `Screen`. Then, your widgets or other extensions can listen for `TickEvent`s using `Handler<TickEvent>`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
|
||||
// Register essential extensions
|
||||
screen.extension(InputExtension);
|
||||
screen.extension(RelativeFocusExtension::new());
|
||||
|
||||
// Register TickExtension for 30 ticks per second
|
||||
screen.extension(TickExtension(30));
|
||||
|
||||
// Create a state to display the tick count
|
||||
let tick_count_state = use_state(0u32);
|
||||
|
||||
rsx! {
|
||||
// Attach a Handler to update the state on each TickEvent
|
||||
@Handler::new({
|
||||
let state_clone = tick_count_state.clone();
|
||||
move |_, e: &TickEvent| {
|
||||
state_clone.set(e.0); // Update the state with the current tick count
|
||||
}
|
||||
});
|
||||
// Display the reactive tick count
|
||||
%tick_count_state
|
||||
Div {
|
||||
format!("Current Tick: {}", tick_count_state.get())
|
||||
}
|
||||
}
|
||||
.draw(&screen);
|
||||
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
This example demonstrates how `TickExtension` provides a consistent timing mechanism, allowing you to build dynamic and animated UIs.
|
||||
@@ -0,0 +1,92 @@
|
||||
# `osui::extensions::velocity`
|
||||
|
||||
The `velocity` module provides the `VelocityExtension` and `Velocity` component, enabling simple animation by automatically moving widgets with a constant velocity.
|
||||
|
||||
## `Velocity` (Component)
|
||||
|
||||
```rust
|
||||
component!(Velocity(pub i32, pub i32));
|
||||
```
|
||||
A tuple struct component that holds two `i32` values, representing the horizontal (`x`) and vertical (`y`) velocity of a widget. The values represent "cells per second" for movement.
|
||||
|
||||
**Fields:**
|
||||
* `0`: `i32` - Horizontal velocity (cells per second). Positive moves right, negative moves left.
|
||||
* `1`: `i32` - Vertical velocity (cells per second). Positive moves down, negative moves up.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@Velocity(10, 0); // Move 10 cells right per second
|
||||
@Transform::new().x(0).y(0);
|
||||
Div { "Moving Right" }
|
||||
}
|
||||
```
|
||||
|
||||
## `VelocityExtension`
|
||||
|
||||
This extension is responsible for applying the specified `Velocity` to widgets that also have a `Transform` component, causing them to move across the screen.
|
||||
|
||||
```rust
|
||||
pub struct VelocityExtension;
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `VelocityExtension::apply_velocity(ticks: u16, velocity: i32, x: &mut u16)`
|
||||
An internal helper function that updates a single coordinate (`x` or `y`) based on the elapsed `ticks` and `velocity`. It ensures that movement occurs at the specified `velocity` (cells per second) by checking if enough time has passed since the last movement.
|
||||
|
||||
#### `VelocityExtension::apply_velocity_xy(ticks: u16, widget: &Arc<Widget>)`
|
||||
An internal helper that applies `Velocity` to both `x` and `y` coordinates of a widget's `Transform` component. It retrieves the `Velocity` and `Transform` components, calculates new positions using `apply_velocity`, and then updates the `Transform` component on the widget.
|
||||
|
||||
### `Extension` Trait Implementation
|
||||
|
||||
#### `init(&mut self, ctx: &super::Context)`
|
||||
Called when the extension is initialized.
|
||||
1. Clones the `Context`.
|
||||
2. Spawns a new thread.
|
||||
3. In this thread, it enters an infinite loop:
|
||||
* Initializes a `tick` counter (0 to 1000, then resets). This acts as a granular time counter.
|
||||
* Iterates over all widgets managed by the `Screen` (obtained via `ctx.get_widgets()`).
|
||||
* For each widget, it calls `Self::apply_velocity_xy(tick, widget)` to update its position if it has a `Velocity` and `Transform` component.
|
||||
* The thread sleeps for 1 millisecond. This ensures very frequent checks and smooth potential movement, as `velocity` is defined in "cells per second".
|
||||
|
||||
**Why a separate thread?**
|
||||
Similar to `InputExtension` and `TickExtension`, a separate thread is used because time-based operations like `thread::sleep` are blocking. This prevents the velocity calculations from blocking the main rendering loop and ensures smooth animations. The 1ms sleep provides a high refresh rate for velocity updates.
|
||||
|
||||
## Usage
|
||||
|
||||
To use `VelocityExtension`, you need to:
|
||||
1. Register it with your `Screen`.
|
||||
2. Attach both a `Transform` and a `Velocity` component to the widget you want to animate. The `Transform` should have `Position::Const` for the coordinates you want to animate.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
|
||||
screen.extension(InputExtension); // Required for general terminal operations
|
||||
screen.extension(RelativeFocusExtension::new()); // Optional, but good practice
|
||||
screen.extension(VelocityExtension); // Register the VelocityExtension
|
||||
|
||||
rsx! {
|
||||
// A Div that moves right at 10 cells/second
|
||||
@Velocity(10, 0);
|
||||
@Transform::new().x(0).y(0).dimensions(10, 1); // Must have Const position to be moved
|
||||
@Style { background: Background::Solid(0xFF0000) };
|
||||
Div { "Moving Text" }
|
||||
|
||||
// A Div that moves down at 5 cells/second, starting below the first
|
||||
@Velocity(0, 5);
|
||||
@Transform::new().x(0).y(2).dimensions(10, 1);
|
||||
@Style { background: Background::Solid(0x0000FF) };
|
||||
Div { "Moving Down" }
|
||||
}
|
||||
.draw(&screen);
|
||||
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
`VelocityExtension` provides a simple, component-based way to add continuous motion to your OSUI elements. For more complex animations, you might combine it with `TickExtension` and custom logic within a widget's `event` method or a more advanced animation extension.
|
||||
Reference in New Issue
Block a user