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,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.