Version 0.1.1

This commit is contained in:
2025-08-10 14:14:04 -05:00
parent 54b9cba486
commit b4891d4795
39 changed files with 4587 additions and 0 deletions
@@ -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.