v0.1.0 with spark
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user