v0.1.0 with spark

This commit is contained in:
2025-08-03 13:05:42 -05:00
parent 4bf347d26c
commit d35cc6408d
28 changed files with 4057 additions and 0 deletions
@@ -0,0 +1,158 @@
---
slug: /
---
# Getting Started with OSUI
This guide will walk you through setting up your first OSUI project and running a basic application.
## Prerequisites
Before you begin, ensure you have:
* **Rust and Cargo**: If you don't have Rust installed, you can get it from [rustup.rs](https://rustup.rs/). OSUI requires a recent stable version of Rust.
## 1. Create a New Cargo Project
First, create a new Rust binary project:
```bash
cargo new my_osui_app --bin
cd my_osui_app
```
## 2. Add OSUI to Your Dependencies
Open your `Cargo.toml` file and add `osui` to your `[dependencies]` section. We also recommend adding `crossterm` if you plan to handle raw terminal events directly, though OSUI uses it internally.
```toml
# Cargo.toml
[package]
name = "my_osui_app"
version = "0.1.0"
edition = "2021"
[dependencies]
osui = "0.1" # Use the latest version from crates.io
crossterm = "0.28" # Required for input handling, OSUI uses it internally
figlet-rs = "0.1" # Used by the Heading element, can be excluded if not needed
```
> **Note**: Always check [crates.io/crates/osui](https://crates.io/crates/osui) for the latest available version.
## 3. Write Your First OSUI Application
Now, open `src/main.rs` and replace its contents with the following code. This example sets up a basic screen, adds an input handler (using an `Extension`), and displays a simple "Hello, OSUI!" message. It also includes a paginator with multiple "pages" to demonstrate basic navigation.
```rust
// src/main.rs
use osui::prelude::*;
fn main() -> std::io::Result<()> {
// Create the main Screen instance.
// The Screen manages the rendering loop, widgets, and extensions.
let screen = Screen::new();
// Register the InputExtension.
// This extension enables raw mode and dispatches keyboard events to widgets.
// Without it, keyboard input (like 'q' to quit or Tab for paginator) won't work.
screen.extension(InputExtension);
// Create a dynamic state variable for a counter.
// This demonstrates OSUI's reactivity.
let count = use_state(0);
// Spawn a background thread to increment the counter every second.
// This will cause the associated widget to re-render automatically.
std::thread::spawn({
let count = count.clone(); // Clone the Arc for the thread
move || loop {
// Dereference the MutexGuard and modify the inner value.
// This also marks the state as 'changed'.
**count.get() += 1;
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
// Define the UI using the `rsx!` macro.
// This is the declarative way to build your UI tree.
rsx! {
// Attach a Handler component to the root widget for key events.
// This handler closes the screen when 'q' is pressed.
@Handler::new({
let screen = screen.clone(); // Clone the Arc for the closure
move |_, e: &crossterm::event::Event| {
if let crossterm::event::Event::Key(crossterm::event::KeyEvent { code, .. }) = e {
if *code == crossterm::event::KeyCode::Char('q') {
screen.close(); // Close the screen, exiting the main loop
}
}
}});
// Paginator is an element that displays one child at a time.
// Press Tab/Shift+Tab to cycle through its children.
Paginator {
// First page: A FlexRow container with a heading and text.
FlexRow {
Heading, smooth: false, { "OSUI" } // A large ASCII art heading
"Welcome to the OSUI demo!"
"Press tab to switch to the next page or shift+tab to the previous page"
}
// Second page: A FlexCol container with two Divs, demonstrating styling.
FlexCol, gap: 3, {
// Attach Transform and Style components directly to the Div.
@Transform::new().padding(2, 2);
@Style { foreground: None, background: Background::RoundedOutline(0x00ff00) };
Div {
"This is text inside a div"
}
@Transform::new().padding(2, 2);
@Style { foreground: None, background: Background::Outline(0x00ff00) };
Div {
"This is text inside a div with square outlines"
}
}
// Third page: Another FlexCol with a reactive counter and an Input element.
FlexCol, gap: 2, {
@transform!{ y: Center }; // Custom macro for convenient Transform creation
static Div { // `static` keyword means this Div itself is static, but its children can be dynamic.
%count // The '%' symbol indicates a dependency on the 'count' state.
"This will increment every second: {count}" // 'count' will be automatically updated.
}
// An interactive Input field.
@Transform::new().padding(1, 1).dimensions(40, 1);
@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xff0000) };
Input { }
}
}
}
// Draw the entire RSX tree onto the screen.
.draw(&screen);
// Start the main event loop and rendering.
screen.run()
}
```
## 4. Run Your Application
From your project's root directory, run:
```bash
cargo run
```
You should see a terminal application launch, displaying the "OSUI" heading and welcome message.
* Press `Tab` to navigate through the pages.
* Press `Shift+Tab` to go back.
* Observe the counter incrementing on the third page.
* Interact with the input field on the third page.
* Press `q` to quit the application.
Congratulations! You've successfully set up and run your first OSUI application.
@@ -0,0 +1,47 @@
# OSUI: A Rust Terminal User Interface Library
OSUI (Operating System User Interface) is a powerful and flexible library for building interactive and customizable Terminal User Interfaces (TUIs) in Rust. It provides a declarative component system inspired by modern web frameworks, real-time keyboard input handling, and a virtual screen abstraction to simplify TUI development.
## Key Features
* **Declarative UI with `rsx!`**: Define your UI structure using an intuitive, JSX-like macro that allows for nesting, component composition, and reactive updates.
* **Component-Based Design**: Build complex UIs from reusable `Element`s and extend their functionality with `Component`s, promoting modularity and maintainability.
* **Reactive State Management**: Integrate dynamic behavior effortlessly with the `State` system, automatically re-rendering parts of your UI when underlying data changes.
* **Flexible Layout System**: Control element positioning and sizing with `Transform`, `Position`, and `Dimension` properties, supporting both fixed and content-based layouts.
* **Extensible Architecture**: Customize or extend OSUI's core behavior by implementing the `Extension` trait, allowing you to add global event handling, custom rendering logic, and more.
* **Virtual Screen Abstraction**: OSUI manages the complexities of terminal rendering, providing a consistent API for drawing text, shapes, and applying styles across different terminal environments.
* **Real-time Input Handling**: Built-in support for capturing and dispatching keyboard events, enabling interactive applications.
## Quick Example
The following example demonstrates a minimal OSUI application that displays "Hello, World!" on the terminal.
```rust
use osui::prelude::*;
fn main() -> std::io::Result<()> {
// 1. Create a new Screen instance, which manages the TUI environment.
let screen = Screen::new();
// 2. Define your UI using the `rsx!` macro.
// Here, a simple string "Hello, World!" becomes a renderable element.
rsx! {
"👋 Hello, World!"
}
// 3. Draw the constructed UI tree onto the screen.
.draw(&screen);
// 4. Run the main rendering loop. This will block until the application is closed.
screen.run()
}
```
This simple application initializes the `Screen`, defines a basic text element using `rsx!`, draws it, and then enters the main rendering loop.
## Philosophy
OSUI aims to provide a high-level, ergonomic API for TUI development, abstracting away the low-level details of terminal interaction. By embracing a component-based and reactive paradigm, it encourages developers to build robust and interactive command-line applications with a familiar development experience, similar to modern graphical UI frameworks.
For more detailed guides and API references, explore the rest of the documentation.
@@ -0,0 +1,263 @@
# Building UIs with RSX
OSUI leverages a declarative syntax, similar to JSX in web development, to define your UI elements. This is primarily facilitated by the `rsx!` macro. This guide explains how to use `rsx!` to construct your UI tree, manage properties, attach components, and handle reactive updates.
## The `rsx!` Macro: Declarative UI Definition
The `rsx!` macro provides a concise way to declare a hierarchy of UI elements. It processes a block of UI definitions and expands them into an `Rsx` object, which can then be drawn onto the `Screen`.
A basic `rsx!` block looks like this:
```rust
use osui::prelude::*;
fn my_ui_function(my_state: State<String>) -> Rsx {
rsx! {
// A simple string is treated as a text element
"Hello, RSX!"
// An element with properties and children
Div {
// Another text element
"I am a child of the Div."
}
// A dynamic element with a state dependency
%my_state
Div {
"Current state value: {my_state}"
}
}
}
```
### Basic Element Types
1. **Text Literals**: A string literal directly within `rsx!` creates a simple text element. OSUI automatically converts `String` and `(String, u32)` (for colored text) into renderable `Element`s.
```rust
rsx! {
"This is plain text."
("This text is colored", 0xFF00FF) // Pink text
}
```
2. **Built-in Elements**: OSUI provides several pre-defined elements like `Div`, `FlexRow`, `FlexCol`, `Input`, `Paginator`, and `Heading`. You reference them by their struct name.
```rust
rsx! {
Div {
"Content inside a div."
}
FlexCol, gap: 1, {
"Item 1"
"Item 2"
}
}
```
## Properties and Configuration
Elements can be configured by setting their public fields. This is done by listing the field names and their values after the element type, separated by commas.
```rust
rsx! {
// Setting `gap` for FlexRow
FlexRow, gap: 2, {
"First item"
"Second item"
}
// Setting `smooth` for Heading
Heading, smooth: true, { "My Title" }
}
```
If an element has no properties to set, or you are using default values, you can omit the property list:
```rust
rsx! {
Div { "No custom properties needed here." }
}
```
## Nesting Elements (Children)
Elements can contain other elements as children. This creates the UI tree. Children are defined within curly braces `{}` immediately following the element declaration.
```rust
rsx! {
Div {
"Parent Div"
Div { // Nested Div
"Child Div"
"Another child"
}
FlexCol { // Another child, a FlexCol
"Flex item 1"
"Flex item 2"
}
}
}
```
## Attaching Components
Components are Rust structs that implement the `Component` trait. They can be attached to any widget to extend its behavior or provide additional data (like styling or transformation). In `rsx!`, components are attached using the `@` prefix.
```rust
use osui::prelude::*;
// Assume MyComponent is defined via `component!(MyComponent { /* ... */ });`
// and Transform and Style are imported from `osui::style`.
rsx! {
// Attach a Transform component to control position/size
@Transform::new().center().padding(1, 1);
// Attach a Style component for background and foreground colors
@Style { background: Background::Solid(0x333333), foreground: Some(0xFFFFFF) };
Div {
"This div is centered, padded, and has a dark background with white text."
}
// Attach a custom component
@MyComponent { my_prop: "value".to_string() };
Div {
"This div has MyComponent attached."
}
}
```
You can attach multiple components to a single element. They are applied in the order they are declared.
## Dynamic vs. Static Widgets
OSUI distinguishes between static and dynamic widgets for performance and reactivity.
### Static Widgets (`static` keyword)
A widget declared with the `static` keyword means that the root `Element` instance itself will be created only once. Its children, however, can still be dynamic. This is suitable for parts of your UI that do not change their fundamental structure or the root `Element` type.
```rust
rsx! {
static Heading, smooth: true, { "Static Title" }
static Div { "This div's root element is static." }
}
```
* **When to use `static`**: For elements whose `Element` trait implementation doesn't change and doesn't depend on external reactive state to rebuild itself. This can include simple text, static containers, or complex elements whose internal state is managed purely by their own logic (not OSUI's `State` system).
* **Performance**: Generally more performant as they avoid re-evaluating the element creation closure on every refresh cycle.
### Dynamic Widgets (Default)
By default, any element declared without `static` is considered dynamic. This means its creation closure (`FnMut() -> WidgetLoad`) will be re-evaluated whenever its declared dependencies change or when a manual `refresh()` is triggered.
```rust
use osui::prelude::*;
let my_counter = use_state(0);
rsx! {
// This Div is dynamic because it depends on `my_counter`
%my_counter
Div {
"Counter: {my_counter}"
}
}
```
* **When to use Dynamic**: For any element whose content or type changes based on reactive state or other external factors that necessitate a rebuild of the underlying `Element`.
* **Reactivity**: Essential for building interactive UIs that respond to state changes.
## Reactive Updates with State (`%` prefix)
OSUI's reactivity system allows you to automatically re-render parts of your UI when specific `State` variables change. This is achieved by declaring a dependency using the `%` prefix followed by the state variable's identifier.
```rust
use osui::prelude::*;
fn main() -> std::io::Result<()> {
let screen = Screen::new();
screen.extension(InputExtension); // Needed for input to trigger updates
let click_count = use_state(0);
// Increment counter on any key press
screen.draw_dyn({
let click_count = click_count.clone();
move || {
WidgetLoad::new(String::new())
.component(Handler::new(move |_, e: &crossterm::event::Event| {
if let crossterm::event::Event::Key(_) = e {
**click_count.get() += 1;
}
}))
}
});
rsx! {
// This Div will re-render whenever `click_count` changes
%click_count
Div {
"You have pressed a key {click_count} times."
}
}.draw(&screen);
screen.run()
}
```
When `**click_count.get() += 1;` is called, it marks `click_count` as changed. During the next render cycle, any `DynWidget` (like our `Div` above) that declares `click_count` as a dependency will be automatically refreshed (rebuilt), reflecting the new value.
Multiple dependencies can be declared:
```rust
let state_a = use_state(0);
let state_b = use_state(false);
rsx! {
%state_a %state_b
Div {
"A: {state_a}, B: {state_b}"
}
}
```
The `rsx!` macro automatically clones the `Arc<State<T>>` for each declared dependency and passes it into the widget's creation closure, ensuring the closure can capture and use the state without ownership issues.
## Expanding RSX Blocks (`=>`)
You can compose `rsx!` blocks by calling a function that returns `Rsx` and using the `=>` operator. This is useful for breaking down complex UIs into smaller, manageable functions.
```rust
use osui::prelude::*;
fn my_header() -> Rsx {
rsx! {
Heading, smooth: true, { "My App" }
}
}
fn my_content(data: State<String>) -> Rsx {
rsx! {
%data
Div {
"Data: {data}"
}
}
}
rsx! {
my_header => () // Call my_header function to insert its elements
my_content => (my_state_variable.clone()) // Pass arguments if needed
Div { "Footer" }
}
```
The `rsx_inner!` macro, which `rsx!` expands to, handles the recursive insertion of `RsxElement`s from the called function.
## Summary
The `rsx!` macro is the cornerstone of building UIs in OSUI. By understanding how to define elements, set properties, attach components, and manage reactivity with `State`, you can construct powerful and interactive terminal applications with a clean and declarative syntax.
@@ -0,0 +1,242 @@
# Building Custom Elements
OSUI's component-based architecture allows you to create your own UI elements by implementing the `Element` trait. This guide details how to define custom elements, render them, handle their children, and process events.
## The `Element` Trait
The `Element` trait is the core interface for any renderable UI component in OSUI. It defines methods that the rendering engine calls during the UI lifecycle.
```rust
pub trait Element: Send + Sync {
/// Called to perform rendering for the element.
fn render(&mut self, scope: &mut RenderScope);
/// Called after rendering, for follow-up logic or cleanup.
fn after_render(&mut self, scope: &mut RenderScope);
/// Called to draw child widgets, if any.
fn draw_child(&mut self, element: &Arc<Widget>);
/// Called when an event occurs.
fn event(&mut self, event: &dyn Event);
/// Returns a type-erased reference to this object.
fn as_any(&self) -> &dyn Any;
/// Returns a mutable type-erased reference to this object.
fn as_any_mut(&mut self) -> &mut dyn Any;
}
```
## Defining a Simple Custom Element
Let's create a very basic `Box` element that just draws a rectangle and its text content.
```rust
use osui::prelude::*;
use std::sync::Arc;
pub struct MyBox {
// Children are typically stored if your element acts as a container
children: Vec<Arc<Widget>>,
// Internal state for managing the box's size, if not determined by children
calculated_size: (u16, u16),
pub border_color: u32, // Public field for RSX properties
pub fill_color: u32, // Public field for RSX properties
}
impl MyBox {
// Constructor for use with `rsx!`
pub fn new() -> Self {
MyBox {
children: Vec::new(),
calculated_size: (0, 0),
border_color: 0xAAAAAA, // Default gray border
fill_color: 0x333333, // Default dark fill
}
}
}
impl Element for MyBox {
// Called when the element needs to render its own content.
fn render(&mut self, scope: &mut RenderScope) {
// Draw the background rectangle first
scope.draw_rect(
0,
0,
scope.get_transform().width,
scope.get_transform().height,
self.fill_color,
);
// Draw a border (outline)
// Note: RenderScope's draw_rect doesn't draw outlines directly,
// so we'd typically rely on `Style::Background::Outline` applied
// as a component to the widget containing this element.
// For a simple filled box, we can just draw the background.
// If we wanted a distinct border *inside* the box, it'd be more complex.
// For simplicity, we'll assume `Style` component handles outlines.
// Get the current accumulated size from the scope,
// which might have been influenced by child rendering in `after_render`.
let (width, height) = scope.get_size_or(self.calculated_size.0, self.calculated_size.1);
// Ensure the scope tracks at least this area for its own calculations.
scope.use_area(width, height);
}
// Called after the element's `render` method and its children's `render` methods.
// This is where container elements typically render their children.
fn after_render(&mut self, scope: &mut RenderScope) {
// Store the original parent size for restoration later
let (original_parent_width, original_parent_height) = scope.get_parent_size();
// Pass this element's resolved size as the parent size for its children.
// This is crucial for children to correctly calculate their `Full` or `Center` dimensions.
let self_transform = scope.get_transform().clone(); // Clone resolved transform for current element
scope.set_parent_size(self_transform.width, self_transform.height);
// Track max dimensions used by children for this box's overall size
let mut max_child_width = 0;
let mut max_child_height = 0;
for child_widget in &self.children {
// Children marked with NoRender or NoRenderRoot are handled by their direct parent
// or the screen, not by this specific element's `after_render`.
// This prevents double-rendering if they are also top-level widgets.
if child_widget.get::<NoRender>().is_some() {
continue;
}
scope.clear(); // Clear the scope for each child's rendering context
// Apply any Transform or Style components attached directly to the child widget.
if let Some(child_style) = child_widget.get() {
scope.set_style(child_style);
}
if let Some(child_transform_comp) = child_widget.get() {
scope.set_transform(&child_transform_comp);
}
// Render the child's own content. This fills its render_stack.
child_widget.get_elem().render(scope);
// Re-apply the transform *after* child render to ensure any content-based sizing
// (Dimension::Content) is reflected in the child's `raw_transform.width/height`.
if let Some(child_transform_comp) = child_widget.get() {
scope.set_transform(&child_transform_comp);
}
// Get the child's now-resolved raw transform (position, size, padding)
let child_raw_transform = scope.get_transform_mut();
// Offset the child's actual drawing coordinates by the parent's position and padding.
// This ensures children are drawn relative to their parent's content area.
child_raw_transform.x += self_transform.x + self_transform.px;
child_raw_transform.y += self_transform.y + self_transform.py;
child_raw_transform.px += self_transform.px; // Accumulate padding
child_raw_transform.py += self_transform.py; // Accumulate padding
// Update the parent's (MyBox's) effective size based on its children.
// This is crucial for MyBox to "auto-size" if it's `Dimension::Content`.
max_child_width = max_child_width.max(
child_raw_transform.x
+ child_raw_transform.width
+ (child_raw_transform.px * 2)
- self_transform.x
- self_transform.px, // Relative to parent content area
);
max_child_height = max_child_height.max(
child_raw_transform.y
+ child_raw_transform.height
+ (child_raw_transform.py * 2)
- self_transform.y
- self_transform.py, // Relative to parent content area
);
scope.draw(); // Draw the child's accumulated render stack to the terminal.
child_widget.get_elem().after_render(scope); // Recursively call after_render for child
}
// After all children are processed, update MyBox's calculated size.
// Add back MyBox's own padding to the children's max extent.
self.calculated_size = (
max_child_width + (self_transform.px * 2),
max_child_height + (self_transform.py * 2),
);
// Ensure the scope's own transform reflects the newly calculated size
scope.use_area(self.calculated_size.0, self.calculated_size.1);
// Restore the parent size for subsequent elements at this level.
scope.set_parent_size(original_parent_width, original_parent_height);
}
// Called when a child widget is added to this element via `rsx!`.
fn draw_child(&mut self, element: &Arc<Widget>) {
// Mark the child as `NoRenderRoot` so the Screen doesn't try to render it directly.
// This indicates that its rendering will be managed by this parent element's `after_render`.
element.inject(|w| w.component(NoRenderRoot));
self.children.push(element.clone());
}
// Handles incoming events for this element.
fn event(&mut self, event: &dyn Event) {
// You can check for specific event types
if let Some(key_event) = event.get::<crossterm::event::Event>() {
// Example: Respond to a key press
if let crossterm::event::Event::Key(ke) = key_event {
// println!("MyBox received key: {:?}", ke.code); // For debugging
}
}
// You might also want to pass events to children,
// though OSUI's default event system often dispatches globally.
}
// Required for downcasting the trait object.
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
```
## Using Your Custom Element in `rsx!`
After defining `MyBox`, you can use it just like any other built-in element:
```rust
use osui::prelude::*;
// In your main or app function:
rsx! {
@Transform::new().dimensions(50, 10); // Set a fixed size for the box
MyBox, border_color: 0xFF0000, fill_color: 0x0000FF, {
("Hello from inside MyBox!", 0xFFFFFF)
Div {
"Another nested div!"
}
}
}.draw(&screen);
```
## Key Considerations for Custom Elements
* **`render()` vs. `after_render()`**:
* `render()`: Use this for drawing the element's *own* content (e.g., text, background shapes). It should use the `RenderScope` to queue drawing commands.
* `after_render()`: Use this for container logic, specifically iterating through `self.children` and rendering them. It needs to manage the `RenderScope`'s parent size and transform for each child.
* **`draw_child()`**: This method is called by the `rsx!` macro when you nest elements inside your custom element. You *must* store the `Arc<Widget>` in a `Vec` or similar structure.
* Crucially, you should also call `element.inject(|w| w.component(NoRenderRoot));`. This tells the main `Screen` loop to *not* render this child directly at the root level, as its rendering will be managed by its parent (`MyBox` in this case).
* **`as_any()` / `as_any_mut()`**: These are boilerplate methods required for downcasting trait objects, allowing you to retrieve specific `Element` or `Component` types from a `Box<dyn Element>` or `Box<dyn Component>`.
* **`RenderScope` Usage**:
* `scope.set_transform(&t)`: Applies a `Transform` component's rules to calculate the absolute position and size (`RawTransform`) for the current scope, based on its parent's size.
* `scope.get_transform()` / `scope.get_transform_mut()`: Accesses the `RawTransform` that represents the current element's resolved position and size.
* `scope.set_parent_size(width, height)`: Critical for nested elements. Before rendering a child, set the `scope`'s parent size to *this element's* resolved size so the child can correctly resolve its `Dimension::Full` or `Position::Center` values. Remember to restore the original parent size after processing all children.
* `scope.use_area(width, height)`: In your `render` method, if your element's size depends on its content or a fixed size, use this to tell the `RenderScope` what minimum area your element occupies. This helps when the element's `Dimension` is `Content`.
* **State Management**: If your custom element needs to hold dynamic data, consider using `osui::state::State<T>` for reactive updates, especially if you want your element to trigger re-renders of itself or its children when its internal data changes.
By following these guidelines, you can create sophisticated and well-integrated custom UI elements that extend OSUI's capabilities to fit your application's unique needs.
@@ -0,0 +1,138 @@
# Handling Input
Interactive terminal applications rely heavily on input handling. OSUI provides a flexible event system to capture and respond to user input, primarily keyboard events. This guide explains how to integrate and use OSUI's input capabilities.
## The `InputExtension`
The core of OSUI's input system is the `InputExtension`. This extension is responsible for:
1. Enabling `crossterm`'s raw mode, which allows for detailed, non-buffered input events.
2. Spawning a dedicated thread to continuously read terminal events.
3. Dispatching these events to all active widgets on the `Screen`.
### Registering the `InputExtension`
To start receiving input, you must register the `InputExtension` with your `Screen` instance:
```rust
use osui::prelude::*;
fn main() -> std::io::Result<()> {
let screen = Screen::new();
// Register the InputExtension once at startup
screen.extension(InputExtension);
// ... your rsx! or draw calls ...
screen.run()
}
```
Once registered, the extension will manage raw mode and event polling. When your application closes (e.g., via `screen.close()`), the `InputExtension`'s `on_close` method will automatically disable raw mode, returning the terminal to its normal state.
## The `Event` Trait and `Handler` Component
OSUI uses a generic `Event` trait and a `Handler` component to enable widgets to subscribe to and process events.
* **`Event` Trait**: A marker trait that identifies types that can be dispatched as events. It requires `Send + Sync` and `as_any()` for type erasure. `crossterm::event::Event` already implements this within OSUI.
* **`Handler<E>` Component**: A wrapper component that holds a closure (`FnMut(&Arc<Widget>, &E)`) which will be called when an event of type `E` is dispatched to the widget it's attached to.
### Attaching an Event Handler
You attach `Handler` components to widgets using the `@` syntax in `rsx!` or by calling `widget.component(Handler::new(...))` directly.
The `Handler`'s closure receives two arguments:
1. An `Arc<Widget>` representing the widget the handler is attached to. This allows the handler to interact with its own widget, e.g., by getting or setting components on it.
2. A reference to the event (`&E`).
#### Example: Handling Keyboard Input
To handle `crossterm::event::Event` (which includes `KeyEvent`, `MouseEvent`, `ResizeEvent`, etc.), you'll typically downcast the incoming event to a `KeyEvent`.
```rust
use osui::prelude::*;
use crossterm::event::{KeyCode, KeyEvent, Event as CrosstermEvent}; // Alias to avoid conflict with osui::event!
fn main() -> std::io::Result<()> {
let screen = Screen::new();
screen.extension(InputExtension);
// This widget will capture all keyboard events.
// We attach the Handler directly to the root widget drawn on the screen.
rsx! {
@Handler::new({
let screen = screen.clone(); // Clone Arc for the closure
move |current_widget, event: &CrosstermEvent| {
// Check if the event is a KeyEvent
if let CrosstermEvent::Key(key_event) = event {
match key_event.code {
KeyCode::Char('q') => {
// Close the screen if 'q' is pressed
println!("Quitting application...");
screen.close();
}
KeyCode::Enter => {
// Example: Increment a counter on Enter
if let Some(mut my_state_comp) = current_widget.get::<State<i32>>() {
**my_state_comp.get_mut() += 1;
println!("Enter pressed! Count: {}", my_state_comp.get_dl());
}
}
_ => {
// Handle other keys or print them for debugging
// println!("Key pressed: {:?}", key_event.code);
}
}
}
}
});
Div {
"Press 'q' to quit, 'Enter' to increment a hidden counter (check console)."
}
}.draw(&screen);
screen.run()
}
```
In the example above, the `Handler` is attached to the root `Div`. Because the `InputExtension` dispatches events to *all* widgets managed by the screen, this root widget will receive every `crossterm::event::Event`.
### Element-Specific Event Handling
Some elements, like `Input` and `Paginator`, implement the `Element::event` method internally to handle specific events relevant to their functionality.
* **`Input` Element**: Manages text input, cursor movement (left/right), backspace, and delete based on `KeyEvent`s.
* **`Paginator` Element**: Switches pages on `KeyCode::Tab` and `KeyCode::BackTab` (`Shift+Tab`).
When you use these elements, their internal `event` method is automatically called by the `Screen`'s rendering loop. You can still attach your own `Handler` components to these elements for additional, custom event logic that doesn't interfere with their built-in behavior.
```rust
use osui::prelude::*;
rsx! {
Paginator {
// This handler will be called *in addition* to Paginator's default tab handling.
@Handler::new(|_, e: &crossterm::event::Event| {
if let crossterm::event::Event::Key(KeyEvent { code: KeyCode::Char('p'), .. }) = e {
// Do something when 'p' is pressed on the Paginator
println!("Paginator received 'p' key!");
}
});
Div { "Page 1" }
Div { "Page 2" }
}
}
```
## Custom Events
Beyond `crossterm` events, you can define and dispatch your own custom event types using the `event!` macro. This is useful for communication between different parts of your application or custom extensions.
See the [Advanced: Custom Events](../advanced/custom_events.md) guide for details.
## Summary
By understanding how to register the `InputExtension` and attach `Handler` components to your widgets, you gain full control over user interaction in your OSUI applications. This robust event system allows for building highly responsive and interactive terminal UIs.
@@ -0,0 +1,184 @@
# Layout and Styling
OSUI provides a robust layout and styling system to control the appearance and positioning of your UI elements. This system is built around several key concepts: `Transform`, `Position`, `Dimension`, `Style`, and `Background`.
## 1. `Transform`: Positioning and Sizing Rules
The `Transform` component is used to define how a widget should be positioned and sized relative to its parent. It specifies declarative rules rather than absolute pixel values, allowing for flexible and responsive layouts.
You typically create and attach a `Transform` using the `@` component syntax in `rsx!` or by calling `widget.component(Transform::new()...)`.
```rust
use osui::prelude::*;
// Default transform: (0,0) position, content-sized
let default_transform = Transform::new();
// Centered horizontally and vertically, content-sized
let centered_transform = Transform::center();
rsx! {
@Transform::new().padding(2, 1).dimensions(30, 5);
Div { "A fixed-size div with padding." }
@Transform::new().right().margin(5, 0);
Div { "Aligned to the right with a 5-cell horizontal margin." }
@Transform::new().bottom().margin(0, 2);
Div { "Aligned to the bottom with a 2-cell vertical margin." }
}
```
### `Transform` Fields:
* `x: Position`: Horizontal position relative to the parent.
* `y: Position`: Vertical position relative to the parent.
* `mx: i32`: Horizontal margin (offset) from the calculated `x` position. Can be negative for overlap.
* `my: i32`: Vertical margin (offset) from the calculated `y` position. Can be negative for overlap.
* `px: u16`: Horizontal padding (internal spacing) around the content.
* `py: u16`: Vertical padding (internal spacing) around the content.
* `width: Dimension`: Rule for the widget's width.
* `height: Dimension`: Rule for the widget's height.
### Chainable Methods for `Transform`
`Transform` provides several convenient chainable methods for common layout patterns:
* `Transform::new()`: Creates a default transform at `(0,0)` with `Content` dimensions and no padding/margin.
* `Transform::center()`: Creates a transform centered both horizontally and vertically.
* `Transform::bottom(self)`: Sets `y` to `Position::End`.
* `Transform::right(self)`: Sets `x` to `Position::End`.
* `Transform::margin(self, x: i32, y: i32)`: Sets `mx` and `my`.
* `Transform::padding(self, x: u16, y: u16)`: Sets `px` and `py`.
* `Transform::dimensions(self, width: u16, height: u16)`: Sets `width` and `height` to `Dimension::Const`.
## 2. `Position`: Horizontal and Vertical Alignment
`Position` defines how an element is placed along an axis relative to its parent's boundaries.
```rust
pub enum Position {
/// Fixed position in cells from the origin (top-left).
Const(u16),
/// Centered in the parent.
Center,
/// Aligned to the end (right for x, bottom for y) of the parent.
End,
}
```
* `Position::Const(value)`: Sets an exact coordinate from the top-left (0,0). You can also use `u16` directly, thanks to `impl From<u16> for Position`.
```rust
@Transform { x: 5, y: 10 }; // Same as x: Position::Const(5), y: Position::Const(10)
```
* `Position::Center`: Centers the element within the available space of its parent on that axis.
```rust
@Transform { x: Position::Center, y: Position::Center };
```
* `Position::End`: Aligns the element to the right (for `x`) or bottom (for `y`) edge of its parent.
```rust
@Transform { x: Position::End, y: Position::Const(0) }; // Top-right aligned
```
## 3. `Dimension`: Sizing Rules
`Dimension` defines how an element's width or height is determined.
```rust
pub enum Dimension {
/// Fills the available space from the parent.
Full,
/// Automatically sized to fit content.
Content,
/// Fixed size in cells.
Const(u16),
}
```
* `Dimension::Full`: The element will take up 100% of the available space on that axis within its parent.
* `Dimension::Content`: The element's size will be determined by its content. For container elements (like `Div`, `FlexRow`, `FlexCol`), this means their size will expand to encompass their children. For text elements, it will be the size of the text. This is the default.
* `Dimension::Const(value)`: The element will have a fixed size in cells. You can also use `u16` directly, thanks to `impl From<u16> for Dimension`.
```rust
@Transform { width: 20, height: 5 }; // Same as width: Dimension::Const(20), height: Dimension::Const(5)
```
## 4. `Style`: Visual Appearance
The `Style` component defines the background and foreground colors of a widget.
```rust
pub struct Style {
pub background: Background,
pub foreground: Option<u32>,
}
```
* `background: Background`: Specifies the widget's background appearance.
* `foreground: Option<u32>`: Sets the text color. `None` means the default terminal foreground color.
You attach `Style` using the `@` component syntax:
```rust
use osui::prelude::*;
rsx! {
@Style { background: Background::Solid(0x222222), foreground: Some(0xFFFFFF) };
Div {
"This text is white on a dark gray background."
}
@Style { background: Background::Outline(0x00FF00), foreground: Some(0x00FF00) };
Div {
"This div has a green outline and green text."
}
}
```
### `Background`: Background Appearance Options
`Background` defines various ways a widget's background can be drawn. Colors are specified as `u32` hex values (e.g., `0xFF0000` for red).
```rust
pub enum Background {
/// Transparent / no background.
NoBackground,
/// Draws a basic outline using the given color.
Outline(u32),
/// Draws a rounded outline using the given color.
RoundedOutline(u32),
/// Fills the background with the specified color.
Solid(u32),
}
```
## The `transform!` Macro
For even more concise `Transform` definitions, you can use the `transform!` macro:
```rust
use osui::prelude::*;
rsx! {
@transform!{ x: 10, y: Center, width: Full, padding: (1, 1) };
Div {
"This div is at x=10, centered vertically, full width, with 1 unit of padding."
}
}
```
This macro automatically converts `u16` values to `Position::Const` or `Dimension::Const` where appropriate.
## How Layout and Styling are Applied
When the `Screen` renders a widget, it performs the following steps (simplified):
1. **Retrieve Components**: It fetches the `Transform` and `Style` components attached to the widget.
2. **Resolve Transform**: The `Transform`'s `use_dimensions` and `use_position` methods are called. These methods take the *parent's* resolved size (from the `RenderScope`) and convert the declarative `Position` and `Dimension` rules into concrete `u16` values (absolute `x`, `y`, `width`, `height`) within a `RawTransform` structure.
3. **Apply Style**: The `Style` component is passed to the `RenderScope`.
4. **Element Rendering**: The widget's `Element::render` method is called. This method uses the now-resolved `RawTransform` and `Style` from the `RenderScope` to queue its drawing instructions (text, rectangles). It might also update `RenderScope`'s size based on content.
5. **Parent Rendering (`after_render`)**: For container elements (like `Div`, `FlexRow`, `FlexCol`), their `Element::after_render` method then takes over. They iterate through their children, set the `RenderScope`'s "parent size" to *their own* resolved size, and recursively trigger the rendering process for each child.
6. **Drawing to Terminal**: Finally, `RenderScope::draw()` is called, which takes all accumulated drawing instructions and applies them to the terminal using `crossterm` and ANSI escape codes. Backgrounds and outlines are drawn first, then text and other content.
This multi-stage process ensures that layout rules are correctly interpreted from parent to child, allowing for adaptive and well-positioned UI elements.
@@ -0,0 +1,127 @@
# Using Extensions
OSUI features an extensible architecture that allows you to add global behaviors, custom rendering logic, or integrate third-party functionalities by implementing the `Extension` trait. This guide explains what extensions are, how to implement them, and how to register them with your `Screen`.
## What are Extensions?
Extensions are separate modules or structs that provide lifecycle hooks for the `Screen` and can interact with widgets at a global level. They are ideal for:
* **Global Event Handling**: Such as processing keyboard or mouse input across all widgets (e.g., `InputExtension`).
* **Periodic Tasks**: Running logic on a fixed interval (e.g., `TickExtension`, `VelocityExtension`).
* **Custom Rendering Overrides**: Injecting logic before or after a widget's rendering.
* **Managing Global State**: Maintaining state accessible to multiple widgets or other extensions.
* **Debugging or Logging**: Observing the UI tree or rendering process.
## The `Extension` Trait
The `Extension` trait defines the interface for all extensions:
```rust
pub trait Extension {
/// Called once when the screen starts running.
fn init(&mut self, _screen: Arc<Screen>) {}
/// Called when the screen is being closed.
fn on_close(&mut self, _screen: Arc<Screen>) {}
/// Called for each widget before its `render` method is invoked.
fn render_widget(&mut self, _scope: &mut RenderScope, _widget: &Arc<Widget>) {}
}
```
All methods have default empty implementations, meaning you only need to implement the hooks relevant to your extension's functionality.
## Implementing a Custom Extension
Let's create a simple extension that logs when the screen initializes and when a widget is rendered.
```rust
use osui::prelude::*;
use std::sync::Arc;
pub struct MyLoggerExtension;
impl Extension for MyLoggerExtension {
fn init(&mut self, screen: Arc<Screen>) {
println!("MyLoggerExtension: Screen initialized!");
// You could store the screen Arc if needed for later interaction
// self.screen = Some(screen);
}
fn on_close(&mut self, screen: Arc<Screen>) {
println!("MyLoggerExtension: Screen closing!");
}
fn render_widget(&mut self, scope: &mut RenderScope, widget: &Arc<Widget>) {
// This hook is called for *every* widget about to be rendered.
// It's useful for debugging or applying global styles/transforms.
// Note: The widget here is the Arc<Widget>, not its inner Element.
// You can use `widget.get_elem()` to access the Element.
// For simplicity, we'll just log the widget's address and a component if it has one.
if let Some(t) = widget.get::<Transform>() {
println!(
"MyLoggerExtension: Rendering widget at {:p} with transform: x={:?} y={:?}",
Arc::as_ptr(widget), t.x, t.y
);
} else {
println!("MyLoggerExtension: Rendering widget at {:p}", Arc::as_ptr(widget));
}
}
}
```
## Registering an Extension
To activate your extension, you must register it with the `Screen` instance using the `screen.extension()` method. This is typically done at the beginning of your `main` function.
```rust
use osui::prelude::*;
use std::sync::Arc; // Needed for Arc<Screen> in the main function
// ... (MyLoggerExtension definition from above) ...
fn main() -> std::io::Result<()> {
let screen = Screen::new();
// Register your custom extension
screen.extension(MyLoggerExtension);
// Register other built-in extensions if needed
screen.extension(InputExtension);
screen.extension(TickExtension(10)); // Example: Tick every 10ms
rsx! {
Div { "Hello, OSUI!" }
}.draw(&screen);
screen.run()
}
```
Once registered, the `Screen` will call the appropriate lifecycle methods of your extension at the right times during its operation.
## Built-in Extensions
OSUI comes with several useful built-in extensions:
* [`InputExtension`](../reference/extensions_api.md#inputextension): Handles keyboard input and dispatches `crossterm::event::Event`s. **Crucial for interactive applications.**
* [`TickExtension`](../reference/extensions_api.md#tickextension): Dispatches `TickEvent`s at a specified rate, useful for animations or periodic updates.
* [`VelocityExtension`](../reference/extensions_api.md#velocityextension): Automatically updates the `Transform` of widgets that have a `Velocity` component, causing them to move.
* [`IdExtension`](../reference/extensions_api.md#idextension): Provides a way to retrieve specific widgets by a unique `Id` component. (Note: The current `IdExtension` implementation only *stores* a screen reference but doesn't actively do anything unless you manually call its `get_element` method.)
You use these built-in extensions by simply calling `screen.extension(...)` with an instance of them, just like `MyLoggerExtension`.
## Interaction between Extensions and Widgets
Extensions can interact with widgets in various ways:
* **Reading Components**: Within `render_widget` or other hooks, an extension can use `widget.get::<C>()` to read components (like `Transform` or `Style`) attached to a widget, influencing how it renders or behaves.
* **Setting Components**: An extension can use `widget.set_component(c)` to dynamically add or modify components on a widget. For example, `VelocityExtension` modifies `Transform` components.
* **Dispatching Events**: Extensions can dispatch custom events to widgets using `widget.event(&my_custom_event)`.
* **Modifying RenderScope**: In `render_widget`, an extension can directly modify the `RenderScope` (e.g., apply a global offset or filter) before the widget's `render` method is called.
By leveraging extensions, you can keep your core UI logic clean and declarative, while offloading cross-cutting concerns or global features into reusable and modular units.
@@ -0,0 +1,64 @@
# Contributing to OSUI
We welcome contributions to OSUI! Whether it's reporting bugs, suggesting features, improving documentation, or submitting code, your help is valuable. This guide outlines the process for contributing.
## How to Contribute
1. **Report Bugs**: If you find a bug, please open an issue on the [GitHub repository](https://github.com/osui-rs/osui/issues). Provide a clear description of the bug, steps to reproduce it, and your environment (OS, Rust version, terminal emulator).
2. **Suggest Features**: Have an idea for a new feature or improvement? Open an issue on GitHub to discuss it.
3. **Improve Documentation**: Spotted a typo, unclear explanation, or missing example? Feel free to submit a pull request with your changes, or open an issue.
4. **Submit Code (Pull Requests)**: If you'd like to contribute code, follow the guidelines below.
## Code Contribution Guidelines
1. **Fork the Repository**: Start by forking the [OSUI GitHub repository](https://github.com/osui-rs/osui).
2. **Clone Your Fork**:
```bash
git clone https://github.com/your-username/osui.git
cd osui
```
3. **Create a New Branch**: Create a descriptive branch for your changes.
```bash
git checkout -b feature/my-new-feature
# or
git checkout -b bugfix/fix-some-bug
```
4. **Make Your Changes**:
* **Code Style**: Adhere to the existing Rust code style. Run `cargo fmt` before committing.
* **Clippy**: Ensure your code passes Clippy lints: `cargo clippy --all-targets --all-features -- -D warnings`.
* **Tests**: If you're adding new functionality, please include unit tests. If fixing a bug, consider adding a regression test.
* **Documentation**: Update relevant documentation (API reference, guides) for any new features or changes. Add comments to your code where necessary.
* **Examples/Demos**: If your feature adds significant new functionality, consider adding a small example to the `src/demos` directory.
5. **Commit Your Changes**: Write clear, concise commit messages.
```bash
git commit -m "feat: Add new awesome feature"
```
6. **Push to Your Fork**:
```bash
git push origin feature/my-new-feature
```
7. **Open a Pull Request**:
* Go to the [OSUI repository on GitHub](https://github.com/osui-rs/osui).
* You should see a prompt to open a pull request from your recently pushed branch.
* Provide a clear title and description for your pull request. Explain what problem it solves and how.
* Reference any related issues (e.g., `Fixes #123`, `Closes #456`).
8. **Review Process**:
* Project maintainers will review your pull request.
* Be prepared to address feedback and make further changes if requested.
* Once approved, your changes will be merged into the `master` branch.
## Development Environment Setup
* **Rust Toolchain**: Make sure you have a recent stable Rust toolchain installed.
```bash
rustup update
```
* **Dependencies**: Ensure all project dependencies are installed by running `cargo build`.
* **Testing**: Run tests with `cargo test`.
* **Linting**: Use `cargo clippy --all-targets --all-features -- -D warnings` to check for common code issues.
* **Formatting**: Use `cargo fmt` to automatically format your code according to Rust's standard style.
Thank you for considering contributing to OSUI! Your efforts help make this project better for everyone.
[**Return to Overview**](../intro/overview.md)
@@ -0,0 +1,213 @@
# Custom Events
Beyond `crossterm` events, OSUI's event system is designed to be extensible, allowing you to define and dispatch your own custom event types. This is essential for building complex applications where different parts of your UI or internal logic need to communicate.
## 1. Defining a Custom Event
You define custom events using the `event!` macro, which automatically implements the necessary `Event` trait for your struct.
### Example: A `UserLoginEvent`
```rust
// In your `events.rs` or `lib.rs` file
use osui::prelude::*; // Import prelude for `event!` macro
event!(UserLoginEvent {
username: String,
success: bool,
});
// A simple unit event
event!(UserLoggedOut);
```
These events automatically get `Debug` and `Clone` derives, and implement the `osui::extensions::Event` trait.
## 2. Dispatching a Custom Event
Events are dispatched to widgets using the `widget.event(&my_event)` method. The `Screen`'s main loop automatically dispatches `crossterm::event::Event`s to all top-level widgets. For custom events, you will typically dispatch them manually from:
* **Event Handlers**: A `Handler<crossterm::event::Event>` that captures input can then dispatch your custom event.
* **Background Threads**: A thread performing some work can dispatch an event to the UI when its work is done.
* **Custom Elements**: An element's internal logic might dispatch an event based on user interaction or state changes.
* **Extensions**: An `Extension` might dispatch events to trigger behavior across multiple widgets.
To dispatch an event, you need an `Arc<Widget>` reference.
### Example: Dispatching from an `Input` handler
Let's imagine you have an `Input` widget where, when the user presses Enter, you want to dispatch a `UserLoginEvent`.
```rust
use osui::prelude::*;
use std::sync::Arc;
use crossterm::event::{KeyCode, KeyEvent, Event as CrosstermEvent};
event!(UserLoginEvent {
username: String,
success: bool,
});
fn main() -> std::io::Result<()> {
let screen = Screen::new();
screen.extension(InputExtension);
let username_input_state = use_state(String::new());
rsx! {
// This handler is attached to the root widget, listening for ALL input events.
// It will then dispatch a custom UserLoginEvent.
@Handler::new({
let screen = screen.clone(); // Clone screen to get all widgets
let username_input_state = username_input_state.clone(); // Clone state to read username
move |current_widget, event: &CrosstermEvent| {
if let CrosstermEvent::Key(KeyEvent { code: KeyCode::Enter, .. }) = event {
let entered_username = username_input_state.get_dl();
// Simulate login logic
let login_success = entered_username == "admin";
// Create and dispatch the custom event to ALL widgets on the screen
// This is inefficient; normally you'd dispatch to specific widgets or use state updates
let login_event = UserLoginEvent {
username: entered_username,
success: login_success,
};
for w in screen.widgets.lock().unwrap().iter() {
w.event(&login_event);
}
}
}
});
Div {
"Enter username (type 'admin' for success):"
@Transform::new().y(1).dimensions(20, 1);
@Style { background: Background::Outline(0x888888) };
Input, state: username_input_state, { }
}
}.draw(&screen);
screen.run()
}
```
## 3. Handling a Custom Event
Widgets (or other entities) can subscribe to your custom event types using the `Handler<E>` component.
### Example: A `LoginStatusDisplay` Widget
Now, let's create a widget that reacts to our `UserLoginEvent`:
```rust
use osui::prelude::*;
use std::sync::Arc;
// Make sure UserLoginEvent is defined and visible
event!(UserLoginEvent {
username: String,
success: bool,
});
pub struct LoginStatusDisplay {
status_text: State<String>,
}
impl LoginStatusDisplay {
pub fn new() -> Self {
Self {
status_text: use_state("Awaiting login...".to_string()),
}
}
}
impl Element for LoginStatusDisplay {
fn render(&mut self, scope: &mut RenderScope) {
scope.draw_text(0, 0, &self.status_text.get_dl());
}
// Implement as_any, as_any_mut, draw_child, after_render as needed for container logic
fn as_any(&self) -> &dyn Any { self }
fn as_any_mut(&mut self) -> &mut dyn Any { self }
}
fn main() -> std::io::Result<()> {
let screen = Screen::new();
screen.extension(InputExtension);
let username_input_state = use_state(String::new());
let login_status_display = Arc::new(Widget::new_static(Box::new(LoginStatusDisplay::new())));
// Attach the handler for UserLoginEvent to the LoginStatusDisplay widget
login_status_display.component(Handler::new({
let status_state = login_status_display.get::<LoginStatusDisplay>().unwrap().status_text.clone();
move |_, event: &UserLoginEvent| {
if event.success {
status_state.set(format!("Welcome, {}!", event.username));
} else {
status_state.set(format!("Login failed for {}!", event.username));
}
}
}));
// Draw the LoginStatusDisplay and the Input field
screen.draw_widget(login_status_display.clone());
rsx! {
// This handler is now attached to a separate, root element.
@Handler::new({
let screen = screen.clone();
let username_input_state = username_input_state.clone();
move |_, event: &CrosstermEvent| {
if let CrosstermEvent::Key(KeyEvent { code: KeyCode::Enter, .. }) = event {
let entered_username = username_input_state.get_dl();
let login_success = entered_username == "admin";
let login_event = UserLoginEvent {
username: entered_username,
success: login_success,
};
// Dispatch to the specific login_status_display widget
// This is more efficient than iterating all screen widgets.
if let Some(w) = screen.widgets.lock().unwrap().iter().find(|w| {
// A more robust way to find the target widget, perhaps by an ID component
w.get_elem().as_any().is::<LoginStatusDisplay>()
}) {
w.event(&login_event);
}
}
}
});
@Transform::new().y(0); // Position the input above the status
Div {
"Enter username (type 'admin' for success):"
@Transform::new().y(1).dimensions(20, 1);
@Style { background: Background::Outline(0x888888) };
Input, state: username_input_state, { }
}
}.draw(&screen); // Add the input via rsx!
screen.run()
}
```
In this enhanced example:
1. The `Input` field (`rsx!`) has its own `Handler` for `crossterm::event::Event`.
2. When `Enter` is pressed in the `Input`'s handler, it constructs a `UserLoginEvent`.
3. Instead of iterating all widgets on the screen, it tries to find the `LoginStatusDisplay` widget (e.g., by checking its inner `Element` type, though using an `Id` component is more robust for production).
4. It dispatches the `UserLoginEvent` directly to that specific `login_status_display` widget.
5. The `Handler<UserLoginEvent>` attached to `login_status_display` then updates its internal `status_text` `State`.
6. Because `status_text` is a `State`, and `LoginStatusDisplay` is a `StaticWidget` whose `render` method reads `status_text`, the display updates automatically.
## Event Propagation (Important)
OSUI's current event system is primarily a **global dispatch model**.
* `InputExtension` (and `TickExtension`) dispatches events to *all* top-level widgets (`Screen.widgets.lock().unwrap().iter()`).
* `Widget::event()` then checks for `Handler` components and calls the `Element::event` method.
This means if you have multiple `Handler`s for the *same event type* on different widgets, they will all be called. For more complex scenarios, you might need to build your own event routing or bubbling system on top of this, or prefer using `State` updates for communication over direct event dispatch between deeply nested components.
However, for simple communication like the `UserLoginEvent` example, dispatching directly to the target widget (once found) is efficient.
Custom events are a powerful tool for decoupling concerns and enabling clear communication between different parts of your OSUI application, allowing you to build more complex and modular UIs.
@@ -0,0 +1,89 @@
# Performance Considerations
Building performant Terminal User Interfaces (TUIs) requires careful attention, as direct terminal manipulation can be slower than native graphical UIs. OSUI is designed with performance in mind, offering mechanisms to optimize rendering and reactivity.
## 1. `DynWidget` vs. `StaticWidget`
This is perhaps the most crucial performance decision in OSUI.
* **`StaticWidget`**:
* **Creation**: The `Element` instance is created only *once* when the widget is initially loaded.
* **Rendering**: During each `Screen::render()` cycle, the `Element::render` and `Element::after_render` methods are called directly on the existing `Element` instance. The `Element`'s internal state (if any) is mutated directly.
* **Overhead**: Minimal. No re-allocation or re-evaluation of closures per frame.
* **When to Use**: For any part of your UI that does not change its fundamental structure or the type of its root `Element` instance. This includes static text, fixed layouts, or elements whose internal state changes but doesn't require a full rebuild of the `Element` itself.
* **In `rsx!`**: Use the `static` keyword: `static Div { "Hello" }`.
* **`DynWidget`**:
* **Creation**: Holds a closure (`FnMut() -> WidgetLoad`) that *rebuilds* the `Element` and its initial components whenever `refresh()` is called.
* **Rendering**: During `Screen::render()`, if `auto_refresh()` determines that a dependency has changed, the widget's internal `Element` is entirely replaced by re-executing the creation closure. Then, the `render` methods are called on this *new* `Element` instance.
* **Overhead**: Higher. Involves re-allocations for the new `Element` and `HashMap` of components, plus the cost of re-evaluating the closure and potentially re-parsing text for elements like `format!()` strings.
* **When to Use**: For parts of your UI that *must* change their `Element` type, or whose content is deeply tied to reactive `State` that necessitates a full rebuild to reflect changes. Use it for dynamic text, lists that grow/shrink, or components that switch between different visual representations.
* **In `rsx!`**: Default behavior or using `%dependency`: `%my_state Div { "Count: {my_state}" }`.
**Recommendation**: Favor `static` widgets whenever possible. Break down your UI into the smallest possible `DynWidget`s to isolate reactive updates and minimize the scope of re-renders.
## 2. `State<T>` Usage and Granularity
OSUI's `State<T>` is efficient for simple values, but consider its implications for larger data structures.
* **Modification Cost**: When `**my_state.get() = ...` or `my_state.set(...)` is called, it marks `inner.changed = inner.dependencies`. This means *all* `DynWidget`s listening to that specific `State<T>` will be rebuilt on the next `auto_refresh` cycle.
* **Large `State` Objects**: If `T` in `State<T>` is a large struct or `Vec`, and you only modify a small part of it, the entire `DynWidget` (and its children) listening to it will still rebuild.
* **Optimization**:
* **Splitting State**: If a complex data structure has independent parts that change, consider splitting it into multiple `State` objects.
```rust
// Instead of:
struct UserProfile { name: String, email: String, settings: Settings }
let profile = use_state(UserProfile { /* ... */ });
// And updating `profile.get().name = ...` which rebuilds everything.
// Consider:
let user_name = use_state(String::new());
let user_email = use_state(String::new());
let user_settings = use_state(Settings::new());
// Then, only widgets depending on `user_name` re-render when `user_name` changes.
```
* **Smart `Element` Implementations**: For complex data, a custom `Element` can internally manage its own `State` and handle partial updates without requiring a full rebuild of the `Element` itself. For instance, an `Element` could hold a `State<Vec<Item>>` and only re-render the changed `Item`s internally, or update specific `Widget` children based on diffing logic, rather than relying on `DynWidget` to rebuild the entire `Element`.
## 3. Terminal I/O Overhead
Every character printed to the terminal, especially with color or cursor positioning, incurs overhead due to ANSI escape code processing and actual screen updates by the terminal emulator.
* **Full Screen Clear**: `utils::clear()` (used by `Screen::render`) clears the entire screen. This is a common TUI practice to avoid artifacts but is also a performance bottleneck for very high frame rates or remote connections. OSUI currently performs a full clear every frame.
* **Minimize Redraws**: OSUI's reactive system already helps minimize *what* is rebuilt, but the `RenderScope` then draws the *entire* content of each widget. Terminal emulators often optimize partial updates, but minimizing the total area of change is always beneficial.
* **Batching**: `RenderScope` implicitly batches drawing commands before `draw()` is called. Avoid manual, unbuffered `print!` calls in tight loops.
## 4. Expensive Operations in `Element::render` or `FnMut() -> WidgetLoad` Closures
* **Avoid Heavy Computation**: Do not perform computationally intensive tasks (e.g., complex data processing, network requests, large file I/O) directly within `Element::render` or the `FnMut() -> WidgetLoad` closure of a `DynWidget`. These are called frequently (every frame for `render`, or every dependency change for the closure).
* **Offload**: If such operations are necessary, offload them to separate `std::thread::spawn` threads or use asynchronous runtime if your application supports it. Update `State<T>` from these background threads, and your UI will react.
```rust
// BAD (expensive in render/build closure):
// rsx! { Div { format!("Result: {}", expensive_computation()) } }
// GOOD:
let computation_result = use_state("Calculating...".to_string());
std::thread::spawn({
let computation_result = computation_result.clone();
move || {
let result = expensive_computation(); // Runs in background
computation_result.set(format!("Result: {}", result)); // Updates state, triggers UI refresh
}
});
rsx! {
%computation_result
Div { "{computation_result}" }
}
```
## 5. `Mutex` Contention
OSUI uses `Mutex`es extensively (`Arc<Mutex<T>>`) for thread-safe access to widgets, elements, components, and state.
* **Minimize Lock Duration**: When you call `my_state.get()` or `widget.get_elem()`, you acquire a `MutexGuard`. Keep the duration for which you hold this lock as short as possible. Perform your read/write operation, then `drop` the guard or let it go out of scope quickly.
* **Avoid Nested Locks**: Do not acquire a `Mutex` lock and then, while holding it, try to acquire another `Mutex` that could be held by a different thread trying to acquire your first lock. This leads to deadlocks. `State::get_dl()` is useful for avoiding this, as it releases the lock immediately after cloning.
By being mindful of these performance considerations, you can ensure your OSUI applications are responsive and efficient, even when handling complex UIs or frequent updates.
@@ -0,0 +1,141 @@
# Component Model
OSUI's architecture is fundamentally component-based, drawing inspiration from modern GUI frameworks. This model centers around three core entities: `Element`s, `Component`s, and `Widget`s, each serving a distinct purpose in building and managing the UI.
## `Element`: The Renderable Unit
An `Element` is the foundational unit that knows how to render itself to the screen. It encapsulates the drawing logic and, for container elements, how to manage and draw its children.
* **Responsibility**:
* **Rendering**: Implementing the `render` method to draw its visual representation using a `RenderScope`.
* **Child Management**: For container elements, implementing `draw_child` to accept children and `after_render` to recursively render them.
* **Event Handling**: Optionally implementing `event` to react to specific dispatched events.
* **Characteristics**:
* `Element` is a trait (`pub trait Element: Send + Sync`).
* Common `Element` implementations include `String` (for text), `Div`, `FlexRow`, `Input`, `Heading`, etc.
* An `Element` instance typically holds its own internal state and references to its children if it's a container.
* **Why a Trait Object?**: `Element` is used as a trait object (`Box<dyn Element>`) because the type of element within a UI tree needs to be dynamic at runtime. A `Widget` can hold *any* `Element` that implements the trait, regardless of its concrete type.
**Example Element (`Div` Simplified):**
```rust
// src/elements/div.rs
pub struct Div {
children: Vec<Arc<Widget>>, // Stores children as Arc<Widget>
// ... other internal state like calculated_size
}
impl Element for Div {
fn render(&mut self, scope: &mut RenderScope) {
// Queue drawing commands for the Div itself (e.g., background)
// scope.draw_rect(...);
}
fn after_render(&mut self, scope: &mut RenderScope) {
// Iterate and render `self.children`
for child_widget in &self.children {
// ... setup scope for child, call child_widget.get_elem().render(scope) ...
}
}
fn draw_child(&mut self, element: &Arc<Widget>) {
// Add the new child to internal list
self.children.push(element.clone());
// Inform the screen not to render this child independently
element.inject(|w| w.component(NoRenderRoot));
}
// ... as_any, as_any_mut, event methods
}
```
## `Component`: Data and Behavior Extension
A `Component` is a distinct piece of data or behavior that can be *attached* to a `Widget`. Unlike `Element`s, which define the primary visual and structural role, `Component`s augment or modify that role.
* **Responsibility**:
* **Data Storage**: Holding configuration (e.g., `Transform`, `Style`), state (e.g., `State<T>`), or IDs (e.g., `Id`).
* **Behavior Attachment**: Providing specific behaviors, often through closures (e.g., `Handler<E>`).
* **Characteristics**:
* `Component` is a trait (`pub trait Component: Send + Sync`).
* Implemented by structs often defined with the `component!` macro.
* A `Widget` stores `Component`s in a `HashMap<TypeId, Box<dyn Component>>`, meaning only one component of a given concrete type can be attached to a widget at a time (though it can be replaced).
* Components are designed to be independent and reusable.
* **Why a Trait Object?**: Similar to `Element`, `Component`s are stored as trait objects (`Box<dyn Component>`) to allow a `Widget` to hold various, arbitrary component types.
**Examples of Components:**
* `Transform`: Defines layout properties (position, size, padding, margin).
* `Style`: Defines visual properties (background, foreground colors).
* `State<T>`: A reactive state variable. While `State<T>` is a generic struct, OSUI internally uses it as a `Component` for its reactivity.
* `Handler<E>`: A component that allows a widget to respond to specific events.
* `Id`: A simple `usize` for uniquely identifying a widget.
* `Velocity`: Defines movement properties for an element.
## `Widget`: The Container and Lifecycle Manager
The `Widget` enum (`Static(StaticWidget)` or `Dynamic(DynWidget)`) is the central wrapper that brings `Element`s and `Component`s together. It manages their lifecycle, provides access to them, and facilitates interactions like event dispatching and reactive updates.
* **Responsibility**:
* **Aggregation**: Holds one `Box<dyn Element>` and a `HashMap<TypeId, Box<dyn Component>>`.
* **Lifecycle**: For `DynWidget`s, manages the rebuilding process based on dependencies.
* **Access**: Provides methods to `get_elem()` (access the inner `Element`), `get<C>()` (retrieve a `Component`), `set_component<C>()` (add/replace a `Component`).
* **Event Dispatch**: Calls `Handler` components and the inner `Element::event` when an event is dispatched to the widget.
* **Thread Safety**: Uses `Mutex`es internally to ensure safe concurrent access to its `Element` and `Component`s from different threads (e.g., render thread, input thread, background threads updating state).
* **Characteristics**:
* `Widget` is an `enum` with `StaticWidget` and `DynWidget` variants.
* Always wrapped in an `Arc<Widget>` for shared ownership and efficient cloning, reflecting its place in the UI tree.
* `StaticWidget`: Holds a fixed `Element` instance. Its content doesn't change unless explicitly replaced.
* `DynWidget`: Holds a closure that can rebuild its `Element` and initial `Component`s. It tracks `DependencyHandler`s and automatically refreshes when they change.
**Example `Widget` Structure:**
```rust
// Internally in OSUI:
pub enum Widget {
Static(StaticWidget), // Wrapper for fixed content
Dynamic(DynWidget), // Wrapper for reactive content
}
// Simplified StaticWidget structure:
pub struct StaticWidget(
Mutex<BoxedElement>, // The main Element instance
Mutex<HashMap<TypeId, BoxedComponent>>, // Attached components
);
// Simplified DynWidget structure:
pub struct DynWidget(
Mutex<BoxedElement>, // The main Element instance
Mutex<HashMap<TypeId, BoxedComponent>>, // Attached components
Mutex<Box<dyn FnMut() -> WidgetLoad>>, // The function to rebuild the Element/Components
// ... dependencies and inject closure
);
```
## How They Work Together (The Flow)
1. **Declarative UI (`rsx!` macro)**: You define your UI using the `rsx!` macro.
* `Div { ... }` generates a `WidgetLoad` containing a `Div` `Element`.
* `@Transform(...)` adds a `Transform` `Component` to this `WidgetLoad`.
* `%my_state` adds `my_state` (a `State<T>`) as a `DependencyHandler` to the `DynWidget`'s list.
2. **`Screen::draw()`**: The `rsx!` output (an `Rsx` object) is passed to `Screen::draw()` (or `draw_parent`).
* The `Screen` creates `Arc<Widget>` instances (either `Static` or `Dynamic`) from the `WidgetLoad` definitions.
* These `Arc<Widget>` are stored in the `Screen`'s `widgets` list, forming the top level of the UI tree.
* For nested elements, the parent's `Element::draw_child` is called, allowing the parent to store references to its children. Crucially, children rendered by a parent are marked with `NoRenderRoot` to prevent the `Screen` from rendering them independently.
3. **Rendering Loop (`Screen::render`)**:
* For each `Arc<Widget>` in its top-level `widgets` list:
* It checks for `NoRender` or `NoRenderRoot` to decide if this widget should be directly rendered or if its parent is handling it.
* It obtains the widget's `Transform` and `Style` `Component`s and sets them on a fresh `RenderScope`.
* It calls `Extension::render_widget` for all registered extensions.
* It calls `widget.get_elem().render(scope)` to let the `Element` queue its drawing commands.
* It calls `widget.get_elem().after_render(scope)`. This is where container `Element`s iterate through their own children, setting up a new `RenderScope` context for each child and recursively calling `child_widget.get_elem().render` and `after_render`.
* Finally, `scope.draw()` is called to flush the accumulated commands to the terminal.
* For `DynWidget`s, `widget.auto_refresh()` is called, which checks dependencies and rebuilds the `Element` if needed.
4. **Event Handling (`Widget::event`)**:
* When an event occurs (e.g., keyboard input from `InputExtension`), `Screen` dispatches it to all top-level widgets by calling `widget.event(&event)`.
* `Widget::event` first checks if a `Handler<E>` `Component` is present for that event type and calls its closure.
* Then, it calls `widget.get_elem().event(&event)`, allowing the `Element` itself to react.
This robust component model allows for clear separation of concerns, reusability of UI parts, and a powerful reactive system, making it possible to build complex and dynamic terminal user interfaces.
@@ -0,0 +1,115 @@
# Extension System
OSUI's extension system is a powerful and flexible mechanism for adding global, cross-cutting concerns to your application without cluttering individual widget implementations. It allows you to inject custom logic into the `Screen`'s lifecycle and rendering pipeline.
## Why an Extension System?
In UI development, certain functionalities are not specific to a single widget but affect the entire application or many widgets. Examples include:
* **Global Input Handling**: Capturing keyboard events and routing them to relevant widgets.
* **Periodic Updates**: Driving animations or time-based logic across the UI.
* **Custom Debugging/Logging**: Observing the rendering process or widget tree.
* **Theming/Styling Overrides**: Applying consistent visual modifications dynamically.
* **Data Persistence/Loading**: Interacting with external systems at application startup/shutdown.
Instead of scattering this logic throughout your `main` function or within every widget, the extension system provides a centralized, modular approach.
## The `Extension` Trait
The core of the system is the `Extension` trait, which defines a set of lifecycle hooks that the `Screen` will call at specific points.
```rust
pub trait Extension {
/// Called once when the screen starts running.
fn init(&mut self, _screen: Arc<Screen>) {}
/// Called when the screen is being closed.
fn on_close(&mut self, _screen: Arc<Screen>) {}
/// Called for each widget before its `render` method is invoked.
fn render_widget(&mut self, _scope: &mut RenderScope, _widget: &Arc<Widget>) {}
}
```
* **`init(&mut self, screen: Arc<Screen>)`**:
* **When**: Called exactly once when `Screen::run()` is invoked, before the main rendering loop begins.
* **Purpose**: Ideal for one-time setup tasks like spawning background threads (e.g., for input polling or tick generation), initializing external resources, or setting up global state the extension will manage. The `Arc<Screen>` allows the extension to interact back with the screen (e.g., closing it, adding new widgets).
* **`on_close(&mut self, screen: Arc<Screen>)`**:
* **When**: Called exactly once when `Screen::close()` is invoked and the main rendering loop has exited.
* **Purpose**: Cleanup. Restore terminal settings (like `InputExtension` disabling raw mode), release resources, save data, or perform final logging.
* **`render_widget(&mut self, scope: &mut RenderScope, widget: &Arc<Widget>)`**:
* **When**: Called for every top-level `Arc<Widget>` in the `Screen`'s `widgets` list, during each frame's `Screen::render()` cycle. It's called *before* the widget's `Element::render` method.
* **Purpose**: This is a powerful hook for inspecting or modifying the rendering context (`RenderScope`) or the `Widget` itself.
* **Inspection**: You can use `widget.get::<C>()` to check a widget's components (e.g., its `Transform` or `Style`).
* **Modification**: You can use `widget.set_component(c)` to dynamically add or change components (e.g., `VelocityExtension` modifies `Transform`). You can also directly modify the `RenderScope` (e.g., adding an offset, changing its style, or drawing overlay content).
* **Filtering/Debugging**: Skip rendering certain widgets based on custom logic, or log their state.
## How Extensions are Integrated
1. **Instantiation**: You create an instance of your struct that implements `Extension`.
2. **Registration**: You register the instance with your `Screen` using `screen.extension(my_extension_instance)`. This typically happens at the start of your `main` function.
* The `Screen` stores `Arc<Mutex<Box<dyn Extension>>>` to allow multiple extensions, shared access, and dynamic dispatch.
3. **Execution**: The `Screen`'s main `run()` and `render()` methods are hardwired to call the respective `Extension` trait methods at the appropriate times.
## Example: Custom Logging Extension
```rust
use osui::prelude::*;
use std::sync::Arc;
pub struct CustomLoggingExtension;
impl Extension for CustomLoggingExtension {
fn init(&mut self, screen: Arc<Screen>) {
println!("[LOG] CustomLoggingExtension initialized for screen {:p}", Arc::as_ptr(&screen));
}
fn on_close(&mut self, screen: Arc<Screen>) {
println!("[LOG] CustomLoggingExtension closing for screen {:p}", Arc::as_ptr(&screen));
}
fn render_widget(&mut self, scope: &mut RenderScope, widget: &Arc<Widget>) {
// Log the coordinates and size of every widget about to be rendered
let raw_transform = scope.get_transform(); // Get the already resolved raw transform
println!(
"[LOG] Rendering widget {:p} at ({}, {}) size ({}, {})",
Arc::as_ptr(widget),
raw_transform.x, raw_transform.y,
raw_transform.width, raw_transform.height
);
// Example: Apply a global offset for debugging
// let mut current_transform = scope.get_transform_mut();
// current_transform.x += 1;
// current_transform.y += 1;
}
}
fn main() -> std::io::Result<()> {
let screen = Screen::new();
screen.extension(InputExtension); // Always useful for interaction
screen.extension(CustomLoggingExtension); // Register our custom extension
rsx! {
Div { "Hello" }
@Transform::new().x(10).y(5);
Div { "World" }
}.draw(&screen);
screen.run()
}
```
When you run this, you'll see console output from `CustomLoggingExtension` as the screen initializes, renders each widget, and closes.
## Benefits of the Extension System
* **Modularity**: Keeps distinct functionalities separate, improving code organization.
* **Reusability**: Extensions can be easily reused across different OSUI applications.
* **Flexibility**: Allows injection of custom behavior without modifying OSUI's core library code.
* **Separation of Concerns**: UI rendering logic is in `Element`s, state is in `State`s, and cross-cutting behaviors are in `Extension`s.
By leveraging the extension system, developers can build highly customized and feature-rich terminal applications with a clean and maintainable codebase.
@@ -0,0 +1,100 @@
# Layout System
OSUI's layout system is designed for flexibility and responsiveness in a grid-based terminal environment. It abstracts away absolute pixel calculations by allowing developers to define layout rules declaratively using `Transform`, `Position`, and `Dimension` components. The system then resolves these rules into concrete coordinates and sizes during the rendering phase.
## Core Principles
1. **Parent-Child Relationship**: Layout is always relative to the parent container. A child element's position and size are determined by its own `Transform` and the dimensions of its immediate parent.
2. **Two-Phase Calculation**:
* **Dimension Resolution**: First, `Dimension` rules (`Full`, `Content`, `Const`) are applied to determine the concrete width and height of an element.
* **Position Resolution**: Second, `Position` rules (`Const`, `Center`, `End`) are applied to determine the concrete `x` and `y` coordinates, using the newly resolved dimensions.
3. **Content-Based Sizing**: Elements can automatically size themselves to fit their content (`Dimension::Content`), allowing for dynamic UI.
4. **Padding and Margin**: Distinct concepts for internal spacing (padding) and external offset (margin).
## Key Components of Layout
### `Transform`
The central component for defining layout rules. It encapsulates all the properties that influence an element's position and size.
* `x: Position`, `y: Position`: Define alignment along horizontal and vertical axes.
* `width: Dimension`, `height: Dimension`: Define sizing along horizontal and vertical axes.
* `px: u16`, `py: u16`: **Padding** - internal space between the element's border and its content/children. This increases the overall size of the element.
* `mx: i32`, `my: i32`: **Margin** - an offset applied *after* the element's position is calculated. This creates space *around* the element relative to its parent's edges. Can be negative for overlap.
(See [Reference: Style API - Transform](./../reference/style_api.md#transform-component) for full details)
### `Position` Enum
Determines the `x` or `y` coordinate.
* `Const(u16)`: Absolute coordinate from the top-left (0,0).
* `Center`: Centers the element within the parent's available space on that axis.
* `End`: Aligns the element to the right or bottom edge of the parent.
(See [Reference: Style API - Position](./../reference/style_api.md#position-enum) for full details)
### `Dimension` Enum
Determines the `width` or `height`.
* `Full`: Takes up all available space from the parent on that axis.
* `Content`: Sizes itself to fit its content (text) or children. This is dynamic.
* `Const(u16)`: Fixed size in terminal cells.
(See [Reference: Style API - Dimension](./../reference/style_api.md#dimension-enum) for full details)
### `RawTransform` Struct
This is the internal, resolved representation of a `Transform`. After all calculations, a `Transform`'s declarative rules are converted into a `RawTransform` with concrete `u16` values for `x`, `y`, `width`, `height`, `px`, `py`. This `RawTransform` is then used by the `RenderScope` for actual drawing.
(See [Reference: Style API - RawTransform](./../reference/style_api.md#rawtransform-struct) for full details)
## Layout Calculation Flow (Simplified)
The layout process happens during the `Screen::render()` cycle, primarily managed by the `RenderScope` and parent `Element::after_render` methods.
1. **Initialize `RenderScope`**: For each top-level widget (or for each child within a container element), a new or cleared `RenderScope` is prepared. Its `parent_width` and `parent_height` are set to the available space (either terminal size or the parent element's resolved size).
2. **Apply `Transform` (Phase 1: Dimensions)**:
* The `Transform` component attached to the current widget is accessed.
* `Transform::use_dimensions()` is called. This method takes the `RenderScope`'s `parent_width` and `parent_height` and resolves the `width` and `height` `Dimension` rules into concrete `u16` values.
* If `Dimension::Full`, it takes the `parent_width`/`height`.
* If `Dimension::Const(n)`, it takes `n`.
* If `Dimension::Content`, it's initially set to `0` or left unchanged; its final value will be determined by the `Element::render` method (based on text size) or by `Element::after_render` (based on children's size).
3. **Element Renders Content (`Element::render`)**:
* The `Element::render` method is called. It uses `RenderScope::draw_text()`, `draw_rect()`, etc., to queue drawing commands.
* Crucially, these `draw_*` methods automatically update the `RenderScope`'s internal `RawTransform.width` and `height` to be at least the size of the drawn content. This is how `Dimension::Content` gets its actual size.
* `Element`s can also explicitly use `scope.use_area(w, h)` to hint their minimum desired size.
4. **Apply `Transform` (Phase 2: Position)**:
* After the `Element::render` has potentially updated the `RawTransform`'s `width` and `height` (for `Content` dimensions), `Transform::use_position()` is called.
* This method takes the now-resolved `RawTransform.width` and `height`, the `RenderScope`'s `parent_width`/`height`, and the `Transform`'s `mx`/`my` (margins) to calculate the final `RawTransform.x` and `y`.
* `Position::Const(n)`: Sets `x` or `y` to `n`.
* `Position::Center`: Calculates `(parent_size - element_size) / 2`.
* `Position::End`: Calculates `parent_size - element_size`.
* `mx`, `my` are then added or subtracted to these calculated base positions.
5. **Child Rendering (`Element::after_render` for containers)**:
* For container elements (`Div`, `FlexRow`, `FlexCol`), their `after_render` method then steps in.
* Before rendering each child, the parent container performs a critical step: it sets the `RenderScope`'s `parent_width` and `parent_height` to *its own* newly resolved `RawTransform.width` and `height`. This creates a new layout context for the child.
* The parent also shifts the `RawTransform.x` and `y` of the child by its own resolved `x`, `y`, and `px`, `py` (padding), ensuring children are drawn relative to the parent's padded content area.
* The entire process (steps 2-5) recursively repeats for each child.
* After all children are rendered, the parent element might update its *own* `RawTransform.width` and `height` based on the maximum extent of its children, especially if its `Dimension` was `Content`.
6. **Final Draw (`RenderScope::draw`)**: Once all elements and their children have queued their commands and positions are finalized, `RenderScope::draw()` translates these `RawTransform`-based instructions into actual terminal ANSI escape codes and prints them.
## Flex Layouts (`FlexRow`, `FlexCol`)
`FlexRow` and `FlexCol` elements implement a simpler sequential layout model on top of the core `Transform` system.
* `FlexRow` (Column-like): Children are stacked vertically. Each child's `y` position is implicitly determined by the previous child's height plus the `gap`. Its `x` position is usually `0` relative to the `FlexRow`.
* `FlexCol` (Row-like): Children are laid out horizontally. Each child's `x` position is implicitly determined by the previous child's width plus the `gap`. Its `y` position is usually `0` relative to the `FlexCol`.
These elements internally manage the cumulative position (`v` variable in source) for their children to ensure correct sequential placement.
By understanding this hierarchical and two-phase layout resolution, you can effectively predict and control how your OSUI elements will appear on the terminal.
@@ -0,0 +1,116 @@
# Reactive Updates
OSUI incorporates a reactive programming model to automatically update parts of the UI in response to changes in application state. This system is built around `State<T>`, the `DependencyHandler` trait, and the `DynWidget` type, all orchestrated by the `Screen`'s rendering loop.
## The Problem: Manual UI Updates
In traditional UI frameworks without reactivity, when data changes, you would manually:
1. Identify which UI elements depend on that data.
2. Retrieve those elements.
3. Update their properties or re-render them explicitly.
This can quickly become complex and error-prone in dynamic applications.
## The Solution: OSUI's Reactivity
OSUI automates this process. When a `State<T>` value is modified, any `DynWidget` that has declared that `State<T>` as a *dependency* is automatically flagged for a rebuild. During the next render cycle, these flagged widgets are re-evaluated, reflecting the new data.
## Key Components
### 1. `State<T>`: The Reactive Data Holder
`State<T>` is a generic struct that wraps your application data (`T`) and provides mechanisms for thread-safe access and change tracking.
* **Internal Structure**: `Arc<Mutex<Inner<T>>>`
* `Arc`: Allows `State` instances to be cheaply cloned and shared across multiple threads and widgets without ownership issues.
* `Mutex`: Ensures thread-safe access to the underlying `value` and internal counters.
* `Inner<T>`: Contains `value: T`, `dependencies: usize` (how many widgets listen), and `changed: usize` (how many dependents need a refresh).
* **Modification**:
* `my_state.set(new_value)`: Replaces the value and marks it as changed.
* `**my_state.get() = new_value` (or `my_state.get().deref_mut().field = new_value`): Mutates the value directly through a `MutexGuard`. The `DerefMut` implementation automatically marks the state as changed by setting `inner.changed = inner.dependencies`.
* **Dependency Tracking**: Implements the `DependencyHandler` trait, allowing `DynWidget`s to register themselves.
(See [Reference: State API](../reference/state_api.md) for more details)
### 2. `DependencyHandler` Trait
A trait that `State<T>` (and potentially other future reactive types) implements. It defines two crucial methods:
* `add()`: Called when a `DynWidget` first registers itself as a listener to this dependency. It increments an internal counter of listeners.
* `check()`: Called by `DynWidget` during its `auto_refresh` cycle. It decrements the `changed` counter and returns `true` if there are still pending changes to be processed by a listener. This ensures each listener processes a change only once per update cycle.
(See [Reference: State API - DependencyHandler Trait](../reference/state_api.md#dependencyhandler-trait) for more details)
### 3. `DynWidget`: The Reactive Widget Wrapper
`DynWidget` is one of the two variants of the `Widget` enum (the other being `StaticWidget`). It is designed to be rebuilt when its dependencies change.
* **Internal Structure**: Holds:
* A `Mutex<Box<dyn FnMut() -> WidgetLoad>>`: This is the *original closure* that built the widget. When a refresh is needed, this closure is re-executed to generate a new `WidgetLoad`.
* A `Mutex<Vec<Box<dyn DependencyHandler>>>`: A list of all `State<T>` instances (or other `DependencyHandler`s) this `DynWidget` is listening to.
* **Key Methods**:
* `dependency(d: D)`: Registers a `DependencyHandler` with this widget. This also calls `d.add()`.
* `refresh()`: Forces the widget to rebuild immediately by re-executing its creation closure.
* `auto_refresh()`: The core of reactivity. It iterates through all registered `DependencyHandler`s. If `handler.check()` returns `true` for any of them, it calls `refresh()` to rebuild the widget.
(See [Reference: Widget API - DynWidget Struct](../reference/widget_api.md#dynwidget-struct) for more details)
## How Reactive Updates Work in Practice
Let's trace the flow with a simple counter example:
1. **Define State**:
```rust
let count = use_state(0);
```
This creates an `Arc<Mutex<Inner<i32>>>` where `dependencies` and `changed` are initially `0`.
2. **Define Reactive UI (`rsx!`):**
```rust
rsx! {
%count // Declare dependency on `count` state
Div {
"Current count: {count}" // `State<T>` implements `Display`
}
}.draw(&screen);
```
* The `rsx!` macro sees `%count`. This tells it to create a `DynWidget` for the `Div`.
* It clones `Arc<State<i32>>` (the `count` variable) and captures it in the `DynWidget`'s creation closure.
* It calls `dyn_widget.dependency(count.clone())`. This calls `count.add()`, incrementing `count.inner.dependencies` to `1`.
3. **Modify State**:
```rust
// In a separate thread or event handler:
**count.get() += 1;
```
* `count.get()` locks the `Mutex` and returns a `MutexGuard<Inner<i32>>`.
* `**count.get()` performs a mutable dereference to `value: i32`.
* Crucially, `Inner<T>::deref_mut()` is called, which then sets `count.inner.changed = count.inner.dependencies` (which is `1` in this case).
* When the `MutexGuard` is dropped, the `Mutex` is released.
4. **Render Loop (`Screen::render`):**
* During the next animation frame, `Screen::render` iterates through its top-level widgets.
* It encounters our `DynWidget` for the `Div`.
* It calls `dyn_widget.auto_refresh()`.
* `auto_refresh()` iterates through its registered dependencies (only `count` in this case).
* It calls `count.check()`.
* `count.inner.changed` is `1`.
* `count.check()` decrements `count.inner.changed` to `0` and returns `true`.
* Since `check()` returned `true`, `dyn_widget.refresh()` is called.
* `refresh()` re-executes the `DynWidget`'s original creation closure.
* The closure captures the `count` state (which now has the incremented value).
* A *new* `Div` `Element` instance is created with the updated string: `"Current count: 1"`.
* This new `Element` and its initial components replace the old ones inside the `DynWidget`'s `Mutex`es.
* The `Screen` then proceeds to render the updated `Div` with the correct text.
This cycle of state modification, dependency tracking, and automatic widget rebuilding forms the core of OSUI's reactivity, allowing you to focus on defining your UI's structure and behavior without constantly managing manual updates.
## Performance Considerations
* **Granularity**: OSUI re-renders the *entire widget* (and its children) when any of its dependencies change. For large widgets with many children, consider breaking them into smaller, more granular `DynWidget`s to minimize re-renders to only the affected parts of the UI tree.
* **Frequent Updates**: If a `State` is updated extremely frequently (e.g., every millisecond), it will trigger a refresh on every frame that `auto_refresh` is called, which might be acceptable depending on complexity.
* **`get_dl()` vs. `get()`**: Use `get_dl()` when you only need to read a cloned value and do not intend to modify the state or hold the lock for an extended period. Use `get()` (and `deref_mut()`) when you need to modify the state.
@@ -0,0 +1,128 @@
# Rendering Pipeline
OSUI's rendering pipeline orchestrates how your declarative UI definitions are translated into actual terminal output. It involves several distinct stages and components working in concert to efficiently draw frames to the screen.
## Overview of the Pipeline
The rendering process is driven by the `Screen::run()` method, which enters a continuous loop. Within each iteration of this loop (a "frame"), the `Screen::render()` method executes the core pipeline:
1. **Clear Screen**: The entire terminal is cleared to prepare for a new frame.
2. **Initialize Render Scope**: A `RenderScope` is created or reset for each top-level widget. This scope provides the drawing context for the current element.
3. **Extension Pre-Render Hook**: Registered `Extension`s can inject logic *before* a widget's main rendering via their `render_widget` method.
4. **Element Rendering (`Element::render`)**: The widget's root `Element` is asked to draw its own content into the `RenderScope`. This queues drawing commands.
5. **Transform Resolution**: The `Transform` component's rules (`Position`, `Dimension`) are applied to calculate the absolute `RawTransform` (position, size) of the element within the `RenderScope`.
6. **Child Rendering (`Element::after_render` for containers)**: If the current `Element` is a container (like `Div`, `FlexRow`, etc.), its `after_render` method recursively initiates the rendering pipeline for each of its children. This involves setting up a new `RenderScope` context for each child.
7. **Flush to Terminal (`RenderScope::draw`)**: Once all drawing commands for an element (and its children) are queued within its `RenderScope`, the `RenderScope::draw()` method translates these commands into ANSI escape codes and writes them to the terminal.
8. **Extension Post-Render/Cleanup Hook**: The `Element::after_render` method is also used for cleanup or final adjustments after drawing children.
9. **Reactivity Check (`Widget::auto_refresh`)**: For `DynWidget`s, a check is performed to see if any `State` dependencies have changed. If so, the widget is marked for rebuilding for the *next* frame.
10. **Throttle**: The loop pauses briefly to control the frame rate.
## Key Components in the Pipeline
### 1. `Screen`
The orchestrator. It holds the list of top-level `Widget`s, manages extensions, and drives the main rendering loop (`run()`, `render()`).
* Initializes terminal raw mode.
* Calls `Extension::init` on startup.
* Manages the frame rate using `std::thread::sleep`.
* Iterates through top-level widgets, initiating their rendering.
* Calls `Extension::on_close` and restores terminal state on shutdown.
(See [Reference: Screen API](../reference/screen_api.md) for more details)
### 2. `Widget` (`Arc<Widget>`)
The container for an `Element` and its `Component`s. It's the unit passed around the UI tree.
* `StaticWidget`: Its `Element` is instantiated once.
* `DynWidget`: Its `Element` can be re-instantiated (rebuilt) if its dependencies change. This rebuild happens *before* its `render` method is called in a subsequent frame.
* Provides access to its `Element` (`get_elem()`) and `Component`s (`get()`, `set_component()`).
(See [Reference: Widget API](../reference/widget_api.md) for more details)
### 3. `Element` (`Box<dyn Element>`)
The actual drawable logic. Each `Element` implementation defines how it appears.
* `render(&mut self, scope: &mut RenderScope)`: Puts drawing instructions into the `RenderScope`.
* `after_render(&mut self, scope: &mut RenderScope)`: For containers, this is where children are processed and recursively rendered. It's also where the element might determine its final `Dimension::Content` size based on children.
* `draw_child(&mut self, element: &Arc<Widget>)`: Called by `rsx!` to establish parent-child relationships. Children processed by a parent are marked `NoRenderRoot` to prevent the `Screen` from rendering them independently.
(See [Reference: Widget API - Element Trait](../reference/widget_api.md#element-trait) and [Guides: Custom Elements](../guides/custom_elements.md) for more details)
### 4. `RenderScope`
The drawing context for a single element. It's a mutable structure that holds:
* The element's current `RawTransform` (absolute position and size).
* The element's current `Style`.
* The `parent_width` and `parent_height` (critical for relative layout calculations).
* A `render_stack` of primitive drawing commands (text, rectangles).
* `set_transform()`: Uses a `Transform` component to calculate the `RawTransform`.
* `set_style()`: Applies a `Style` component.
* `draw_text()`, `draw_rect()`, etc.: Queue drawing commands. These also update the scope's dimensions for `Dimension::Content` sizing.
* `draw()`: Flushes all queued commands and the background style to the terminal using ANSI escape codes.
* `clear()`: Resets the scope for the next element.
* `set_parent_size()`: Crucial for container elements to establish the bounding box for their children.
(See [Reference: RenderScope API](../reference/render_scope_api.md) for more details)
### 5. `Transform` and `Style` Components
These components, attached to a `Widget`, provide the declarative rules for layout and appearance.
* `Transform`: Contains `Position` and `Dimension` rules, plus `margin` and `padding`. These are resolved into `RawTransform` by `RenderScope`.
* `Style`: Contains `Background` and `foreground` color. Applied to `RenderScope`.
(See [Reference: Style API](../reference/style_api.md) for more details)
### 6. `Extension`s
Extensions are hooks into the pipeline.
* `Extension::render_widget(scope, widget)`: Called for each top-level widget *before* its `Element::render`. Allows extensions to inspect or modify the `RenderScope` or widget before rendering.
(See [Reference: Extensions API](../reference/extensions_api.md) for more details)
## Flow Diagram (Conceptual)
```mermaid
graph TD
A[Screen::run() Loop] --> B{Frame};
B --> C[utils::clear()];
C --> D{For each top-level Arc<Widget> w};
D --> E{Create/Clear RenderScope (rs)};
E --> F{Set rs.parent_size};
F --> G{Get w's Transform & Style Components};
G --> H{rs.set_transform(w.transform_comp)};
H --> I{rs.set_style(w.style_comp)};
I --> J{For each Extension ext};
J --> K[ext.render_widget(rs, w)];
K --> L[w.get_elem().render(rs)];
L --> M{w.get_elem().after_render(rs)};
M --> N{rs.draw()};
N --> O{w.auto_refresh() if DynWidget};
O --> P{Check for screen.close()};
P --> Q[Wait 28ms];
Q --> B;
subgraph Element/Container Flow in M
M_start[Element::after_render(scope)] --> M1{For each child_widget};
M1 --> M2[child_scope = scope.clone()];
M2 --> M3[child_scope.clear()];
M3 --> M4[child_scope.set_parent_size(self_resolved_width, self_resolved_height)];
M4 --> M5[child_scope.set_transform(child_transform_comp)];
M5 --> M6[child_scope.set_style(child_style_comp)];
M6 --> M7[child_widget.get_elem().render(child_scope)];
M7 --> M8[child_widget.get_elem().after_render(child_scope)];
M8 --> M9[child_scope.draw()];
M9 --> M10{Next child / Return};
end
```
Understanding this pipeline is crucial for debugging rendering issues, optimizing performance, and building advanced custom elements or extensions that interact deeply with OSUI's drawing logic.
@@ -0,0 +1,158 @@
# RSX Internals
The `rsx!` macro is a cornerstone of OSUI, providing a declarative, JSX-like syntax for building UI trees. While it appears to directly construct widgets, it actually expands into an intermediate representation handled by the `frontend` module. This allows for powerful features like static/dynamic differentiation and dependency tracking.
## The Problem: Expressing UI Trees in Rust
Directly constructing OSUI widgets in Rust code can be verbose:
```rust
use osui::prelude::*;
let my_div = Arc::new(Widget::Dynamic(DynWidget::new(|| {
WidgetLoad::new(Div::new())
.component(Transform::new().center())
.set_component(Style { background: Background::Solid(0x333333), foreground: Some(0xFFFFFF) })
})));
// How to add children? How to declare dependencies? How to make it static?
```
The `rsx!` macro aims to solve this by providing a compact, expressive syntax.
## `frontend` Module: The RSX Intermediate Representation
The `frontend` module defines the data structures that the `rsx!` macro generates. It acts as a bridge between the high-level declarative syntax and the low-level `Widget` creation.
### `Rsx` Struct
`Rsx` is essentially a wrapper around a `Vec<RsxElement>`. It represents a collection of UI elements, typically a list of siblings or children of a parent element.
```rust
pub struct Rsx(pub Vec<RsxElement>);
```
* `Rsx::draw()`: The entry point to take an `Rsx` tree and render it onto the `Screen`.
* `Rsx::draw_parent()`: Used recursively to draw children, passing the `Arc<Widget>` of their parent.
* `Rsx::create_element()`: Adds a dynamic element definition to the `Rsx` vector.
* `Rsx::create_element_static()`: Adds a static element definition to the `Rsx` vector.
* `Rsx::expand()`: Allows merging another `Rsx` tree into the current one.
### `RsxElement` Enum
`RsxElement` is the core enum that represents a single node in the intermediate UI tree. It can be either a static or a dynamic element.
```rust
pub enum RsxElement {
/// A static widget with children.
Element(StaticWidget, Rsx),
/// A dynamically generated widget (e.g., with state) with associated dependencies and children.
DynElement(
Box<dyn FnMut() -> WidgetLoad + Send + Sync>, // The closure that builds the WidgetLoad
Vec<Box<dyn DependencyHandler>>, // Dependencies for dynamic updates
Rsx, // Its children
),
}
```
* **`RsxElement::Element`**: Corresponds to elements declared with `static` in `rsx!`. It directly holds a `StaticWidget` instance and its `Rsx` children.
* **`RsxElement::DynElement`**: Corresponds to elements without `static` or with `%dependency` in `rsx!`. It holds:
* A `Box<dyn FnMut() -> WidgetLoad>`: This is the actual *code* (a closure) that will be executed later to create the `Element` and its initial `Component`s. This closure captures any necessary environment variables (like `State` clones).
* `Vec<Box<dyn DependencyHandler>>`: A list of the dependencies declared with `%`. When `DynWidget::auto_refresh()` is called, these handlers are checked to decide if the widget needs rebuilding.
* `Rsx`: The children of this dynamic element.
## How `rsx!` Expands
The `rsx!` macro is implemented using multiple `macro_rules!` rules that parse different patterns (text, element types, properties, components, dependencies, children). It uses an internal recursive macro, `rsx_inner!`, to build the `Rsx` structure.
Let's look at a simplified conceptual expansion:
```rust
// Example rsx! input:
rsx! {
@Transform::new().center();
%my_state
Div {
"Hello: {my_state}"
static Input { }
}
}
```
This *conceptually* expands to something like this (highly simplified, actual expansion is more complex with error handling and exact types):
```rust
// Conceptual Expansion of rsx!
{
let mut r = osui::frontend::Rsx(Vec::new());
// Processing the 'Div' element
r.create_element(
// The closure to build the WidgetLoad for Div
{
let my_state = my_state.clone(); // Clone the Arc<State<T>> for capture
move || {
osui::widget::WidgetLoad::new(osui::elements::Div::new())
// Attach Transform component
.component(osui::style::Transform::new().center())
}
},
// Dependencies list
vec![
Box::new(my_state.clone()) as Box<dyn osui::state::DependencyHandler>
],
// Children of the Div
osui::frontend::Rsx(vec![
// Processing "Hello: {my_state}" text
osui::frontend::RsxElement::DynElement(
{
let my_state = my_state.clone();
move || {
osui::widget::WidgetLoad::new(
format!("Hello: {}", my_state) // Format string dynamically
)
}
},
vec![
Box::new(my_state.clone()) as Box<dyn osui::state::DependencyHandler>
],
osui::frontend::Rsx(Vec::new()) // No children for text
),
// Processing `static Input { }`
osui::frontend::RsxElement::Element(
osui::widget::StaticWidget::new(Box::new(osui::elements::Input::new())),
osui::frontend::Rsx(Vec::new()) // No children for Input here
),
])
);
r // The final Rsx object
}
```
## How the `Rsx` Tree is Rendered
When you call `.draw(&screen)` on an `Rsx` object:
1. `Rsx::draw_parent()` is called, which iterates through its `Vec<RsxElement>`.
2. For each `RsxElement`:
* If it's `RsxElement::Element(static_widget, children_rsx)`:
* An `Arc<Widget::Static(static_widget)>` is created.
* It's added to the screen's top-level widgets via `screen.draw_widget()`.
* `children_rsx.draw_parent(screen, Some(parent_widget_arc))` is recursively called.
* If it's `RsxElement::DynElement(build_fn, dependencies, children_rsx)`:
* `screen.draw_box_dyn(build_fn)` is called. This immediately executes `build_fn` *once* to get the initial `WidgetLoad`, creates an `Arc<Widget::Dynamic(...)>`, and adds it to the screen.
* All `dependencies` are then registered with the newly created `DynWidget` using `widget.dependency_box()`.
* `children_rsx.draw_parent(screen, Some(parent_widget_arc))` is recursively called.
This two-stage process (macro expansion to `Rsx` then `Rsx::draw` to `Widget`s) allows OSUI to efficiently manage static vs. dynamic content and set up the reactivity system.
## Performance Implications
* **Static vs. Dynamic**: The `static` keyword in `rsx!` is critical for performance. `static` elements expand into `RsxElement::Element`, which directly holds a `StaticWidget`. This `StaticWidget` is instantiated only once. Dynamic elements (`RsxElement::DynElement`) hold a *closure* that is re-executed every time the widget needs to refresh. Use `static` whenever a UI part doesn't need to dynamically change its `Element` type or internal `Element` state based on external `State`s.
* **Closure Captures**: Be mindful of what is captured by closures in dynamic elements. Cloning `Arc`s (`State<T>`, `Arc<Screen>`, etc.) is efficient. Capturing large structs by value can increase memory usage on each re-render.
Understanding the internal representation generated by `rsx!` helps in optimizing your UI structure and debugging complex reactive behaviors.
@@ -0,0 +1,221 @@
# Elements API Reference
OSUI provides a set of built-in `Element` implementations that serve as the fundamental visual components for building your terminal user interfaces. This section details their purpose and configurable properties.
All elements implicitly implement the `Element` trait and can be used within the `rsx!` macro.
## Basic Elements
### `String`
Represents simple plain text.
```rust
impl Element for String
```
* **Properties**: None directly. Content is the string itself.
* **Usage**:
```rust
rsx! {
"Hello, World!"
}
```
### `(String, u32)` (Colored Text)
Represents text with a specific 24-bit RGB foreground color.
```rust
impl Element for (String, u32)
```
* **Properties**:
* `0`: The `String` content.
* `1`: The `u32` RGB color (e.g., `0xFF0000` for red).
* **Usage**:
```rust
rsx! {
("This text is red.", 0xFF0000)
}
```
## Container Elements
Container elements manage and render their children. They typically take care of positioning children relative to themselves.
### `Div`
A basic rectangular container element. It positions its children at their specified coordinates relative to the div's top-left corner and expands its own `Dimension::Content` size to fit them.
```rust
pub struct Div {
// children: Vec<Arc<Widget>>, // Internal
// size: (u16, u16), // Internal calculated size
}
```
* **Properties**: None specific to `Div`. Layout and style are controlled by attached `Transform` and `Style` components.
* **Usage**:
```rust
rsx! {
@Transform::new().padding(1, 1);
@Style { background: Background::Solid(0x333333) };
Div {
"Content inside a div."
@Transform::new().x(2).y(2);
Div { "Nested div offset by (2,2)" }
}
}
```
### `FlexRow`
A container element that arranges its children vertically, one after another, like a column. It expands its height to fit children and can apply a uniform `gap` between them.
```rust
pub struct FlexRow {
pub gap: u16,
// children: Vec<Arc<Widget>>, // Internal
// size: (u16, u16), // Internal calculated size
}
```
* **Properties**:
* `gap: u16`: The number of empty cells between each child element. Defaults to `0`.
* **Usage**:
```rust
rsx! {
FlexRow, gap: 1, {
"First Item"
("Second Item (colored)", 0x00FFFF)
Div { "Third Item (a div)" }
}
}
```
This will render "First Item", then a 1-cell gap, then "Second Item", then a 1-cell gap, etc., all stacked vertically.
### `FlexCol`
A container element that arranges its children horizontally, one after another, like a row. It expands its width to fit children and can apply a uniform `gap` between them.
```rust
pub struct FlexCol {
pub gap: u16,
// children: Vec<Arc<Widget>>, // Internal
// size: (u16, u16), // Internal calculated size
}
```
* **Properties**:
* `gap: u16`: The number of empty cells between each child element. Defaults to `0`.
* **Usage**:
```rust
rsx! {
FlexCol, gap: 2, {
"Left Item"
("Middle Item (colored)", 0xFFCC00)
Div { "Right Item (a div)" }
}
}
```
This will render "Left Item", then a 2-cell gap, then "Middle Item", etc., all laid out horizontally.
### `Paginator`
A container element that displays only one of its children at a time. It provides built-in keyboard navigation to cycle through its children.
```rust
pub struct Paginator {
// children: Vec<Arc<Widget>>, // Internal
// size: (u16, u16), // Internal calculated size
// index: usize, // Internal current page index
}
```
* **Properties**: None specific to `Paginator`.
* **Internal Behavior**:
* Handles `crossterm::event::KeyCode::Tab` to advance to the next child. If at the last child, it wraps to the first.
* Handles `crossterm::event::KeyCode::BackTab` (Shift+Tab) to go to the previous child. If at the first child, it wraps to the last.
* **Usage**:
```rust
rsx! {
Paginator {
Div { "Page 1 Content" }
FlexCol { "Page 2: Item A", "Item B" }
"Page 3: Just some text."
}
}
```
## Form Elements
### `Input`
An interactive element that allows users to type text. It manages its own internal state, cursor position, and handles basic text editing key presses.
```rust
pub struct Input {
pub state: State<String>, // Reactive state holding the input string
// cursor: usize, // Internal cursor position
}
```
* **Properties**:
* `state: State<String>`: A reactive state variable that holds the current text content of the input field. You can pass your own `State<String>` to bind to it, or `Input::new()` creates a default one.
* **Internal Behavior**:
* Handles `crossterm::event::KeyEvent` for:
* `KeyCode::Char`: Inserts character at cursor.
* `KeyCode::Backspace`: Deletes character before cursor.
* `KeyCode::Delete`: Deletes character at cursor.
* `KeyCode::Left`, `KeyCode::Right`: Moves cursor.
* **Usage**:
```rust
use osui::prelude::*;
let my_input_state = use_state(String::from("Initial Text"));
rsx! {
@Transform::new().dimensions(30, 1).padding(1, 0);
@Style { background: Background::Outline(0x888888), foreground: Some(0xFFFFFF) };
Input, state: my_input_state, { }
}
// You can access my_input_state.get_dl() elsewhere to get the current value.
```
Note that while the `Input` element has a `state` field, it's not declared as a dependency with `%` in `rsx!`. This is because `Input` internally manages its own `State<String>` and triggers its own re-renders when the text changes. You would use `%` if another widget needed to react to changes in `my_input_state`.
## Display Elements
### `Heading`
An element that renders text using FIGlet ASCII art fonts.
```rust
pub struct Heading {
pub font: FIGfont, // The FIGlet font to use
pub smooth: bool, // Whether to replace '-' with '─' and '|' with '│'
// children: Vec<Arc<Widget>>, // Internal: stores text children
}
```
* **Properties**:
* `font: FIGfont`: The FIGlet font instance to use. You typically use `FIGfont::standard().unwrap()` or load a custom font.
* `smooth: bool`: If `true`, replaces standard ASCII box drawing characters with Unicode smooth box drawing characters for a cleaner look. Defaults to `false`.
* **Usage**:
```rust
use figlet_rs::FIGfont; // Import for `FIGfont` type
rsx! {
// Default standard font, not smooth
Heading { "OSUI" }
// Using a custom font and smoothing
Heading, font: FIGfont::big().unwrap(), smooth: true, { "Big Title" }
}
```
Note that `Heading` expects `String` children (or `(String, u32)` children) for its text content. It concatenates all string children and renders them as one FIGlet text block.
These built-in elements provide a solid foundation for constructing diverse and interactive terminal user interfaces with OSUI.
@@ -0,0 +1,209 @@
# Extensions API Reference
OSUI's extension system provides a powerful mechanism for adding global behaviors, cross-cutting concerns, or custom rendering logic to your application. Extensions are independent units that can hook into the `Screen`'s lifecycle and rendering pipeline.
## `Extension` Trait
The core trait that all extensions must implement.
```rust
pub trait Extension {
/// Called once when the screen starts running, before the main loop begins.
///
/// Useful for setting up resources, spawning threads, or initializing global state.
fn init(&mut self, _screen: Arc<Screen>) {}
/// Called when the screen is being closed, after the main loop has exited.
///
/// Useful for cleanup, restoring terminal state, or saving data.
fn on_close(&mut self, _screen: Arc<Screen>) {}
/// Called for each top-level widget just before its `Element::render` method is invoked.
///
/// This hook provides an opportunity to inspect or modify the `RenderScope`
/// or the widget itself before it draws its content.
fn render_widget(&mut self, _scope: &mut RenderScope, _widget: &Arc<Widget>) {}
}
```
* `init(&mut self, screen: Arc<Screen>)`: For one-time setup.
* `on_close(&mut self, screen: Arc<Screen>)`: For cleanup.
* `render_widget(&mut self, scope: &mut RenderScope, widget: &Arc<Widget>)`: Called per widget during each render frame.
## `Event` Trait
A marker trait for types that can be dispatched as events within OSUI's system.
```rust
pub trait Event: Send + Sync {
fn as_any(&self) -> &dyn Any;
}
```
* `as_any()`: Required for type-erasing the event, allowing `dyn Event` to be downcasted to concrete types.
* **Helper Method**: `impl<'a> dyn Event + 'a` has a `get<T: Event + 'static>(&self) -> Option<&T>` method for convenient downcasting.
## `Handler<E>` Component
A component that wraps a closure, allowing a `Widget` to subscribe to a specific `Event` type. When an event of type `E` is dispatched to the widget (via `Widget::event`), this handler's closure is called.
```rust
#[derive(Clone)]
pub struct Handler<E: Event>(Arc<Mutex<dyn FnMut(&Arc<Widget>, &E) + Send + Sync>>);
```
### `Handler<E>` Methods
* `new<F: FnMut(&Arc<Widget>, &E) + Send + Sync + 'static>(f: F) -> Handler<E>`: Creates a new handler. The closure receives the `Arc<Widget>` it's attached to and the event.
* `call(&self, w: &Arc<Widget>, e: &E)`: Manually calls the wrapped closure. Used internally by `Widget::event`.
### Usage Example: `Handler`
```rust
use osui::prelude::*;
use crossterm::event::{KeyCode, KeyEvent, Event as CrosstermEvent};
// In your main function or widget:
let my_widget = rsx! {
@Handler::new({
let my_local_data = "some_context".to_string();
move |widget_ref, event: &CrosstermEvent| {
if let CrosstermEvent::Key(KeyEvent { code: KeyCode::Char('x'), .. }) = event {
println!("Widget {:p} received 'x' key. Context: {}", Arc::as_ptr(widget_ref), my_local_data);
// You can also get/set components on the widget_ref:
// if let Some(mut transform) = widget_ref.get::<Transform>() { ... }
}
}
});
Div { "Press 'x'" }
}.draw(&screen);
```
## Built-in Extensions
OSUI provides several pre-implemented extensions for common functionalities:
### `IdExtension`
Provides a mechanism to retrieve a widget by a unique ID. It relies on the `Id` component.
```rust
pub struct IdExtension(pub Arc<Screen>);
component!(Id(pub usize)); // The component used for identifying widgets
impl Extension for Arc<IdExtension> {} // Note: Implements for Arc<IdExtension>
```
* `new(screen: Arc<Screen>) -> Arc<Self>`: Creates a new `IdExtension` instance.
* `get_element(self: &Arc<IdExtension>, id: usize) -> Option<Arc<Widget>>`: Iterates through all widgets on the screen to find one with the matching `Id` component.
```rust
use osui::prelude::*;
let screen = Screen::new();
let id_ext = IdExtension::new(screen.clone());
screen.extension(id_ext.clone()); // Register the extension
let my_widget_id = 123;
rsx! {
@Id(my_widget_id); // Attach the Id component
Div { "My Identifiable Div" }
}.draw(&screen);
// Later, retrieve the widget by ID
if let Some(widget) = id_ext.get_element(my_widget_id) {
// Do something with the widget
println!("Found widget with ID {}", my_widget_id);
}
```
### `InputExtension`
Handles raw terminal input using `crossterm` and dispatches `crossterm::event::Event`s to all widgets.
```rust
pub struct InputExtension;
impl Extension for InputExtension { /* ... */ }
impl crate::extensions::Event for crossterm::event::Event { /* ... */ } // Implemented for crossterm events
```
* **Behavior**: Enables raw mode on `init()`, continuously reads events, and calls `widget.event(&e)` for every widget. Disables raw mode on `on_close()`.
* **Usage**: **Essential for any interactive application that needs keyboard input.**
```rust
use osui::prelude::*;
let screen = Screen::new();
screen.extension(InputExtension); // Enable input handling
```
### `TickExtension`
Dispatches `TickEvent`s at a specified rate (in ticks per second). Useful for animations, timers, or periodic updates.
```rust
pub struct TickExtension(pub u16); // Rate in ticks per second
event!(TickEvent(pub u32)); // The event dispatched by this extension
impl Extension for TickExtension { /* ... */ }
```
* **Constructor**: Takes `u16` representing ticks per second.
* **Behavior**: Spawns a thread that sends a `TickEvent(tick_count)` to all widgets at the specified rate.
* **Usage**:
```rust
use osui::prelude::*;
let screen = Screen::new();
screen.extension(TickExtension(30)); // 30 ticks per second (approx 33ms interval)
// A widget that reacts to ticks
let tick_counter = use_state(0);
rsx! {
@Handler::new({
let tick_counter = tick_counter.clone();
move |_, e: &TickEvent| {
// The `e` is the TickEvent(tick_count)
**tick_counter.get() = e.0; // Update state with current tick count
}
});
%tick_counter
Div { "Current Tick: {tick_counter}" }
}.draw(&screen);
```
### `VelocityExtension`
Automatically updates the `Transform` of widgets that have a `Velocity` component, simulating movement.
```rust
pub struct VelocityExtension;
component!(Velocity(pub i32, pub i32)); // (velocity_x, velocity_y)
impl Extension for VelocityExtension { /* ... */ }
```
* **Behavior**: Spawns a thread that periodically iterates through all widgets. If a widget has both a `Velocity` and a `Transform` component, it updates the `Transform::x` and `Transform::y` based on the velocity.
* **Note**: `VelocityExtension` works by directly modifying `Position::Const` values. If `Transform::x` or `Transform::y` are `Center` or `End`, velocity won't apply to that axis.
* **Usage**:
```rust
use osui::prelude::*;
let screen = Screen::new();
screen.extension(VelocityExtension);
rsx! {
@Transform::new().x(5).y(5); // Initial position
@Velocity(10, 0); // Move 10 cells per second horizontally (positive X)
Div { "Moving Right" }
@Transform::new().x(50).y(10);
@Velocity(-5, 5); // Move left and down
Div { "Moving Left-Down" }
}.draw(&screen);
```
Extensions are a powerful way to add modular, global, or cross-cutting features to your OSUI applications, keeping your core UI definitions focused on structure and reactivity.
@@ -0,0 +1,233 @@
# Macros API Reference
OSUI provides several procedural macros to simplify the definition of common structures like events, components, and especially UI layouts using a declarative syntax.
## `event!` Macro
Declares a struct that implements the `Event` trait. This simplifies the creation of custom event types for OSUI's reactive system.
### Syntax
```rust
event!(Name); // Unit struct
event!(Name { field: Type, ... }); // Named struct with fields
event!(Name (Type, Type, ...)); // Tuple struct
```
### Examples
```rust
use osui::prelude::*;
// Defines a unit struct `Clicked` that implements `osui::extensions::Event`.
event!(Clicked);
// Defines a named struct `Resized` with `width` and `height` fields, implements `Event`.
event!(Resized { width: u32, height: u32 });
// Defines a tuple struct `Moved` with two `u32` fields, implements `Event`.
event!(Moved(u32, u32));
```
### How it Works
The macro automatically adds the necessary `#[derive(Debug, Clone)]` and the `impl Event for ...` block, including the `as_any()` method required for type erasure.
## `component!` Macro
Declares a struct that implements the `Component` trait. Components allow widgets to extend their behavior or contain additional data. This macro helps avoid boilerplate.
### Syntax
```rust
component!(Name); // Unit struct
component!(Name { field: Type, ... }); // Named struct with fields
component!(Name (Type, Type, ...)); // Tuple struct
```
### Examples
```rust
use osui::prelude::*;
// Defines a unit struct `Focusable` that implements `osui::widget::Component`.
component!(Focusable);
// Defines a named struct `Tooltip` with a `text` field, implements `Component`.
component!(Tooltip { text: String });
// Defines a tuple struct `Size` with two `u32` fields, implements `Component`.
component!(Size(u32, u32));
```
### How it Works
Similar to `event!`, this macro adds `#[derive(Debug, Clone)]` and the `impl Component for ...` block, providing `as_any()` and `as_any_mut()`.
## `event_handler!` Macro
Creates an event handler closure that safely calls a method on `self` within a `move` closure, handling the lifetime issues for `Arc` or raw pointers.
### Syntax
```rust
event_handler!($self_ty:ty, $self:ident, $events:ident, $method:ident)
```
* `$self_ty`: The type of `self` (e.g., `MyStruct`).
* `$self`: The instance variable (e.g., `self`).
* `$events`: The event source object (e.g., a `Handler::new` call or `widget.on(...)` if such an API existed) where you want to register the handler.
* `$method`: The method on `$self` to be called when the event occurs.
### Example
```rust
use osui::prelude::*;
use std::sync::Arc;
use crossterm::event::{KeyCode, KeyEvent, Event as CrosstermEvent};
struct MyApp {
screen: Arc<Screen>,
counter: State<u32>,
}
impl MyApp {
fn new(screen: Arc<Screen>) -> Self {
Self {
screen: screen.clone(),
counter: use_state(0),
}
}
// Method that will handle the event
fn handle_key_event(&mut self, _widget: &Arc<Widget>, event: &CrosstermEvent) {
if let CrosstermEvent::Key(KeyEvent { code: KeyCode::Char('a'), .. }) = event {
**self.counter.get() += 1;
println!("'a' pressed! Counter: {}", self.counter.get_dl());
}
}
fn build_ui(mut self: Arc<Self>) -> Rsx { // Self must be Arc<Self> here for cloning
let counter_dep = self.counter.clone();
rsx! {
// Attach a Handler component to the root widget
@Handler::new({
let self_ref = Arc::downgrade(&self); // Use Weak for self-referential closures if needed for more complex scenarios, or clone Arc directly.
// For direct method calls, a raw pointer cast is used by the macro,
// but generally it's safer to clone Arcs for closures or use Weak.
// The macro's internal implementation uses unsafe raw pointer:
// let self_ptr = &*self as *const Self as *mut Self;
// move |widget, event| unsafe { (*self_ptr).handle_key_event(widget, event) }
// Let's use a safe Arc clone here for demonstration
let app_clone = self.clone();
move |widget, event| {
app_clone.clone().handle_key_event(widget, event);
}
});
%counter_dep
Div {
"Press 'a' to increment: {counter_dep}"
}
}
}
}
// NOTE: The `event_handler!` macro as provided in the source code uses `unsafe` raw pointers.
// In real-world code, using `Arc::clone` or `Arc::downgrade` (for weak references)
// and then `upgrade()` within the closure is generally safer and idiomatic for
// closures that need to refer back to their owning struct.
// The provided macro is a low-level utility and care must be taken regarding lifetimes.
```
### Safety Considerations
The provided `event_handler!` macro internally uses `unsafe` code to cast `self` to a raw mutable pointer and dereference it within the closure. **This is inherently unsafe** because it bypasses Rust's borrow checker. You *must* ensure that the instance referred to by the raw pointer outlives the closure. In complex scenarios (e.g., where the event handler could outlive the original struct), this can lead to use-after-free or data races. For safer patterns, consider:
* Cloning `Arc`s for each capture in the closure.
* Using `Arc::downgrade` for weak references if the closure might outlive the original `Arc`.
## `transform!` Macro
Creates a `Transform` struct with specified field values. This offers a more concise syntax for defining transforms compared to `Transform::new().field(...).field(...)`.
### Syntax
```rust
transform!{ field: value, ... }
```
### Examples
```rust
use osui::prelude::*;
// Creates a Transform with x=10, y=20, and default values for others.
let t1 = transform!{ x: 10, y: 20 };
// Creates a Transform centered horizontally, with full width and 2 units of padding.
let t2 = transform!{ x: Center, width: Full, padding: (2, 2) };
rsx! {
@transform!{ x: 5, y: 5, dimensions: (30, 10) };
Div { "A fixed-size div at (5,5)" }
}
```
### How it Works
The macro expands to a `Transform::new()` call followed by setting each specified field using its `into()` method (which allows `u16` to convert to `Position::Const` or `Dimension::Const`).
## `rsx!` Macro
The primary macro for declaratively building UI element trees in OSUI. It supports static elements, dynamic elements with state dependencies, and component attachment.
### Syntax (Simplified Key Patterns)
```rust
rsx! {
// Text literal
"Some text"
// Text literal with color
("Some colored text", 0xFF0000)
// Static element: `static` keyword
// ElementType, prop1: val1, ... { children }
static MyElement, some_prop: true, { "Static child" }
// Static element (no properties):
static MyElement { "Static child" }
// Dynamic element (default, no `static` keyword):
// %dependency1 %dependency2 ... ElementType, prop1: val1, ... { children }
%my_state
MyDynamicElement, another_prop: "value", { "Dynamic child: {my_state}" }
// Dynamic element (no properties):
%my_state
MyDynamicElement { "Dynamic child: {my_state}" }
// Attaching components: `@ComponentType;`
@Transform::new().center();
@Style { background: Background::Solid(0x111111) };
Div { "Div with Transform and Style" }
// Expanding another Rsx block: `function_call => (args)`
my_sub_rsx_function => (arg1, arg2)
}
```
### How it Works (High-Level)
The `rsx!` macro recursively calls an internal `rsx_inner!` macro. It parses the declarative syntax and constructs a tree of `RsxElement` enums (either `RsxElement::Element` for static or `RsxElement::DynElement` for dynamic widgets). This `Rsx` tree is then used by `Rsx::draw` or `Rsx::draw_parent` to create the actual `Widget` instances on the `Screen`.
* **`static`**: Creates a `StaticWidget` for the root `Element`.
* **No `static`**: Creates a `DynWidget` whose element-creation closure will be re-evaluated on dependency changes.
* **`%dependency`**: Automatically clones the `Arc<State<T>>` (or other `DependencyHandler`) and registers it with the `DynWidget`.
* **`@Component`**: Attaches the specified component to the `Widget` being created.
* **`properties: value`**: Sets public fields on the `Element` struct during its construction.
* **`{ children }`**: Recursively processes nested `rsx!` blocks to create child elements.
The `rsx!` macro is the most idiomatic way to build user interfaces in OSUI, offering a concise and powerful syntax for defining complex UI hierarchies and reactivity.
@@ -0,0 +1,221 @@
# `RenderScope` API Reference
The `RenderScope` is a crucial internal component of OSUI's rendering engine. It acts as a drawing canvas and context for individual widgets, accumulating drawing instructions (text, shapes, colors) and managing transformation states. Widgets use `RenderScope` to define what and where they want to draw, and the `Screen` then flushes these instructions to the terminal.
## `RenderScope` Struct
```rust
pub struct RenderScope {
transform: RawTransform,
render_stack: Vec<RenderMethod>, // Internal list of drawing commands
parent_width: u16,
parent_height: u16,
style: Style,
}
```
* `transform`: The `RawTransform` representing the current widget's absolute position and size. This is resolved from a `Transform` component.
* `render_stack`: A queue of `RenderMethod` enums (internal) that define individual draw operations.
* `parent_width`, `parent_height`: The dimensions of the *parent* container, used for resolving `Position::Center`, `Position::End`, and `Dimension::Full`.
* `style`: The `Style` component currently applied to this scope.
## `RenderScope` Methods
### `RenderScope::new()`
Creates a new, empty `RenderScope` with default transform and style.
```rust
pub fn new() -> RenderScope
```
* **Returns**: A new `RenderScope` instance.
* **Usage**: Called internally by the `Screen` for each widget's render pass.
### `set_transform_raw(&mut self, transform: RawTransform)`
Directly sets the raw (absolute) transform for this scope. This bypasses the declarative `Transform` component rules.
```rust
pub fn set_transform_raw(&mut self, transform: RawTransform)
```
* `transform`: The `RawTransform` to apply.
* **Usage**: Rarely used directly by application developers; primarily for internal layout calculations or advanced custom elements.
### `set_transform(&mut self, transform: &Transform)`
Applies a declarative `Transform` component to this scope, resolving its position and dimensions into concrete values based on the `parent_width` and `parent_height`.
```rust
pub fn set_transform(&mut self, transform: &Transform)
```
* `transform`: A reference to the `Transform` component.
* **Usage**: Called by the `Screen` or a parent element before a child's `render` method to set up its coordinate system.
### `draw_text(&mut self, x: u16, y: u16, text: &str)`
Adds a plain text drawing instruction to the render stack. The text will be rendered relative to the `RenderScope`'s `x` and `y` coordinates, and will use the `RenderScope`'s current `style.foreground` if set.
```rust
pub fn draw_text(&mut self, x: u16, y: u16, text: &str)
```
* `x`, `y`: Relative coordinates within the current `RenderScope`'s content area.
* `text`: The string to draw.
* **Side Effect**: Updates the `RenderScope`'s `transform.width` and `transform.height` to encompass the drawn text if it's larger than the current dimensions. This is how `Dimension::Content` works.
### `draw_text_inverted(&mut self, x: u16, y: u16, text: &str)`
Adds a text drawing instruction where the background and foreground colors are swapped.
```rust
pub fn draw_text_inverted(&mut self, x: u16, y: u16, text: &str)
```
* `x`, `y`, `text`: Same as `draw_text`.
* **Usage**: Useful for creating highlighted text, like a cursor in an input field.
### `draw_text_colored(&mut self, x: u16, y: u16, text: &str, color: u32)`
Adds a text drawing instruction with a specific 24-bit RGB foreground color. This color overrides the `RenderScope`'s `style.foreground` for this specific text.
```rust
pub fn draw_text_colored(&mut self, x: u16, y: u16, text: &str, color: u32)
```
* `x`, `y`, `text`: Same as `draw_text`.
* `color`: The 24-bit RGB color (e.g., `0xFF00FF`).
### `draw_rect(&mut self, x: u16, y: u16, width: u16, height: u16, color: u32)`
Adds a filled rectangle drawing instruction to the render stack.
```rust
pub fn draw_rect(&mut self, x: u16, y: u16, width: u16, height: u16, color: u32)
```
* `x`, `y`: Relative top-left coordinates.
* `width`, `height`: Dimensions of the rectangle.
* `color`: The 24-bit RGB fill color.
* **Side Effect**: Updates the `RenderScope`'s `transform.width` and `transform.height` to encompass the drawn rectangle if it's larger.
### `use_area(&mut self, width: u16, height: u16)`
Manually ensures that the `RenderScope`'s internal `transform.width` and `transform.height` are at least the specified values.
```rust
pub fn use_area(&mut self, width: u16, height: u16)
```
* `width`, `height`: Minimum width and height to ensure.
* **Usage**: For elements that might not draw content but have a conceptual size (e.g., a spacer, or a container that needs a minimum dimension).
### `draw(&self)`
Executes all accumulated drawing instructions in the `render_stack` and flushes them to the terminal. This also draws the `RenderScope`'s background style (`Style::Background`).
```rust
pub fn draw(&self)
```
* **Behavior**:
1. Applies the `RenderScope`'s `style.background` (Solid, Outline, RoundedOutline).
2. Iterates through `render_stack`, applying text colors (if `style.foreground` is `Some`) or specific `draw_text_colored` colors, and drawing rectangles.
3. Uses `utils::print_liner` for efficient output.
* **Usage**: Called internally by the `Screen` or parent elements after `render` and `after_render` for a child is complete.
### `clear(&mut self)`
Clears all accumulated drawing instructions, resets the `transform` to default (all zeros), and resets the `style` to `Style::new()`.
```rust
pub fn clear(&mut self)
```
* **Usage**: Called by the `Screen` before rendering each top-level widget, and by container elements before rendering each of their children, to provide a clean drawing context.
### `get_size(&self) -> (u16, u16)`
Returns the current width and height of the `RenderScope` as determined by its `transform.width` and `transform.height`.
```rust
pub fn get_size(&self) -> (u16, u16)
```
### `get_size_or(&self, width: u16, height: u16) -> (u16, u16)`
Returns the current width and height, or falls back to the provided `width` and `height` if the current dimensions are zero.
```rust
pub fn get_size_or(&self, width: u16, height: u16) -> (u16, u16)
```
### `get_size_or_parent(&self) -> (u16, u16)`
Returns the current width and height, or falls back to the parent's dimensions (`parent_width`, `parent_height`) if the current dimensions are zero.
```rust
pub fn get_size_or_parent(&self) -> (u16, u16)
```
### `get_parent_size(&self) -> (u16, u16)`
Returns the width and height of the `RenderScope`'s parent container.
```rust
pub fn get_parent_size(&self) -> (u16, u16)
```
### `set_parent_size(&mut self, width: u16, height: u16)`
Sets the dimensions of the parent container for this `RenderScope`. This is crucial for children to correctly resolve `Dimension::Full`, `Position::Center`, and `Position::End`.
```rust
pub fn set_parent_size(&mut self, width: u16, height: u16)
```
* **Usage**: Primarily used by container elements in their `after_render` method before rendering their children.
### `get_transform_mut(&mut self) -> &mut RawTransform`
Returns a mutable reference to the `RenderScope`'s internal `RawTransform`.
```rust
pub fn get_transform_mut(&mut self) -> &mut RawTransform
```
* **Usage**: Allows elements or extensions to directly manipulate the resolved position and size.
### `get_transform(&self) -> &RawTransform`
Returns an immutable reference to the `RenderScope`'s internal `RawTransform`.
```rust
pub fn get_transform(&self) -> &RawTransform
```
### `set_style(&mut self, style: Style)`
Sets the `Style` for the current render scope. This style applies to subsequent drawing instructions unless overridden by a colored text instruction.
```rust
pub fn set_style(&mut self, style: Style)
```
* `style`: The `Style` to apply.
* **Usage**: Called by the `Screen` or a parent element before a child's `render` method to set up its visual appearance.
### `get_style(&mut self) -> &mut Style`
Gets a mutable reference to the current `Style` in the scope.
```rust
pub fn get_style(&mut self) -> &mut Style
```
`RenderScope` is the bridge between your declarative UI definitions and the actual terminal output. Understanding its methods is key to creating custom elements and mastering OSUI's rendering pipeline.
@@ -0,0 +1,219 @@
# `Screen` API Reference
The `Screen` struct is the central orchestrator of an OSUI application. It manages the UI tree, handles the rendering loop, and coordinates with extensions.
## `Screen` Struct
```rust
pub struct Screen {
pub widgets: Mutex<Vec<Arc<Widget>>>,
// Internal fields for extensions and running state
// extensions: Mutex<Vec<Arc<Mutex<Box<dyn Extension + Send + Sync>>>>>,
// running: Mutex<bool>,
}
```
* `widgets`: A `Mutex`-protected vector holding all top-level `Arc<Widget>` instances managed by this screen. These are the root elements of your UI tree.
## `Screen` Methods
### `Screen::new()`
Creates a new `Screen` instance, wrapped in an `Arc` for shared ownership.
```rust
pub fn new() -> Arc<Self>
```
* **Returns**: An `Arc<Screen>`.
* **Usage**: The primary way to initialize your OSUI environment.
```rust
use osui::prelude::*;
let screen = Screen::new();
```
### `Screen::draw<E: Element + 'static + Send + Sync>(self: &Arc<Self>, element: E) -> Arc<Widget>`
Draws a static element onto the screen. This creates a new `StaticWidget` internally and adds it to the screen's widget list.
```rust
pub fn draw<E: Element + 'static + Send + Sync>(self: &Arc<Self>, element: E) -> Arc<Widget>
```
* `element`: The element to draw. This can be any type that implements the `Element` trait, such as `String`, `Div`, `Input`, etc.
* **Returns**: An `Arc<Widget>` representing the newly created static widget.
* **Usage**: Ideal for simple, non-reactive elements. Often used as the final step after defining your UI with `rsx!`.
```rust
use osui::prelude::*;
let screen = Screen::new();
let my_widget = screen.draw(String::from("Hello, OSUI!"));
// Equivalent to: rsx! { "Hello, OSUI!" }.draw(&screen);
```
### `Screen::draw_box(self: &Arc<Self>, element: BoxedElement) -> Arc<Widget>`
Draws a `BoxedElement` (a boxed trait object implementing `Element`) as a static widget.
```rust
pub fn draw_box(self: &Arc<Self>, element: BoxedElement) -> Arc<Widget>
```
* `element`: A `Box<dyn Element + Send + Sync>`.
* **Returns**: An `Arc<Widget>`.
* **Usage**: Useful when you dynamically create a boxed element that you want to add as static content.
```rust
use osui::prelude::*;
let screen = Screen::new();
let my_boxed_element: BoxedElement = Box::new(Div::new());
screen.draw_box(my_boxed_element);
```
### `Screen::draw_widget(self: &Arc<Self>, widget: Arc<Widget>)`
Adds an already existing `Arc<Widget>` to the screen's list of top-level widgets.
```rust
pub fn draw_widget(self: &Arc<Self>, widget: Arc<Widget>)
```
* `widget`: The `Arc<Widget>` to add.
* **Usage**: When you've manually constructed an `Arc<Widget>` (e.g., a `StaticWidget` or `DynWidget`) and want to display it. `Rsx::draw_parent` uses this internally for non-`NoRenderRoot` widgets.
```rust
use osui::prelude::*;
let screen = Screen::new();
let my_static_widget = Arc::new(Widget::Static(StaticWidget::new(Box::new(String::from("Manually created widget")))));
screen.draw_widget(my_static_widget);
```
### `Screen::draw_dyn<F: FnMut() -> WidgetLoad + 'static + Send + Sync>(self: &Arc<Self>, element: F) -> Arc<Widget>`
Draws a dynamic element onto the screen. This element will be re-evaluated when its dependencies change.
```rust
pub fn draw_dyn<F: FnMut() -> WidgetLoad + 'static + Send + Sync>(self: &Arc<Self>, element: F) -> Arc<Widget>
```
* `element`: A closure that returns a `WidgetLoad`. This closure encapsulates the logic for creating the widget's initial state and element.
* **Returns**: An `Arc<Widget>` representing the newly created dynamic widget.
* **Usage**: For reactive components whose content changes based on `State` or other dynamic factors.
```rust
use osui::prelude::*;
let screen = Screen::new();
let count = use_state(0);
let my_dyn_widget = screen.draw_dyn({
let count = count.clone();
move || WidgetLoad::new(format!("Count: {}", *count.get()))
});
my_dyn_widget.dependency(count); // Explicitly declare dependency if not using rsx!
```
### `Screen::draw_box_dyn(self: &Arc<Self>, element: Box<dyn FnMut() -> WidgetLoad + Send + Sync>) -> Arc<Widget>`
Draws a dynamic element from a boxed closure. Similar to `draw_dyn` but takes a `Box<dyn FnMut() -> WidgetLoad>`.
```rust
pub fn draw_box_dyn(self: &Arc<Self>, element: Box<dyn FnMut() -> WidgetLoad + Send + Sync>) -> Arc<Widget>
```
* `element`: A boxed closure.
* **Returns**: An `Arc<Widget>`.
* **Usage**: Less common for direct use, as `draw_dyn` covers most cases. Used internally by `Rsx::create_element`.
### `Screen::extension<E: Extension + Send + Sync + 'static>(self: &Arc<Self>, ext: E)`
Registers an extension with the screen. Extensions receive lifecycle events and can influence rendering.
```rust
pub fn extension<E: Extension + Send + Sync + 'static>(self: &Arc<Self>, ext: E)
```
* `ext`: An instance of a type that implements the `Extension` trait.
* **Usage**: Essential for adding global functionalities like input handling or periodic updates.
```rust
use osui::prelude::*;
let screen = Screen::new();
screen.extension(InputExtension); // Enable keyboard input
screen.extension(TickExtension(100)); // Enable 100ms ticks
```
### `Screen::run(self: &Arc<Self>) -> std::io::Result<()>`
Starts the main rendering loop of the application. This method blocks the current thread until `screen.close()` is called.
```rust
pub fn run(self: &Arc<Self>) -> std::io::Result<()>
```
* **Returns**: `Ok(())` on successful exit, or an `Err` if a terminal operation fails.
* **Behavior**:
* Calls `init()` on all registered extensions.
* Hides the terminal cursor.
* Enters a loop that repeatedly calls `render()` and sleeps for 28ms (approx. 36 FPS).
* When the loop exits (due to `close()` being called), it shows the cursor, clears the screen, and calls `on_close()` on all extensions.
* **Usage**: The last call in your `main` function.
```rust
use osui::prelude::*;
let screen = Screen::new();
// ... setup widgets and extensions ...
screen.run()?; // Note the '?' for error propagation
```
### `Screen::render(self: &Arc<Self>) -> std::io::Result<()>`
Performs a single rendering pass of all widgets on the screen.
```rust
pub fn render(self: &Arc<Self>) -> std::io::Result<()>
```
* **Returns**: `Ok(())` or an `Err` on terminal failure.
* **Behavior**:
* Clears the terminal.
* Initializes a new `RenderScope` with the current terminal dimensions.
* Iterates through all widgets in `screen.widgets`:
* If a widget has `NoRender` or `NoRenderRoot` components, it's skipped for direct rendering by the screen (its parent is responsible for rendering it).
* Otherwise, it applies the widget's `Style` and `Transform` to the `RenderScope`.
* Calls `Extension::render_widget` for each extension.
* Calls `Element::render` on the widget's root element.
* Draws the `RenderScope` to the terminal.
* Calls `Element::after_render` on the widget's root element.
* Calls `Widget::auto_refresh()` on dynamic widgets to check for state changes.
* **Usage**: Primarily called internally by `Screen::run()`. You typically don't need to call this directly unless implementing a custom rendering loop.
### `Screen::close(self: &Arc<Self>)`
Signals the main rendering loop to terminate.
```rust
pub fn close(self: &Arc<Self>)
```
* **Behavior**: Sets an internal flag that causes the `Screen::run()` loop to exit on its next iteration. It also performs cleanup: shows the cursor, clears the terminal, and calls `on_close()` on all registered extensions.
* **Usage**: Call this from an event handler or other logic to gracefully shut down your application.
```rust
use osui::prelude::*;
use crossterm::event::{KeyCode, KeyEvent, Event as CrosstermEvent};
let screen = Screen::new();
screen.extension(InputExtension);
rsx! {
@Handler::new({
let screen = screen.clone();
move |_, e: &CrosstermEvent| {
if let CrosstermEvent::Key(KeyEvent { code: KeyCode::Char('q'), .. }) = e {
screen.close(); // Exit application on 'q'
}
}
});
"Press 'q' to quit"
}.draw(&screen);
screen.run()?;
```
@@ -0,0 +1,184 @@
# `State` API Reference
OSUI provides a reactive state management system built around the `State<T>` struct and the `DependencyHandler` trait. This system allows your UI to automatically re-render when underlying data changes, eliminating the need for manual update calls in most cases.
## Core Concepts
### `DependencyHandler` Trait
This trait is implemented by types that can act as dependencies for dynamic widgets.
```rust
pub trait DependencyHandler: std::fmt::Debug + Send + Sync {
/// Called when a dependent (e.g., a `DynWidget`) is registered with this handler.
fn add(&self);
/// Returns `true` if the state has changed since the last check.
fn check(&self) -> bool;
}
```
* `add()`: Increments an internal counter, indicating that another `DynWidget` is listening to this state.
* `check()`: Decrements an internal counter and returns `true` if the state was marked as changed *and* there are still dependents that haven't processed the change.
## `State<T>` Struct
`State<T>` is the primary type for managing reactive data in OSUI. It wraps your data `T` in an `Arc<Mutex<Inner<T>>>`, allowing for shared, thread-safe access and change tracking.
```rust
#[derive(Debug, Clone)]
pub struct State<T> {
inner: Arc<Mutex<Inner<T>>>,
}
#[derive(Debug)]
pub struct Inner<T> {
value: T,
dependencies: usize, // Number of widgets/handlers listening
changed: usize, // Number of dependents that need to be notified of a change
}
```
### `State` Creation
#### `use_state<T>(v: T) -> State<T>`
A convenience function to create a new `State` instance.
```rust
pub fn use_state<T>(v: T) -> State<T>
```
* `v`: The initial value for the state.
* **Returns**: A new `State<T>`.
```rust
use osui::prelude::*;
let counter = use_state(0);
let name = use_state(String::from("Alice"));
```
### `State<T>` Methods
#### `get_dl(&self) -> T` (Clone required for `T`)
Gets a **cloned** value of the inner data. This is recommended to avoid deadlocks when accessing the state from multiple threads or within complex UI logic, as it doesn't hold the `Mutex` lock.
```rust
pub fn get_dl(&self) -> T
```
* **Requires**: `T: Clone`.
* **Returns**: A cloned instance of the inner `value`.
```rust
let count_val = counter.get_dl();
println!("Current count: {}", count_val);
```
#### `get(&self) -> MutexGuard<'_, Inner<T>>`
Obtains a `MutexGuard` for the `Inner<T>` struct. This allows direct read and write access to the underlying `value`. When the `MutexGuard` is dropped (or explicitly dereferenced mutably), the state is marked as changed.
```rust
pub fn get(&self) -> MutexGuard<'_, Inner<T>>
```
* **Returns**: A `MutexGuard` that dereferences to `&Inner<T>`.
* **Usage**: For both reading and mutating the state.
```rust
// Reading
let inner_state = counter.get();
println!("Count from guard: {}", inner_state.value);
// Mutating (will mark state as changed)
let mut inner_state = counter.get();
inner_state.value += 1; // Direct mutation
*inner_state = 5; // Replaces the entire Inner struct (less common for value)
```
#### `set(&self, v: T)`
Sets the inner value directly and unconditionally marks the state as changed, notifying all listening dependents.
```rust
pub fn set(&self, v: T)
```
* `v`: The new value for the state.
```rust
counter.set(10); // Sets count to 10 and triggers a refresh for dependents
```
#### `update(&self)`
Manually marks the state as changed without modifying its value. This is useful if you mutate the inner value directly through `get()` and then want to explicitly trigger a refresh *after* the `MutexGuard` has been dropped, or if the change is internal to `T` and not visible via `DerefMut`.
```rust
pub fn update(&self)
```
```rust
// Example where `update` might be useful (less common with DerefMut):
let mut inner_data = counter.get();
// Perform complex operations on inner_data.value
// ...
// Dropping inner_data will mark as changed, so update() might be redundant here
// But if you had a situation where `DerefMut` didn't cover the change:
// inner_data.some_internal_list.push(item);
// drop(inner_data); // Dropping the guard normally triggers update
// counter.update(); // Manual update if needed for some reason (e.g., if you only had an immutable guard before)
```
### `State<T>` and `DependencyHandler` Implementation
`State<T>` implements `DependencyHandler`, enabling it to participate in OSUI's reactive system:
* `add()`: When a `DynWidget` declares a dependency on a `State<T>` (e.g., using `%my_state` in `rsx!`), this method is called. It increments `inner.dependencies`.
* `check()`: During `DynWidget::auto_refresh`, this method is called. It checks if `inner.changed > 0`. If true, it decrements `inner.changed` and returns `true`, indicating the widget needs to be rebuilt.
### `Deref` and `DerefMut` for `Inner<T>`
The `Inner<T>` struct implements `Deref` and `DerefMut` to its inner `value: T`. This allows you to directly access and modify the `T` value through the `MutexGuard`.
* `impl Deref for Inner<T>`: Allows `*inner_state` to yield `&T`.
* `impl DerefMut for Inner<T>`: Allows `*inner_state = ...` or `inner_state.mutate_field = ...` to yield `&mut T`. **Crucially, when `deref_mut` is called, it sets `inner.changed = inner.dependencies`, ensuring all registered dependents are notified.**
```rust
let my_state = use_state(0);
// Directly read via Deref:
let guard = my_state.get();
println!("{}", *guard); // Prints the value of the integer
// Directly mutate via DerefMut:
let mut guard = my_state.get();
*guard += 1; // Mutates the integer, triggers 'changed' flag
drop(guard); // The guard is dropped here, releasing the mutex
// The DynWidget dependent on `my_state` will now refresh on the next auto_refresh cycle.
```
### `Display` for `State<T>`
`State<T>` also implements `Display` if `T` implements `Display`. This is very convenient for embedding `State` values directly into strings in `rsx!` for display.
```rust
use osui::prelude::*;
let my_str_state = use_state(String::from("World"));
rsx! {
%my_str_state
Div {
// This works because State<String> implements Display
"Hello, {my_str_state}!"
}
}
```
The `State` system, combined with `DynWidget` and the `rsx!` macro, forms the backbone of OSUI's reactive programming model, allowing for efficient and automatic UI updates in response to data changes.
@@ -0,0 +1,176 @@
# Style API Reference
The `style` module defines the fundamental structures for managing rendering geometry, positioning, sizing, and visual appearance of OSUI widgets. These types are essential for controlling how your UI elements are laid out and drawn.
## `RawTransform` Struct
`RawTransform` holds the concrete, resolved layout information for a widget *after* layout calculations have been performed. It contains absolute pixel values.
```rust
#[derive(Debug, Clone)]
pub struct RawTransform {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
pub px: u16, // Padding X
pub py: u16, // Padding Y
}
```
* `x`, `y`: Absolute top-left coordinate of the widget in terminal cells.
* `width`, `height`: Absolute dimensions of the widget in terminal cells.
* `px`, `py`: Resolved horizontal and vertical padding applied *around* the content area.
### `RawTransform::new()`
Creates a new `RawTransform` with all fields set to 0.
```rust
pub fn new() -> RawTransform
```
## `Position` Enum
`Position` defines horizontal or vertical positioning rules relative to a parent container.
```rust
#[derive(Debug, Clone)]
pub enum Position {
/// Fixed position in cells from the origin.
Const(u16),
/// Centered in the parent.
Center,
/// Aligned to the end (right or bottom) of the parent.
End,
}
```
### `Position` Implementations
* `impl From<u16> for Position`: Allows `u16` values to be directly used where `Position` is expected (e.g., `Transform { x: 10 }`).
* `use_position(&self, size: u16, parent: u16, m: i32, r: &mut u16)`: An internal method used by `Transform` to resolve the position based on the element's size, parent's size, and margin.
## `Dimension` Enum
`Dimension` defines sizing rules for width or height.
```rust
#[derive(Debug, Clone)]
pub enum Dimension {
/// Fills the available space from the parent.
Full,
/// Automatically sized to fit content.
Content,
/// Fixed size in cells.
Const(u16),
}
```
### `Dimension` Implementations
* `impl From<u16> for Dimension`: Allows `u16` values to be directly used where `Dimension` is expected (e.g., `Transform { width: 50 }`).
* `use_dimension(&self, parent: u16, r: &mut u16)`: An internal method used by `Transform` to resolve the dimension based on the parent's size.
## `Background` Enum
`Background` defines various visual appearances for a widget's background. Colors are 24-bit RGB values represented as `u32` (e.g., `0xFF0000` for red).
```rust
#[derive(Debug, Clone)]
pub enum Background {
/// Transparent / no background.
NoBackground,
/// Draws a basic outline using the given color.
Outline(u32),
/// Draws a rounded outline using the given color.
RoundedOutline(u32),
/// Fills the background with the specified color.
Solid(u32),
}
```
## `Transform` Component
The `Transform` component is the primary way to define a widget's desired layout properties. It is typically attached to a `Widget` via `rsx!` or `widget.component()`.
```rust
component!(Transform {
pub x: Position,
pub y: Position,
pub mx: i32, // Margin X
pub my: i32, // Margin Y
pub px: u16, // Padding X
pub py: u16, // Padding Y
pub width: Dimension,
pub height: Dimension,
});
```
### `Transform` Methods
* `Transform::new() -> Transform`: Creates a default transform with top-left alignment (`Position::Const(0)`) and content sizing (`Dimension::Content`), with no margins or padding.
* `Transform::center() -> Transform`: Shortcut for centering both horizontally and vertically (`Position::Center`) with content sizing.
* `bottom(mut self) -> Self`: Sets `y` to `Position::End`.
* `right(mut self) -> Self`: Sets `x` to `Position::End`.
* `margin(mut self, x: i32, y: i32) -> Self`: Adds margin (offset) from parent edge. `x` is `mx`, `y` is `my`.
* `padding(mut self, x: u16, y: u16) -> Self`: Adds internal spacing (padding) around content. `x` is `px`, `y` is `py`.
* `dimensions(mut self, width: u16, height: u16) -> Self`: Sets constant dimensions (`Dimension::Const`) for `width` and `height`.
* `use_dimensions(&self, parent_width: u16, parent_height: u16, raw: &mut RawTransform)`: Internal method that resolves `Dimension` rules into absolute values (`raw.width`, `raw.height`) based on parent size.
* `use_position(&self, parent_width: u16, parent_height: u16, raw: &mut RawTransform)`: Internal method that resolves `Position` rules into absolute coordinates (`raw.x`, `raw.y`) based on parent size and the resolved widget size.
### Usage Example: `Transform`
```rust
use osui::prelude::*;
rsx! {
// A div that is 20x5 cells, centered, with 1 unit of padding
@Transform::new().dimensions(20, 5).center().padding(1, 1);
Div { "Centered Box" }
// A div aligned to the bottom-right, with 2 cells margin from edges
@Transform::new().bottom().right().margin(2, 2);
Div { "Bottom Right" }
// A div that fills the parent's width, is content-height, and starts at (0, 3)
@Transform { x: 0, y: 3, width: Full, height: Content };
Div { "Full Width Container" }
}
```
## `Style` Component
The `Style` component defines the visual appearance of a widget, primarily its background and foreground colors.
```rust
component!(Style {
pub background: Background,
pub foreground: Option<u32>,
});
```
### `Style` Methods
* `Style::new() -> Self`: Creates a default style with `Background::NoBackground` and no foreground color (`None`).
### Usage Example: `Style`
```rust
use osui::prelude::*;
rsx! {
// A solid red background with white text
@Style { background: Background::Solid(0xFF0000), foreground: Some(0xFFFFFF) };
Div { "Red Box" }
// A green rounded outline with default foreground
@Style { background: Background::RoundedOutline(0x00FF00), foreground: None };
Div { "Green Rounded Outline" }
}
```
By combining `Transform` and `Style` components, developers have fine-grained control over the layout and aesthetics of every element in their OSUI applications.
@@ -0,0 +1,140 @@
# Utilities (`utils` module) API Reference
The `utils` module provides a collection of helper functions primarily for low-level terminal manipulation (like clearing the screen or hiding the cursor) and string measurements. These functions are often used internally by OSUI's rendering engine but can also be helpful for application developers directly.
## Functions
### `clear()`
Clears the entire terminal screen and moves the cursor to the top-left corner (1,1).
```rust
pub fn clear() -> io::Result<()>
```
* **Returns**: A `std::io::Result<()>` indicating success or an I/O error.
* **Behavior**: Sends ANSI escape codes `\x1B[2J` (clear screen) and `\x1B[H` (cursor home).
* **Usage**: Used by `Screen::render` before drawing a new frame. You might use it yourself for custom full-screen updates outside of OSUI's main loop.
```rust
use osui::utils;
// ...
utils::clear().unwrap();
```
### `hide_cursor()`
Hides the terminal cursor.
```rust
pub fn hide_cursor() -> io::Result<()>
```
* **Returns**: A `std::io::Result<()>` indicating success or an I/O error.
* **Behavior**: Sends ANSI escape code `\x1b[?25l`.
* **Usage**: Called by `Screen::run` at application startup for a cleaner TUI experience. You should not need to call this manually in most OSUI applications.
### `show_cursor()`
Shows the terminal cursor.
```rust
pub fn show_cursor() -> io::Result<()>
```
* **Returns**: A `std::io::Result<()>` indicating success or an I/O error.
* **Behavior**: Sends ANSI escape code `\x1B[?25h`.
* **Usage**: Called by `Screen::close` at application shutdown to restore the terminal state. You should not need to call this manually.
### `flush()`
Flushes the standard output buffer. This ensures that any `print!` or `println!` macros or direct writes to `stdout` are immediately displayed on the terminal.
```rust
pub fn flush() -> io::Result<()>
```
* **Returns**: A `std::io::Result<()>` indicating success or an I/O error.
* **Usage**: Used internally by OSUI's print functions to ensure immediate rendering. You might use it after a series of prints if you're not using OSUI's `RenderScope` for drawing.
```rust
use std::io::{self, Write};
use osui::utils;
print!("Loading...");
utils::flush()?; // Ensure "Loading..." is visible
// ... long operation ...
println!("Done.");
```
### `str_size(s: &str) -> (u16, u16)`
Calculates the width (maximum line length) and height (number of lines) of a string, assuming it's rendered in a monospaced terminal environment. It handles newline characters (`\n`).
```rust
pub fn str_size(s: &str) -> (u16, u16)
```
* `s`: The input string.
* **Returns**: A tuple `(width, height)` where `width` is the maximum line width and `height` is the number of lines.
* **Usage**: Used by `RenderScope` to determine content-based element sizes. Also useful for custom elements needing to know the dimensions of text.
```rust
use osui::utils;
let (width, height) = utils::str_size("Hello\nWorld");
assert_eq!((5, 2), (width, height)); // "World" is 5 chars, 2 lines
```
### `hex_ansi(hex: u32) -> String`
Converts a 24-bit RGB hex color (e.g., `0xFF00FF`) into an ANSI escape sequence for setting the **foreground** color.
```rust
pub fn hex_ansi(hex: u32) -> String
```
* `hex`: A `u32` representing the RGB color (e.g., `0xAABBCC`).
* **Returns**: A `String` containing the ANSI escape code (e.g., `"\x1b[38;2;R;G;Bm"`).
* **Usage**: Used internally for coloring text.
### `hex_ansi_bg(hex: u32) -> String`
Converts a 24-bit RGB hex color (e.g., `0xFF00FF`) into an ANSI escape sequence for setting the **background** color.
```rust
pub fn hex_ansi_bg(hex: u32) -> String
```
* `hex`: A `u32` representing the RGB color.
* **Returns**: A `String` containing the ANSI escape code (e.g., `"\x1b[48;2;R;G;Bm"`).
* **Usage**: Used internally for coloring backgrounds.
### `print(x: u16, y: u16, text: &str)` (crate-internal)
Prints text directly to the terminal at a specific 0-indexed `(x, y)` coordinate (converted to 1-indexed for ANSI). It resets the terminal style (`\x1b[0m`) after printing.
```rust
pub(crate) fn print(x: u16, y: u16, text: &str)
```
* `x`, `y`: 0-indexed coordinates for the top-left corner of the text.
* `text`: The string to print.
* **Usage**: Internal helper for `RenderScope`.
### `print_liner(x: u16, y: u16, liner: &str, text: &str)` (crate-internal)
Prints text to the terminal at `(x, y)` coordinates, prepending each line with a specified `liner` string (typically an ANSI color code). It resets the terminal style after printing.
```rust
pub(crate) fn print_liner(x: u16, y: u16, liner: &str, text: &str)
```
* `x`, `y`: 0-indexed coordinates.
* `liner`: A string (e.g., an ANSI color code) to prepend to each line.
* `text`: The string to print.
* **Usage**: Internal helper for `RenderScope` to apply styles to printed output.
These utility functions provide the low-level terminal interaction necessary for OSUI's rendering, but some can be useful for direct debugging or custom terminal output when not managed by OSUI's drawing pipeline.
@@ -0,0 +1,159 @@
# Widget System API Reference
OSUI's UI is built upon a flexible widget system that combines `Element`s (renderable units) with `Component`s (data/behavior). The `Widget` enum serves as the primary container for these UI entities.
## Core Traits
### `Element` Trait
The fundamental building block for anything that can be rendered or participate in the UI tree.
```rust
pub trait Element: Send + Sync {
fn render(&mut self, scope: &mut RenderScope);
fn after_render(&mut self, scope: &mut RenderScope);
fn draw_child(&mut self, element: &Arc<Widget>);
fn event(&mut self, event: &dyn Event);
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
```
* `render(&mut self, scope: &mut RenderScope)`: Called to perform the element's direct rendering (e.g., drawing text, shapes).
* `after_render(&mut self, scope: &mut RenderScope)`: Called after the element's `render` and its children's `render` methods. Used by container elements (`Div`, `FlexRow`, `FlexCol`) to process and render their children.
* `draw_child(&mut self, element: &Arc<Widget>)`: Called by the `rsx!` macro or parent elements when a child widget is added to this element. Container elements must implement this to store their children.
* `event(&mut self, event: &dyn Event)`: Called when an event is dispatched to this widget.
* `as_any(&self) -> &dyn Any`: Required for downcasting.
* `as_any_mut(&mut self) -> &mut dyn Any`: Required for mutable downcasting.
### `Component` Trait
An optional trait for state or metadata attached to widgets. Components are key-value pairs (`TypeId` to `Box<dyn Component>`) stored alongside the element.
```rust
pub trait Component: Send + Sync {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
```
* `as_any(&self) -> &dyn Any`: Required for downcasting.
* `as_any_mut(&mut self) -> &mut dyn Any`: Required for mutable downcasting.
* **Note**: Components usually implement `Clone` as well, so they can be retrieved (`get()`) by value.
## Widget Containers
### `WidgetLoad` Struct
A temporary container used during the initial construction of a widget, particularly by `rsx!`.
```rust
pub struct WidgetLoad(BoxedElement, HashMap<TypeId, BoxedComponent>);
```
* `new<E: Element + 'static>(e: E) -> Self`: Creates a new `WidgetLoad` with a root element.
* `component<C: Component + 'static>(mut self, c: C) -> Self`: Attaches a component if one of its type doesn't already exist. Chainable.
* `set_component<C: Component + 'static>(mut self, c: C) -> Self`: Replaces any existing component of the same type. Chainable.
* `get<C: Component + 'static + Clone>(&self) -> Option<C>`: Attempts to retrieve a cloned component of the given type.
```rust
use osui::prelude::*;
let wl = WidgetLoad::new(String::from("My Text"))
.component(Transform::new().center())
.set_component(Style { background: Background::Solid(0x000000), foreground: Some(0xFFFFFF) });
if let Some(t) = wl.get::<Transform>() {
// ... use t ...
}
```
### `StaticWidget` Struct
Represents a widget with fixed content and no dynamic rebuilding behavior.
```rust
pub struct StaticWidget(Mutex<BoxedElement>, Mutex<HashMap<TypeId, BoxedComponent>>);
```
* `new(e: BoxedElement) -> Self`: Creates a new `StaticWidget`. Used internally by `Screen::draw`.
* `component<C: Component + 'static>(&self, c: C)`: Attaches a component if type doesn't exist.
* `set_component<C: Component + 'static>(&self, c: C)`: Replaces a component.
* `get<C: Component + 'static + Clone>(&self) -> Option<C>`: Retrieves a cloned component.
* **Note**: Access to the element and components is via `Mutex`es for thread safety.
### `DynWidget` Struct
Represents a widget with dynamic content, supporting reactive updates and rebuilding.
```rust
pub struct DynWidget(
Mutex<BoxedElement>,
Mutex<HashMap<TypeId, BoxedComponent>>,
Mutex<Box<dyn FnMut() -> WidgetLoad + Send + Sync>>, // The rebuild function
Mutex<Vec<Box<dyn DependencyHandler>>>, // Registered dependencies
Mutex<Option<Box<dyn FnMut(WidgetLoad) -> WidgetLoad + Send + Sync>>>, // Inject function
);
```
* `new<F: FnMut() -> WidgetLoad + 'static + Send + Sync>(mut e: F) -> Self`: Creates a new `DynWidget` from a build closure.
* `inject<F: FnMut(WidgetLoad) -> WidgetLoad + 'static + Send + Sync>(&self, f: F)`: Provides a callback that can modify the `WidgetLoad` during refresh. Useful for adding components dynamically.
* `refresh(&self)`: Forces the widget to rebuild its content by re-evaluating its creation function.
* `auto_refresh(&self)`: Rebuilds the widget only if any of its registered dependencies (`DependencyHandler`) have changed. Called by `Screen` during `render`.
* `dependency<D: DependencyHandler + 'static>(&self, d: D)`: Adds a dependency.
* `dependency_box(&self, d: Box<dyn DependencyHandler>)`: Adds a boxed dependency.
* `component<C: Component + 'static>(&self, c: C)`: Attaches a component if type doesn't exist.
* `set_component<C: Component + 'static>(&self, c: C)`: Replaces a component.
* `get<C: Component + 'static + Clone>(&self) -> Option<C>`: Retrieves a cloned component.
## `Widget` Enum
The main reference-counted container for either a `StaticWidget` or `DynWidget`. This is the standard way to pass widgets around.
```rust
pub enum Widget {
Static(StaticWidget),
Dynamic(DynWidget),
}
```
* `new_static(e: BoxedElement) -> Self`: Creates a static `Widget`.
* `new_dyn<F: FnMut() -> WidgetLoad + 'static + Send + Sync>(mut e: F) -> Self`: Creates a dynamic `Widget`.
### Common `Widget` Methods (Delegated)
These methods delegate to the underlying `StaticWidget` or `DynWidget` variant.
* `get_elem(&self) -> MutexGuard<BoxedElement>`: Gets a mutable lock to the widget's root `Element`. Use `*widget.get_elem()` to access the `Element`.
* `after_render(&self)`: Calls `Element::after_render` on the root element.
* `component<C: Component + 'static>(self: &Arc<Self>, c: C) -> &Arc<Self>`: Attaches a component. Returns `self` for chaining.
* `set_component<C: Component + 'static>(self: &Arc<Self>, c: C) -> &Arc<Self>`: Replaces a component. Returns `self` for chaining.
* `get<C: Component + 'static + Clone>(&self) -> Option<C>`: Retrieves a cloned component. Returns `None` if not found or type mismatch.
* `inject<F: FnMut(WidgetLoad) -> WidgetLoad + 'static + Send + Sync>(self: &Arc<Self>, mut f: F)`: For dynamic widgets, sets a callback to modify `WidgetLoad` on refresh. For static, it injects components from the `WidgetLoad` returned by `f`.
* `refresh(self: &Arc<Self>)`: Forces a rebuild for `DynWidget`s. Does nothing for `StaticWidget`s.
* `auto_refresh(self: &Arc<Self>)`: Triggers rebuild for `DynWidget`s if dependencies have changed. Does nothing for `StaticWidget`s.
* `dependency<D: DependencyHandler + 'static>(self: &Arc<Self>, d: D) -> &Arc<Self>`: Adds a dependency for `DynWidget`s. Does nothing for `StaticWidget`s.
* `dependency_box(self: &Arc<Self>, d: Box<dyn DependencyHandler>) -> &Arc<Self>`: Adds a boxed dependency for `DynWidget`s. Does nothing for `StaticWidget`s.
* `event<E: Event + Clone + 'static>(self: &Arc<Self>, e: &E)`: Dispatches an event. It first checks for an attached `Handler<E>` component and calls it, then calls `Element::event` on the root element.
## Special Components
OSUI uses a few internal components to control rendering behavior:
* `NoRender`: If a widget has this component, the `Screen`'s main rendering loop will skip rendering it directly. This is typically used for widgets that are managed and rendered by their parent `Element::after_render` method.
* `NoRenderRoot`: Similar to `NoRender`, but specifically signals that the widget is a child being managed by a parent element, preventing the `Screen` from considering it a top-level root widget for direct rendering.
* `Handler<E>`: (Described in [Handling Input](../guides/handling_input.md) and [Extensions API](../reference/extensions_api.md)) Enables widgets to subscribe to specific event types.
## Usage Patterns
When working with widgets, you'll commonly:
1. Create them using `rsx!` or `Screen::draw`/`draw_dyn`.
2. Attach `Transform` and `Style` components for layout and appearance.
3. Attach other custom components for data or behavior.
4. For dynamic widgets, declare `State` dependencies using `%` in `rsx!`.
5. Access elements and components using `get_elem()`, `get()`, `set_component()`.
Understanding the interplay between `Element`, `Component`, and `Widget` is crucial for building complex and interactive OSUI applications.