Merge pull request #1 from osui-rs/v0.2.0

V0.2.0
This commit is contained in:
Klesti Selimaj
2026-01-31 22:00:20 +01:00
committed by GitHub
67 changed files with 11171 additions and 2849 deletions
+9 -4
View File
@@ -1,7 +1,12 @@
kleo-dev:
name: Leo
name: Klesti
title: Creator of OSUI
url: https://github.com/kleo-dev
image_url: https://github.com/kleo-dev.png
url: https://selimaj.dev
image_url: https://github.com/selimaj-dev.png
socials:
github: kleo-dev
github: selimaj-dev
instagram: selimaj.dev
x: selimajdev
linkedin: klesti-selimaj
portfolio: https://selimaj.dev
youtube: SelimajDev
+1
View File
@@ -1,6 +1,7 @@
---
title: OSUI v0.0.9 alpha, what's new and should you update
description: OSUI v0.0.9 is just on the edge of development, alpha is just the beginning.
date: 2025-06-30T22:19
slug: osui-0.0.9-alpha
authors:
- kleo-dev
+32
View File
@@ -0,0 +1,32 @@
---
title: OSUI v0.2.0 is here!
description: New codebase, new strucutre, new everything.
date: 2026-01-31 18:52
slug: osui-0.2.0
authors:
- kleo-dev
---
Wow, 2 years into this project, i was 14 years old when i started this project, aside from my first [Rust project](https://github.com/wyst-lang/wyst/tree/legacy) This was my second project and my most important for learning how rust actually works.
<!-- truncate -->
I'll be honest, i wasn't the best developer back then, but i've learned so much, and i'm excited for a new era, i want to get back into this project to thank it for placing me where i'm at.
Enough said though, let's get into the changes.
## Rewrite
This rewrite has been one of the best yet, while not fully frontend featured, the engine and structure matters a lot, this is a much more resilient architecture than any other OSUI structure.
## Performance
The performance is much better and it's more predictable, however i do not have a clue on why the spikes appear on those specific iterations.
## Scoping
Scoping allows for the separation of dynamic and static DOM, for a more flexible UI, this makes `for` loops and `if` statements work perfectly without any bugs.
![Scoping](https://private-user-images.githubusercontent.com/103524696/540255465-fe705306-260f-4443-b31f-17ecb9401ef8.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Njk4OTMxOTMsIm5iZiI6MTc2OTg5Mjg5MywicGF0aCI6Ii8xMDM1MjQ2OTYvNTQwMjU1NDY1LWZlNzA1MzA2LTI2MGYtNDQ0My1iMzFmLTE3ZWNiOTQwMWVmOC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjYwMTMxJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI2MDEzMVQyMDU0NTNaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1jM2FjY2ZmMjQzMmRhZGVhZjFjZmMxMjg2YzFlZTFkNzdhNGM4OTA2MWU2MWM3ZjliNDY1YzE3ZGFjMmRiMTQ1JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.zuJ_ZkgzhP0Ww2Z3-zu12fQPQVRxl_kr5VQwHbDD5sQ)
## What's missing
I've only worked on this version for about 2-3 weeks, so it's not fully featured, here are the features that will be added on future versions:
- Positioning and sizing API, Position::Center, etc.
- Styling API, fg: #fff, etc.
- RSX string dependencies (or ref cloning) (`%state "{state}"`).
+1
View File
@@ -1,6 +1,7 @@
---
title: Releasing OSUI v0.0.9
description: A new upcoming version of OSUI will come out. Let's re-write it.
date: 2025-01-19T23:16
slug: osui-0.0.9-rewrite
authors:
- kleo-dev
+33
View File
@@ -0,0 +1,33 @@
---
sidebar_position: 0
title: Introduction
slug: /
---
# Introduction to OSUI
OSUI is a powerful and flexible Rust library for building advanced Terminal User Interfaces (TUIs). It provides a component-based architecture inspired by modern web frameworks, offering a familiar and ergonomic development experience for crafting interactive command-line applications.
## What is OSUI?
OSUI stands for "Operating System User Interface" in the terminal context. It aims to bridge the gap between simple text-based applications and rich graphical user interfaces by offering a robust framework for complex TUI development.
### Key Features
* **Component System**: Build UIs using reusable, composable components that manage their own state and lifecycle.
* **Reactive State Management**: Leverage React-like hooks (`useState`, `useEffect`) for efficient and predictable state handling.
* **Declarative UI with RSX**: Define your UI structure using an intuitive, macro-based RSX (React-like Syntax) similar to JSX.
* **Event Handling**: A type-safe event system allows components to communicate and respond to user input and internal changes.
* **Pluggable Rendering Engine**: Ships with a `Console` engine for `crossterm`-based terminal rendering, and an extensible `Engine` trait for custom backends.
* **Benchmark Tooling**: Built-in benchmarking capabilities to measure and optimize rendering performance.
## Why OSUI?
Traditional TUI libraries often require imperative manipulation of the terminal buffer, which can become cumbersome for complex applications. OSUI addresses this by:
* **Promoting Modularity**: Components encapsulate UI logic and appearance, making code easier to organize, test, and maintain.
* **Simplifying State Logic**: The hook-based state management system ensures that your UI automatically reacts to data changes, reducing boilerplate and potential bugs.
* **Enhancing Developer Experience**: The `rsx!` macro and `#[component]` attribute provide a declarative way to define your UI, allowing you to focus on *what* your UI should look like, rather than *how* to draw it.
* **Encouraging Scalability**: The clear separation of concerns (components, state, rendering engine) makes OSUI suitable for applications ranging from simple utilities to complex interactive dashboards.
Whether you're building a CLI dashboard, an interactive configuration tool, or a text-based game, OSUI provides the tools to create engaging and performant terminal experiences.
+43
View File
@@ -0,0 +1,43 @@
---
sidebar_position: 1
title: Installation
---
# Installation
To get started with OSUI, you need to add it as a dependency to your Rust project. OSUI is available on [crates.io](https://crates.io/crates/osui).
## Adding OSUI to Your Project
Open your project in your terminal and run this command:
```bash
cargo add osui
```
The `osui` crate re-exports its procedural macros (`#[component]` and `rsx!`) through its `prelude` module, so you typically don't need to add `osui-macros` as a separate dependency.
## Enabling the `rsx` Feature (Recommended)
The `rsx` feature is crucial for using OSUI's declarative UI syntax. It is usually enabled by default when you add `osui` as a dependency. If you ever explicitly disable default features for `osui`, remember to re-enable `rsx`:
```toml
[dependencies]
osui = { version = "0.2.0", features = ["rsx"] }
```
:::info
The `rsx` feature is vital for using OSUI's `rsx!` macro and `#[component]` attribute, which are fundamental to building UIs in OSUI.
:::
## Building Your Project
Once `osui` is added to your `Cargo.toml`, you can build your project using Cargo:
```bash
cargo build
```
This will download and compile OSUI and its dependencies.
You are now ready to start building your first OSUI application! Proceed to the [Getting Started](./02-getting-started.md) guide to write your first "Hello World" component.
+89
View File
@@ -0,0 +1,89 @@
---
sidebar_position: 2
title: Getting Started
---
# Getting Started: Hello World
Let's write a minimal OSUI application that displays "Hello World" in the terminal. This example will introduce you to the core concepts of an OSUI application: the `Console` engine, running an application, and defining a basic component with `rsx!`.
## 1. Create a New Project
If you haven't already, create a new Rust binary project:
```bash
cargo new hello-osui
cd hello-osui
```
## 2. Add OSUI Dependency
```bash
cargo add osui
```
## 3. Write the Hello World Code
Open `src/main.rs` and replace its contents with the following:
```rust title="src/main.rs"
use osui::prelude::*; // Import commonly used OSUI items
use std::sync::Arc;
pub fn main() {
// 1. Initialize the Console engine
// The Console engine uses crossterm to interact with the terminal.
let engine = Console::new();
// 2. Run the application
// The `run` method takes your root component and starts the rendering loop.
// It returns a `Result`, which we unwrap here for simplicity.
engine.run(App {}).expect("Failed to run OSUI application");
}
/// 3. Define your root component
/// The `#[component]` attribute transforms a function into a reusable UI component.
/// - The first parameter `cx: &Arc<Context>` is mandatory and provides access to component context (state, events, children).
/// - Components must return a `View`.
#[component]
fn App(cx: &Arc<Context>) -> View {
// 4. Use RSX (React-like Syntax) to define the UI
// `rsx!` is a procedural macro for declarative UI construction.
// Here, it just renders a simple string literal.
rsx! {
"Hello World"
}
// `view(&cx)` converts the RSX into a `View` that can be rendered.
.view(&cx)
}
```
## 4. Run Your Application
Execute your application from the terminal:
```bash
cargo run
```
You should see "Hello World" displayed in your terminal, which will then clear when the application exits.
## Understanding the Code
Let's break down the key parts of the "Hello World" example:
* **`use osui::prelude::*;`**: This line imports the `prelude` module, which re-exports the most commonly used types and macros from OSUI. This includes `Console`, `Context`, `View`, `#[component]`, and `rsx!`.
* **`Console::new()`**: The `Console` struct is OSUI's default rendering engine. It uses the `crossterm` library to draw content to your terminal.
* **`engine.run(App {})`**: This starts the OSUI application lifecycle. It takes an instance of your root component (`App {}` in this case) and begins the continuous rendering loop. The `App` component will be rendered repeatedly until the application is stopped (e.g., by pressing `Ctrl+C` or a command from within the app).
* **`#[component] fn App(cx: &Arc<Context>) -> View { ... }`**:
* The `#[component]` attribute is a procedural macro that transforms a standard Rust function into an OSUI component. This enables prop handling and integration into the component tree.
* All component functions must take `cx: &Arc<Context>` as their first argument. The `Context` provides access to component-specific state, lifecycle methods, event handlers, and the ability to add child components.
* Component functions must return a `View`, which is an `Arc<dyn Fn(&mut DrawContext) + Send + Sync>`. This `View` represents the instructions for rendering the component.
* **`rsx! { "Hello World" }.view(&cx)`**:
* The `rsx!` macro is OSUI's declarative UI syntax. It allows you to define a hierarchy of components and text nodes in a way that feels similar to HTML or React's JSX.
* In this simple case, `"Hello World"` is a text literal that `rsx!` converts into a renderable element.
* The `.view(&cx)` method takes the `Rsx` object generated by the `rsx!` macro and processes it within the current `Context`, ultimately producing the final `View` to be returned by the component.
This simple example demonstrates the fundamental building blocks of an OSUI application. Next, we'll explore how to create more complex components and pass data between them.
**Next:** Learn about [Creating Components](./03-example-basic-component.md) and passing them data.
+112
View File
@@ -0,0 +1,112 @@
---
sidebar_position: 3
title: Basic Component with Children
---
# Basic Component with Children and Props
In OSUI, applications are built by composing smaller, reusable components. This guide expands on the "Hello World" example by demonstrating how to define a custom component, pass data to it (props), and render its children.
## 1. The `MyComponent` Example
Let's modify our `src/main.rs` to introduce `MyComponent`.
```rust title="src/main.rs"
use osui::prelude::*;
use std::sync::Arc;
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run engine");
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
// Here, MyComponent is instantiated with a string literal as its child.
// This string will be available inside MyComponent via the `children` prop.
MyComponent { "------ example" }
}
.view(&cx)
}
#[component]
fn MyComponent(cx: &Arc<Context>, children: &Rsx) -> View {
rsx! {
// `@{children}` is an RSX expression that renders the children passed to MyComponent.
// It's a special syntax to embed an `Rsx` value directly into the component's output.
@{children}
"Simple Component"
}
.view(&cx)
}
```
## 2. Run the Application
```bash
cargo run
```
You should see output similar to this:
```
------ example
Simple Component
```
The exact positioning will depend on your terminal's size and OSUI's default rendering behavior, but the text "------ example" (from the child) should appear before "Simple Component" (from `MyComponent` itself).
## Understanding the Component Pattern
### The `children` Prop
In OSUI, just like in React or other component-based frameworks, content nested inside a component's `rsx!` invocation is implicitly passed as `children`.
When you write:
```rust
rsx! {
MyComponent { "------ example" }
}
```
The string literal `"------ example"` becomes the `children` prop for `MyComponent`.
### `MyComponent` Definition
Let's look at `MyComponent`'s definition:
```rust
#[component]
fn MyComponent(cx: &Arc<Context>, children: &Rsx) -> View {
// ...
}
```
1. **`#[component]`**: Marks `MyComponent` as a reusable component.
2. **`cx: &Arc<Context>`**: The mandatory context parameter.
3. **`children: &Rsx`**: This is where the magic happens. Any content passed as children within the `rsx!` invocation for `MyComponent` (like `"------ example"`) will be collected into an `Rsx` type and passed to this `children` prop. The `Rsx` type itself is a collection of renderable nodes.
4. **`-> View`**: Components must return a `View`.
### Rendering Children with `@{children}`
Inside `MyComponent`'s `rsx!`:
```rust
rsx! {
@{children} // Renders the content passed to MyComponent
"Simple Component"
}
```
* **`@{children}`**: This is an RSX expression. The `@{...}` syntax allows you to embed arbitrary Rust expressions directly into your `rsx!` output. In this case, `children` is an `&Rsx` value which implements `ToRsx`, making it eligible to be directly rendered as part of the component's output. When `children` is rendered, it generates its own `View`, effectively embedding the child content into the parent component's render tree.
* **`"Simple Component"`**: This is a direct text literal within `MyComponent`'s own output, rendered after the children.
This pattern allows you to build highly flexible and composable components, where parents can define the overall structure and layout, while children provide specific content.
## Next Steps
You've seen how to define components and pass basic children. The next step is to explore the full power of OSUI's RSX syntax, including how to pass other types of props, use conditional rendering, and loop through data.
**Next:** Dive into the details of the [RSX Syntax Guide](../guides/01-rsx-syntax.md).
+123
View File
@@ -0,0 +1,123 @@
---
sidebar_position: 0
title: Creating Components
---
# Creating Components
Components are the building blocks of any OSUI application. They encapsulate UI logic, state, and rendering instructions, making your code modular and reusable. This guide explains how to define and use components effectively.
## The `#[component]` Attribute
OSUI uses the `#[component]` procedural macro to transform a regular Rust function into an OSUI component. This macro handles the boilerplate necessary for prop handling and integrating the function into the component tree.
### Basic Structure
A component function generally looks like this:
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
pub fn MyComponent(cx: &Arc<Context>) -> View {
// Component logic goes here
rsx! {
"Hello from MyComponent!"
}.view(&cx)
}
```
**Key requirements:**
1. **`#[component]`**: Always annotate your component function with this attribute.
2. **Function Signature**:
* It must take `cx: &Arc<Context>` as its *first* argument. The `Context` is essential for managing state, events, and child components.
* It must return a `View`. A `View` is an `Arc<dyn Fn(&mut DrawContext) + Send + Sync>`, essentially a closure that contains the drawing instructions for your component.
3. **Return Value**: The most common way to return a `View` is by using the `rsx!` macro followed by `.view(&cx)`.
### Component Props
Components become truly powerful when they can receive data from their parents. These are called "props" (properties). To define props for your component, simply add more parameters to your component function after `cx: &Arc<Context>`.
OSUI's `#[component]` macro automatically generates a struct for your component based on these parameters.
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
pub fn GreetUser(cx: &Arc<Context>, name: &str, age: &u8) -> View {
// Access props directly by their parameter names
rsx! {
format!("Hello, {}! You are {} years old.", name, age)
}.view(&cx)
}
#[component]
pub fn App(cx: &Arc<Context>) -> View {
let user_name = "Alice".to_string();
let user_age = 30;
rsx! {
// Instantiate GreetUser and pass props
GreetUser {
name: user_name, // Prop name matches the parameter name
age: user_age,
}
}.view(&cx)
}
```
**Prop Rules:**
* **Parameter Names**: The names of your function parameters (e.g., `name`, `age`) become the names of the props you use when instantiating the component in `rsx!`.
* **Reference Types**: Props are typically passed as references (e.g., `&str`, `&u8`). The `#[component]` macro automatically "strips" the reference when generating the internal component struct, storing the owned type. This means you don't need to manually clone values unless you intend to move them into a closure or `State`.
* **`children` Prop**: As seen in the [previous guide](../intro/03-example-basic-component.md), any content nested inside a component's `rsx!` invocation is implicitly passed as a `children: &Rsx` prop. This allows for flexible content composition.
### Example: Component with `children` and custom props
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
pub fn Card(cx: &Arc<Context>, title: &str, children: &Rsx) -> View {
rsx! {
format!("--- {} ---", title) // Render the title prop
@{children} // Render the children passed to Card
"----------------"
}.view(&cx)
}
#[component]
pub fn App(cx: &Arc<Context>) -> View {
rsx! {
Card {
title: "My Awesome Card", // Pass a custom prop
// The content below is passed as the `children` prop
rsx! {
"This is the content inside the card."
"It can span multiple lines or include other components."
}
}
Card {
title: "Another Card",
"Just some simple text here." // Even a single string literal can be children
}
}.view(&cx)
}
```
In this example, the `Card` component takes a `title` prop and a `children` prop. It renders the title, then its children, and finally a footer.
## When to Create a Component
* **Reusability**: If you find yourself writing the same UI structure multiple times, extract it into a component.
* **Separation of Concerns**: When a part of your UI has its own state or complex logic, it's a good candidate for a component.
* **Readability**: Breaking down large `rsx!` blocks into smaller components improves the readability and maintainability of your code.
* **Performance (Reactivity)**: Components, especially when using state hooks, allow OSUI to efficiently re-render only the parts of the UI that have changed.
By following these guidelines, you can build well-structured and scalable OSUI applications.
**Next:** Deep dive into the `rsx!` macro and its full capabilities in the [RSX Syntax Guide](./01-rsx-syntax.md).
+192
View File
@@ -0,0 +1,192 @@
---
sidebar_position: 1
title: RSX Syntax Guide
---
# RSX Syntax Guide
The `rsx!` macro is the cornerstone of OSUI's declarative UI system. Inspired by React's JSX, it provides an ergonomic way to define your component hierarchies directly in Rust code. This guide covers all the features of the `rsx!` syntax.
## Basic Structure
The `rsx!` macro produces an `Rsx` object, which is a collection of renderable nodes. You typically call `.view(&cx)` on the `Rsx` object to convert it into a `View` that your component returns.
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
// Your UI elements go here
"Hello, RSX!" // A simple text literal
}.view(&cx)
}
```
## Types of Nodes
`rsx!` supports several types of nodes:
### 1. Text Literals
Plain string literals are rendered as text.
```rust
rsx! {
"This is some text."
"This is another line of text."
}
```
### 2. Rust Expressions: `@{expr}`
You can embed any Rust expression that evaluates to a renderable type (one that implements `ToRsx`) using the `@{...}` syntax. This is useful for dynamic content or rendering other `Rsx` objects.
```rust
let dynamic_text = format!("The current time is: {:?}", std::time::SystemTime::now());
let other_rsx = rsx! { "Some nested content" };
rsx! {
@{dynamic_text} // Renders the string from the expression
@{other_rsx} // Renders another Rsx object
@{123 + 456} // Renders the result of the arithmetic operation (as a string)
}
```
:::tip
Any type that implements `std::fmt::Display` (like `String`, `&str`, `i32`, `f64`, etc.) automatically implements `ToRsx` and can be used directly within `rsx!`.
:::
### 3. Component Instantiation: `ComponentName { prop: value, ... }`
To use another component, specify its name (path) followed by an optional braced block containing its props and children.
```rust
#[component]
fn MyButton(cx: &Arc<Context>, text: &str) -> View {
rsx! {
format!("[ {} ]", text)
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
MyButton { text: "Click Me" } // Component with a prop
MyButton { "Another Button" } // Component with children (which becomes `text` if `children: &Rsx` is also defined)
}.view(&cx)
}
```
* **Props**: Key-value pairs (`prop_name: value`) inside the braces. The `prop_name` must match a parameter name in the target component's function signature.
* **Children**: Any `rsx!` content (text, expressions, other components) directly nested inside the component's braces after props will be collected into the special `children: &Rsx` prop, if defined by the component.
### 4. Conditional Rendering: `@if condition { ... }`
Conditionally render parts of your UI based on a boolean expression.
```rust
let show_message = true;
let is_admin = false;
rsx! {
@if show_message {
"This message is always shown."
}
@if is_admin {
"Admin panel access granted."
} else {
"Access denied." // `else` is optional
}
}
```
* The `condition` must be a Rust expression that evaluates to a `bool`.
* The content inside the `{...}` block is an `rsx!` fragment that will be rendered if the condition is true.
* An optional `else { ... }` block can follow for false conditions.
#### Reactivity with Dependencies: `%$dep @if condition { ... }`
For conditional rendering to react to state changes, you need to explicitly declare dependencies using the `%$dep` syntax.
```rust
let count = use_state(0); // A reactive state
rsx! {
%count @if *count.get() > 0 { // This block re-renders if `count` changes
format!("Count is: {}", count.get_dl())
} else {
"Count is zero."
}
}
```
* **`%count`**: Declares `count` as a dependency for this `if` block. When `count`'s value changes (via `count.set()` or `*count.get_mut()`), this entire `if` block will be re-evaluated and re-rendered.
* You can declare multiple dependencies: `%dep1, dep2, dep3 @if ...`
* You can also rename dependencies for clarity: `%original_name as new_name @if ...`
### 5. Loop Rendering: `@for pattern in expr { ... }`
Render a list of items by iterating over a collection.
```rust
let items = vec!["Apple", "Banana", "Cherry"];
rsx! {
"Fruits:"
@for item in items { // Loops over the `items` vector
format!("- {}", item)
}
"Numbers:"
@for i in (0..3) {
format!("Number: {}", i)
}
}
```
* `pattern` is a standard Rust `for` loop pattern (e.g., `item`, `(index, item)`, `_`).
* `expr` is a Rust expression that evaluates to an `IntoIterator`.
* The content inside the `{...}` block is an `rsx!` fragment that will be rendered for each iteration.
#### Reactivity with Dependencies: `%$dep @for pattern in expr { ... }`
Similar to `@if`, `@for` loops also support dependency tracking for reactive updates.
```rust
let my_list = use_state(vec!["One".to_string(), "Two".to_string()]);
// Later, you might update my_list.set(new_vec);
// or my_list.get_mut().push("Three");
rsx! {
"My Dynamic List:"
%my_list @for item in my_list.get_dl() { // This block re-renders if `my_list` changes
format!("- {}", item)
}
}
```
* **`%my_list`**: Declares `my_list` as a dependency. When `my_list` is updated, the loop will be re-executed, rendering the new list.
### 6. Mount Hook: `!mount_hook_instance`
This syntax is used to explicitly "mount" a component's lifecycle hook. This is specifically for `use_mount_manual`.
```rust
let my_mount_hook = use_mount_manual();
rsx! {
"This text is always visible."
!my_mount_hook // Explicitly triggers the mount effects for `my_mount_hook`
}
```
* When `!my_mount_hook` is encountered in the `rsx!` output, it calls `my_mount_hook.mount()`, triggering any `use_effect` callbacks registered with that specific `Mount` instance.
## Summary
The `rsx!` macro is a powerful tool for building declarative user interfaces in OSUI. By combining text, expressions, components, conditionals, and loops with reactive dependencies, you can create complex and dynamic TUIs with a clean and familiar syntax.
**Next:** Learn how to manage component data over time with OSUI's [State Management hooks](./02-state-management.md).
+195
View File
@@ -0,0 +1,195 @@
---
sidebar_position: 2
title: State Management
---
# State Management
Effective state management is crucial for building interactive and dynamic TUI applications. OSUI provides a React-like hook system that enables components to hold mutable state and react to changes efficiently. This guide covers the core state management hooks: `use_state` and `use_effect`.
## `use_state`: Managing Component-Local State
The `use_state` hook allows your components to declare and manage mutable, reactive state. When state managed by `use_state` changes, OSUI automatically re-renders affected parts of your UI.
### Basic Usage
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn Counter(cx: &Arc<Context>) -> View {
// 1. Initialize state with `use_state`
// `count` is a `State<i32>`, initialized to 0.
let count = use_state(0);
// 2. Define a button that increments the count
// This is a placeholder for actual interactive elements.
// In a real app, an event handler would trigger `count.set()` or `*count.get_mut()`.
let increment_button_simulated = {
let count = count.clone(); // Clone the State handle to move into the closure
move || {
// Option 1: Using `set()` for direct replacement
// count.set(*count.get() + 1);
// Option 2: Using `get()` for mutable access (recommended for complex changes)
*count.get() += 1; // `Inner` guard automatically calls `update()` on drop
}
};
// Simulate clicking the button once per render for demonstration
// In a real app, this would be triggered by user input or other events.
increment_button_simulated();
rsx! {
// 3. Display the state value
// `count.get_dl()` gets a cloned, deadlock-less copy of the value.
// `count.get()` returns a guard for mutable access, also deadlock-less.
format!("Count: {}", count.get_dl())
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(Counter {}).expect("Failed to run Counter app");
}
```
In this example, the `Counter` component's state (`count`) is incremented on each render cycle (simulated). The `rsx!` macro automatically updates to reflect the new `count` value because the `Counter` component is re-rendered by the engine and the `count` state is used.
### `State<T>` and `Inner<'a, T>`
* **`State<T>`**: This is the primary handle to your reactive state. It's an `Arc<Mutex<T>>` internally, allowing safe shared ownership and mutation across threads. When you call `use_state(value)`, you get a `State<T>`.
* **`count.get()`**: This method returns an `Inner<'a, T>` guard. `Inner` implements `Deref` and `DerefMut`, so you can treat it almost like a direct reference to your data (`*count.get()`). The `Inner` guard is crucial because it automatically triggers updates to dependents when it's dropped *if* the value was mutated.
* **`count.set(value)`**: A convenience method to replace the entire state value and then trigger updates.
* **`count.get_dl()`**: Returns a cloned copy of the state value. The "dl" stands for "deadlock-less", as it doesn't hold the mutex lock for an extended period, making it safer for quick reads. Use this when you only need to read the value and cloning is cheap.
* **`count.update()`**: Manually notifies all dependents that the state *might* have changed, even if you didn't use `get_mut()` or `set()`. Useful if you modify the internal `Arc<Mutex<T>>` directly (not recommended) or a complex part of `T` without triggering the `DerefMut` auto-update.
### Important Considerations for `State<T>`:
* **Cloning `State` handles**: `State<T>` itself can be cloned (`count.clone()`). This creates a new `Arc` reference to the *same* underlying state. This is essential when moving `State` into closures or child components.
* **`Send + Sync`**: The type `T` held by `State<T>` must implement `Send` and `Sync` for thread-safe access.
* **Reactivity**: `use_state` makes state reactive. When the value changes, any `use_effect` or `rsx!` dynamic scope (`%state @if...` or `%state @for...`) that declared this `State` as a dependency will be re-evaluated.
## `use_effect`: Performing Side Effects
The `use_effect` hook allows you to perform side effects (e.g., logging, network requests, setting up event listeners) in response to state changes or component mounting.
### Basic Usage
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn EffectExample(cx: &Arc<Context>) -> View {
let count = use_state(0);
let message = use_state("Initial message".to_string());
// Effect 1: Logs when `count` changes
use_effect(
{
let count = count.clone(); // Clone for the closure
move || {
println!("Effect 1: Count changed to {}", count.get_dl());
}
},
&[&count], // Dependencies: &[&dyn HookDependency]
);
// Effect 2: Logs when `message` changes (and also on initial render)
use_effect(
{
let message = message.clone();
move || {
println!("Effect 2: Message is now '{}'", message.get_dl());
}
},
&[&message], // Dependencies: &[&dyn HookDependency]
);
// Simulate state changes (e.g., from user input or timers)
let _ = {
let count = count.clone();
let message = message.clone();
std::thread::spawn(move || {
sleep(500); // Wait 0.5 seconds
*count.get() += 1; // Triggers Effect 1
sleep(500);
message.set("Updated message!".to_string()); // Triggers Effect 2
sleep(500);
*count.get() += 1; // Triggers Effect 1 again
});
};
rsx! {
format!("Count: {}", count.get_dl())
format!("Message: {}", message.get_dl())
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(EffectExample {}).expect("Failed to run EffectExample app");
}
```
### `use_effect` Parameters:
1. **`f: F`**: A closure (`FnMut() + Send + Sync + 'static`) that represents the side effect. This closure will be executed when any of its dependencies change. The closure is run in a separate spawned thread to avoid blocking the main rendering loop.
2. **`dependencies: &[&dyn HookDependency]`**: A slice of references to objects that implement the `HookDependency` trait. These are typically `State<T>` instances. The effect closure will be called whenever any of these dependencies notify an update.
### Understanding Dependencies:
* **Empty Dependency List (`&[]`)**: If you pass an empty slice (`&[]`), the effect will only run once when the component is first mounted. This is useful for setup logic like initializing global resources or subscriptions.
* **Specific Dependencies**: When you list `State` objects as dependencies, the effect will run:
* Once, immediately when `use_effect` is called (during the initial component render).
* Again, whenever any of the listed `State` objects trigger an update.
* **`HookDependency` Trait**: This trait defines how an object can register an `HookEffect` to be called upon update. `State<T>` and `Mount` both implement this trait, making them usable as dependencies.
## Lifecycle Hooks: `use_mount` and `use_mount_manual`
These hooks are special cases of `use_effect` for managing actions tied to a component's "mounting" lifecycle.
* **`use_mount()`**: Returns a `Mount` instance that automatically calls `mount()` immediately upon its creation. Effects registered with this `Mount` instance will run once when the component is first rendered.
* **`use_mount_manual()`**: Returns a `Mount` instance that starts in an unmounted state. Effects registered with it will *only* run when you explicitly call `mount_instance.mount()`. This is useful for controlling the mount event from specific `rsx!` nodes (`!mount_instance`) or from other logic.
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn LifecycleExample(cx: &Arc<Context>) -> View {
let mount_hook = use_mount(); // Automatically mounted
let manual_mount_hook = use_mount_manual(); // Manual mount needed
use_effect(
move || {
println!("Component mounted (automatic hook)!");
},
&[&mount_hook],
);
use_effect(
move || {
println!("Component manually mounted!");
},
&[&manual_mount_hook],
);
rsx! {
"Lifecycle example"
// This will trigger the `manual_mount_hook`'s effects
!manual_mount_hook
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(LifecycleExample {}).expect("Failed to run LifecycleExample app");
}
```
This guide has laid the foundation for managing state and side effects in your OSUI applications. By mastering `use_state` and `use_effect`, you can build sophisticated and responsive TUI experiences.
**Next:** Learn how to handle user interactions and other events with OSUI's [Event Handling system](./03-event-handling.md).
+284
View File
@@ -0,0 +1,284 @@
---
sidebar_position: 3
title: Event Handling
---
# Event Handling
OSUI provides a flexible and type-safe event system that allows components to react to various occurrences, such as user input, internal state changes, or custom application-specific events. This guide explains how to define, emit, and listen for events using `on_event`, `emit_event`, and `emit_event_threaded`.
## Event Propagation Model
In OSUI, events are propagated downwards through the component tree. When an event is emitted from a `Context`, it first triggers all registered handlers on that `Context`, and then recursively calls `emit_event` on all its child `Context`s.
## Defining Custom Events
Any Rust type that implements `Send + Sync + Any + 'static` can be used as an event. Typically, you'll define custom `struct`s or `enum`s for your events to carry specific data.
```rust
// Define a custom event
#[derive(Debug, Clone)]
pub struct ButtonClickEvent {
pub button_id: usize,
pub timestamp: std::time::Instant,
}
// Another custom event
#[derive(Debug, Clone)]
pub enum CustomAppEvent {
Tick,
ReloadData,
}
```
## Listening for Events: `cx.on_event()`
Components can register event handlers using `cx.on_event()` to respond to specific event types.
```rust
use osui::prelude::*;
use std::sync::Arc;
use std::time::Instant;
// Custom event definition
#[derive(Debug, Clone)]
pub struct MyCustomEvent {
pub value: String,
}
#[component]
fn EventListener(cx: &Arc<Context>) -> View {
let received_events = use_state(Vec::<String>::new());
// Register an event handler for `MyCustomEvent`
cx.on_event({
let received_events = received_events.clone(); // Clone State for the closure
move |_ctx, event: &MyCustomEvent| {
// This closure runs when MyCustomEvent is emitted
let mut events_guard = received_events.get();
events_guard.push(format!("Received: {}", event.value));
// No need to call `update()` manually, `Inner` guard handles it on drop
}
});
rsx! {
"Event Listener Component"
@for msg in received_events.get_dl() {
format!("- {}", msg)
}
}.view(&cx)
}
```
* `cx.on_event<T: Any + 'static, F: Fn(&Arc<Self>, &T) + Send + Sync + 'static>(self: &Arc<Self>, handler: F)`:
* `T`: The type of event you want to listen for (e.g., `MyCustomEvent`).
* `F`: A closure that takes `&Arc<Context>` (the component's context) and `&T` (a reference to the event data).
* You pass a closure that captures the necessary state (`received_events` in this case) and logic to execute when the event fires.
## Emitting Events: `cx.emit_event()` and `cx.emit_event_threaded()`
Components or other parts of your application can send events using `cx.emit_event()` or `cx.emit_event_threaded()`.
### `cx.emit_event()` (Synchronous)
`emit_event` processes event handlers sequentially in the current thread.
```rust
// ... (MyCustomEvent and EventListener component definitions from above)
#[component]
fn EventSender(cx: &Arc<Context>) -> View {
let button_clicks = use_state(0);
// Simulate an action that emits an event
let emit_click_event = {
let cx = cx.clone(); // Clone Context for the closure
let button_clicks = button_clicks.clone();
move || {
let current_clicks = *button_clicks.get();
*button_clicks.get() += 1;
let event = MyCustomEvent {
value: format!("Button clicked {} times", current_clicks + 1),
};
cx.emit_event(event); // Emit the event
}
};
// Simulate clicking the button every second
use_effect(
{
let emit_click_event = emit_click_event.clone();
move || {
loop {
sleep(1000); // Wait 1 second
emit_click_event();
}
}
},
&[], // No dependencies, run once on mount
);
rsx! {
format!("Button clicks: {}", button_clicks.get_dl())
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
// EventSender and EventListener are siblings in the tree.
// Events emitted by EventSender will propagate down to EventListener.
EventSender {}
EventListener {}
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run App");
}
```
### `cx.emit_event_threaded()` (Asynchronous)
`emit_event_threaded` spawns a new thread for each registered event handler. This is useful for long-running or potentially blocking event handlers, preventing them from freezing your UI.
```rust
// ... (MyCustomEvent, EventListener definitions)
#[component]
fn ThreadedEventSender(cx: &Arc<Context>) -> View {
let cx_clone = cx.clone();
// Emit an event using `emit_event_threaded` on mount
use_effect(
move || {
println!("Emitting threaded event on mount!");
let event = MyCustomEvent {
value: "Threaded mount event".to_string(),
};
cx_clone.emit_event_threaded(&event); // Notice the `&event` for threaded
},
&[], // Run once on mount
);
rsx! {
"Threaded Event Sender"
}.view(&cx)
}
#[component]
fn AppWithThreaded(cx: &Arc<Context>) -> View {
rsx! {
ThreadedEventSender {}
EventListener {} // This listener will receive the threaded event
}.view(&cx)
}
// To run: engine.run(AppWithThreaded {})
```
* `cx.emit_event_threaded<E: Any + Send + Sync + Clone + 'static>(self: &Arc<Self>, event: &E)`:
* Takes `&E` (a reference to the event), which must implement `Clone` because each spawned thread receives a clone of the event data.
* Each handler for `E` will be executed in its own `std::thread::spawn`.
## Realistic Usage Scenario: Interactive Counter
Let's create a more interactive counter that responds to keyboard input.
```rust
use osui::prelude::*;
use std::sync::Arc;
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
// Define a custom event for counter actions
#[derive(Debug, Clone)]
pub enum CounterAction {
Increment,
Decrement,
}
#[component]
fn InteractiveCounter(cx: &Arc<Context>) -> View {
let count = use_state(0);
// Listen for CounterAction events
cx.on_event({
let count = count.clone();
move |_ctx, action: &CounterAction| {
let mut count_guard = count.get();
match action {
CounterAction::Increment => *count_guard += 1,
CounterAction::Decrement => *count_guard -= 1,
}
}
});
rsx! {
"Press 'q' to quit, '+' to increment, '-' to decrement."
format!("Current Count: {}", count.get_dl())
}.view(&cx)
}
// A component (or `main` function logic) to poll keyboard input
// and emit CounterAction events.
#[component]
fn KeyboardInputHandler(cx: &Arc<Context>) -> View {
// This effect runs once on mount to start the keyboard polling thread
use_effect(
{
let cx = cx.clone();
move || {
let _ = crossterm::terminal::enable_raw_mode();
loop {
if event::poll(std::time::Duration::from_millis(50)).unwrap() {
if let Event::Key(key_event) = event::read().unwrap() {
if key_event.kind == KeyEventKind::Press {
match key_event.code {
KeyCode::Char('+') => cx.emit_event(CounterAction::Increment),
KeyCode::Char('-') => cx.emit_event(CounterAction::Decrement),
KeyCode::Char('q') => {
let _ = crossterm::terminal::disable_raw_mode();
cx.stop().expect("Failed to stop engine");
break;
},
_ => {}
}
}
}
}
}
}
},
&[], // Empty deps: run once on mount
);
// This component doesn't render anything visible itself.
// Its purpose is purely to handle input and emit events.
rsx! { "" }.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(AppWithKeyboardInput {}).expect("Failed to run interactive app");
let _ = crossterm::terminal::disable_raw_mode(); // Ensure raw mode is disabled on exit
}
#[component]
fn AppWithKeyboardInput(cx: &Arc<Context>) -> View {
rsx! {
KeyboardInputHandler {} // Handles input and emits events
InteractiveCounter {} // Listens for events and updates UI
}.view(&cx)
}
```
In this example:
* `KeyboardInputHandler` runs in a separate thread (due to `use_effect`'s spawning behavior).
* It polls for keyboard events using `crossterm`.
* Upon detecting `+`, `-`, or `q`, it emits a `CounterAction` or `Stop` command to its `Context`.
* `InteractiveCounter` listens for `CounterAction` events and updates its internal `count` state, which then causes it to re-render.
* The `stop()` command, emitted by `KeyboardInputHandler`, instructs the `Console` engine to terminate its rendering loop.
This showcases a full interaction loop using custom events for communication between components.
**Next:** Explore specialized lifecycle management with `use_mount` and `use_mount_manual` in [Lifecycle Hooks](./04-lifecycle-hooks.md).
+149
View File
@@ -0,0 +1,149 @@
---
sidebar_position: 4
title: Lifecycle Hooks
---
# Lifecycle Hooks
In OSUI, components have a lifecycle, meaning they go through various stages from creation to destruction. While OSUI doesn't expose a full set of lifecycle methods like some frameworks, it provides powerful hooks for managing effects related to a component's "mounting" phase: `use_mount` and `use_mount_manual`. These are special applications of the `use_effect` hook, designed for setup logic.
## The "Mounted" Concept
A component is considered "mounted" when it has been rendered for the first time and is part of the active component tree. Lifecycle hooks allow you to run code precisely at this point.
## `use_mount()`: Automatic Mounting
The `use_mount()` hook provides a `Mount` instance that is automatically marked as "mounted" upon its creation. Any `use_effect` that depends on this `Mount` instance will execute its effect closure immediately when the component renders.
### When to use `use_mount()`:
* **Initial Setup**: Performing actions that should only happen once when the component first appears on screen, such as fetching initial data, setting up global event listeners, or initializing complex external resources.
* **No Cleanup Required**: For effects that don't require any cleanup logic. If cleanup is needed, ensure your effect closure handles it or consider using `use_effect` with an empty dependency array (which is similar in behavior for initial execution, but offers more control over subsequent runs if dependencies are added).
### Example: Initial Data Fetch
```rust
use osui::prelude::*;
use std::sync::Arc;
use std::time::Duration;
// Simulate a data fetching operation
async fn fetch_data_async() -> String {
// In a real application, this would be an actual network request or disk read.
tokio::time::sleep(Duration::from_secs(1)).await; // Simulate network delay
"Data loaded successfully!".to_string()
}
#[component]
fn DataLoader(cx: &Arc<Context>) -> View {
let data_state = use_state("Loading data...".to_string());
let mount_hook = use_mount(); // Automatically initializes as "mounted"
// Use `use_effect` with `mount_hook` as a dependency
use_effect(
{
let data_state = data_state.clone();
move || {
// This effect runs once when `mount_hook` is initialized (component mounts)
println!("DataLoader: Component mounted, starting data fetch...");
// Spawn a new async task (requires a runtime like tokio)
tokio::spawn(async move {
let data = fetch_data_async().await;
data_state.set(data); // Update state, triggering re-render
println!("DataLoader: Data fetch complete.");
});
}
},
&[&mount_hook], // Effect runs when `mount_hook` updates (i.e., on mount)
);
rsx! {
format!("Status: {}", data_state.get_dl())
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
DataLoader {}
}.view(&cx)
}
pub fn main() {
// For async operations, you need a runtime like tokio.
// Add `tokio = { version = "1", features = ["full"] }` to your Cargo.toml
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let engine = Console::new();
engine.run(App {}).expect("Failed to run async app");
});
}
```
## `use_mount_manual()`: Explicit Mounting
The `use_mount_manual()` hook provides a `Mount` instance that starts in an "unmounted" state. Its associated `use_effect` callbacks will *not* run until you explicitly call the `mount()` method on that specific `Mount` instance. This offers finer control over when initial setup logic executes.
### When to use `use_mount_manual()`:
* **Conditional Mounting**: When you want to delay the "mount" logic until a specific condition is met or an interaction occurs.
* **`rsx!`-driven Mounting**: You can trigger the `mount()` method directly from your `rsx!` template using the `!mount_hook_instance` syntax. This is useful if the mount event is tied to the presence of a specific element or a complex rendering path.
### Example: Delayed Mount with `rsx!`
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn DelayedSetup(cx: &Arc<Context>) -> View {
let setup_status = use_state("Waiting for manual mount...".to_string());
let manual_mount_hook = use_mount_manual(); // Starts unmounted
use_effect(
{
let setup_status = setup_status.clone();
move || {
// This effect will only run when `manual_mount_hook.mount()` is called.
println!("DelayedSetup: Manual mount triggered, performing setup!");
setup_status.set("Setup complete!".to_string());
}
},
&[&manual_mount_hook], // Depends on the manual mount hook
);
rsx! {
format!("Status: {}", setup_status.get_dl())
// The `!manual_mount_hook` in RSX triggers `manual_mount_hook.mount()`
// which then causes the `use_effect` to run.
!manual_mount_hook
"This component is now mounted."
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
DelayedSetup {}
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run delayed mount app");
}
```
In this example, the "Setup complete!" message will appear only after the `!manual_mount_hook` line is processed by the `rsx!` renderer, explicitly calling `mount_hook.mount()`.
## Summary of Lifecycle Hooks
* `use_mount()`: For effects that should run once immediately when the component is initially rendered.
* `use_mount_manual()`: For effects that you want to explicitly control when they run, either through programmatic calls to `.mount()` or via the `!mount_hook_instance` syntax in `rsx!`.
These hooks, combined with `use_effect`, give you fine-grained control over when side effects are performed during your component's lifetime.
**Next:** Understand how to synchronize component state with events for sophisticated data flow patterns in [Data Flow and Sync](./05-data-flow-and-sync.md).
+215
View File
@@ -0,0 +1,215 @@
---
sidebar_position: 5
title: Data Flow and Sync
---
# Data Flow and Synchronization
OSUI's reactive state system and event handling capabilities provide a robust foundation for managing data flow within your application. The `use_sync_state` and `use_sync_effect` hooks offer powerful patterns for synchronizing component state with external events and vice versa, enabling sophisticated inter-component communication and data management.
## `use_sync_state`: State from Events
`use_sync_state` allows a component's internal `State` to be automatically updated whenever a specific type of event is emitted to its `Context`. This is a powerful way to inject external data or changes into a component's reactive state.
### How it works:
It combines `use_state` and `cx.on_event`.
1. You provide an initial value for the state.
2. You specify the event type (`E`) and a `decoder` function.
3. The `decoder` function takes an `&E` event and returns a new value `T` for the state.
4. Whenever an event of type `E` is emitted to the current `Context`, the `decoder` runs, and the internal `State<T>` is updated.
```rust
use osui::prelude::*;
use std::sync::Arc;
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
// Define a simple event to change a message
#[derive(Debug, Clone)]
pub struct MessageChangeEvent(pub String);
#[component]
fn MessageDisplay(cx: &Arc<Context>) -> View {
// Use `use_sync_state` to update the message based on `MessageChangeEvent`
let message = use_sync_state(
cx,
"Initial Message".to_string(), // Initial state value
|event: &MessageChangeEvent| event.0.clone(), // Decoder: extract string from event
);
rsx! {
"Received Message:"
format!(" {}", message.get_dl())
}.view(&cx)
}
#[component]
fn MessageInput(cx: &Arc<Context>) -> View {
// Simulate input by reacting to key presses and emitting MessageChangeEvent
use_effect(
{
let cx = cx.clone();
move || {
let _ = crossterm::terminal::enable_raw_mode();
loop {
if event::poll(std::time::Duration::from_millis(50)).unwrap() {
if let Event::Key(key_event) = event::read().unwrap() {
if key_event.kind == KeyEventKind::Press {
match key_event.code {
KeyCode::Char(c) => {
let msg = format!("Typed: {}", c);
cx.emit_event(MessageChangeEvent(msg));
},
KeyCode::Enter => {
cx.emit_event(MessageChangeEvent("Enter pressed!".to_string()));
},
KeyCode::Esc => {
let _ = crossterm::terminal::disable_raw_mode();
cx.stop().expect("Failed to stop engine");
break;
},
_ => {}
}
}
}
}
}
}
},
&[], // Run once on mount
);
rsx! {
"Type something to change the message (Esc to quit):"
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
MessageInput {} // Emits MessageChangeEvent
MessageDisplay {} // Synchronizes its state with MessageChangeEvent
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run app");
let _ = crossterm::terminal::disable_raw_mode();
}
```
In this example, `MessageInput` emits `MessageChangeEvent`s based on keyboard input. `MessageDisplay` automatically updates its `message` state whenever it receives one of these events from its parent `Context`, thanks to `use_sync_state`.
## `use_sync_effect`: Events from State
`use_sync_effect` allows changes in a component's internal `State` to automatically trigger the emission of a specific type of event to its `Context`. This is useful for communicating state changes upwards or to sibling components.
### How it works:
It combines `use_effect` and `cx.emit_event`.
1. You provide an `State<T>` instance you want to monitor.
2. You specify an `encoder` function and optional dependencies.
3. The `encoder` function takes a `&State<T>` and returns an event `Ev`.
4. Whenever the `State<T>` changes (or any specified dependencies), the `encoder` runs, and the generated event `Ev` is emitted to the current `Context`.
```rust
use osui::prelude::*;
use std::sync::Arc;
use std::collections::HashMap;
// Event to signal a counter has changed
#[derive(Debug, Clone)]
pub struct CounterUpdatedEvent {
pub id: usize,
pub new_value: i32,
}
#[component]
fn ChildCounter(cx: &Arc<Context>, id: &usize, initial_value: &i32) -> View {
let count = use_state(*initial_value);
// Use `use_sync_effect` to emit `CounterUpdatedEvent` when `count` changes
use_sync_effect(
cx,
&count, // Monitor this state
move |state_ref: &State<i32>| {
// Encoder: create an event from the state
CounterUpdatedEvent {
id: *id,
new_value: state_ref.get_dl(),
}
},
&[&count], // Effect runs when `count` changes
);
// Simulate incrementing the counter periodically
use_effect(
{
let count = count.clone();
move || {
loop {
sleep(1000); // Increment every second
*count.get() += 1;
}
}
},
&[], // Run once on mount
);
rsx! {
format!("Counter {}: {}", id, count.get_dl())
}.view(&cx)
}
#[component]
fn ParentDashboard(cx: &Arc<Context>) -> View {
let all_counts = use_state(HashMap::<usize, i32>::new());
// Listen for `CounterUpdatedEvent` from children
cx.on_event({
let all_counts = all_counts.clone();
move |_ctx, event: &CounterUpdatedEvent| {
let mut counts_guard = all_counts.get();
counts_guard.insert(event.id, event.new_value);
}
});
rsx! {
"Dashboard Overview:"
@for (id, value) in all_counts.get_dl() {
format!(" Counter {}: {}", id, value)
}
"---"
ChildCounter { id: 1, initial_value: 0 }
ChildCounter { id: 2, initial_value: 10 }
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
ParentDashboard {}
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run app");
}
```
In this example:
* `ChildCounter` uses `use_sync_effect` to emit a `CounterUpdatedEvent` every time its internal `count` state changes.
* `ParentDashboard` listens for these `CounterUpdatedEvent`s (which bubble up from its children) using `cx.on_event` and updates its own `all_counts` `HashMap` state. This `HashMap` then drives the display in the dashboard.
## When to use `use_sync_state` and `use_sync_effect`:
* **Inter-component Communication**: When components need to communicate beyond simple prop passing. Events are excellent for sibling-to-sibling or child-to-ancestor communication without prop drilling.
* **Centralized State Management**: You can have a central "store" component that emits events, and other components `use_sync_state` to react to those events.
* **External System Integration**: When your TUI needs to react to external system events (e.g., file changes, network updates) by mapping them to internal `State`.
* **Decoupling**: They help decouple components, as they don't need direct references to each other, only awareness of event types.
By combining `use_state`, `use_effect`, `use_sync_state`, and `use_sync_effect` with OSUI's event system, you can build powerful and maintainable data flow architectures for your TUI applications.
**Next:** Learn how to arrange your components visually using OSUI's rendering primitives in [Building Complex Layouts](./06-building-complex-layouts.md).
@@ -0,0 +1,155 @@
---
sidebar_position: 6
title: Building Complex Layouts
---
# Building Complex Layouts
OSUI provides foundational primitives for rendering text and components, allowing you to compose them into complex layouts. While OSUI doesn't include a built-in layout engine (like flexbox or grid), it exposes the `DrawContext` and geometric types (`Point`, `Area`, `Size`) that enable you to manually position elements or build your own layout components.
## The Rendering Pipeline
At its core, OSUI's rendering works by accumulating `DrawInstruction`s into a `DrawContext`. The engine then takes this `DrawContext` and executes the instructions to draw to the terminal.
1. **`View`**: A component returns a `View`, which is essentially a closure that takes a mutable `DrawContext` and adds drawing instructions to it.
2. **`DrawContext`**: This is the canvas for your component. It has an `area` (the total space available to the current component) and an `allocated` area (the space currently used by drawing instructions within that component).
3. **`DrawInstruction`**: The actual commands to draw, like `Text`, `View` (for child components), or `Child` (for nested `DrawContext`s).
## Core Rendering Primitives
These types, found in the `osui::render` module, are essential for manual layout.
* ### `Point`
Represents a position `(x, y)` in terminal coordinates. `x` is column, `y` is row.
```rust
pub struct Point {
pub x: u16,
pub y: u16,
}
```
* ### `Size`
Represents `width` and `height` in terminal columns and rows.
```rust
pub struct Size {
pub width: u16,
pub height: u16,
}
```
* ### `Area`
Combines `Point` and `Size` to define a rectangular region.
```rust
pub struct Area {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
```
## `DrawContext`: Your Drawing Canvas
Inside a `View` closure, you receive a mutable `DrawContext`. This is how you interact with the rendering system.
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn MyCustomLayout(cx: &Arc<Context>, children: &Rsx) -> View {
let children_view = children.view(&cx); // Convert Rsx children to a View
Arc::new(move |ctx: &mut DrawContext| {
// `ctx.area` gives you the total available space for this component.
let available_width = ctx.area.width;
let available_height = ctx.area.height;
// --- Manual layout example: Two columns ---
// Allocate space for the left column
let left_col_area = ctx.allocate(
ctx.area.x,
ctx.area.y,
available_width / 2,
available_height,
);
// Draw some text in the left column
ctx.draw_text(
Point { x: left_col_area.x + 1, y: left_col_area.y + 1 },
"Left Panel",
);
ctx.draw_text(
Point { x: left_col_area.x + 1, y: left_col_area.y + 2 },
&format!("Available: {}x{}", left_col_area.width, left_col_area.height),
);
// Allocate space for the right column
let right_col_area = ctx.allocate(
ctx.area.x + available_width / 2, // Start x at half width
ctx.area.y,
available_width / 2,
available_height,
);
// Draw the children (passed to MyCustomLayout) into the right column
// This effectively "moves" the children's rendering into this specific area.
ctx.draw_view(
right_col_area, // Children will render relative to this new area
children_view.clone(),
);
// Draw more text in the right column
ctx.draw_text(
Point { x: right_col_area.x + 1, y: right_col_area.y + 1 },
"Right Panel (Children Area)",
);
})
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
MyCustomLayout {
rsx! {
"Hello from the child content!"
"This text should appear in the right panel."
}
}
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run app");
}
```
### Key `DrawContext` Methods:
* **`ctx.area`**: The `Area` that the *current component* has been allocated by its parent. All drawing coordinates are relative to `ctx.area.x` and `ctx.area.y`.
* **`ctx.allocate(x, y, width, height)`**: This method marks a region within `ctx.area` as "used". It takes coordinates relative to `ctx.area`'s top-left corner (0,0 of its own space) and returns a new `Area` representing the allocated sub-region. It also updates `ctx.allocated` to be the union of all allocations so far within this `DrawContext`.
* **`ctx.draw_text(point, text)`**: Adds a `Text` instruction. `point` is relative to `ctx.area`.
* **`ctx.draw_view(area, view)`**: Adds a `View` instruction. This is how you tell the renderer to draw a child component (or another `View`) within a specific `area`. The `area` here is also relative to `ctx.area`. The child view will then receive this `area` as its own `ctx.area`.
### Building a Simple Layout Component
The `MyCustomLayout` component above demonstrates a basic two-column layout. You can create more sophisticated layout components by:
1. **Calculating Sub-Regions**: Based on `ctx.area.width` and `ctx.area.height`, divide the space into logical sub-regions (e.g., header, footer, sidebar, main content).
2. **Allocating Space**: Use `ctx.allocate()` to define these sub-regions.
3. **Drawing Content**:
* For static text or background elements, use `ctx.draw_text()`.
* For child components, call `child_rsx.view(&cx)` to get their `View`, and then use `ctx.draw_view(sub_area, child_view)` to render them in their designated space.
### Tips for Layouts:
* **Relative Positioning**: Always think of `Point` and `Area` coordinates as being *relative* to the `ctx.area` of the current `View` being rendered. The `Console` engine handles translating these relative coordinates to absolute terminal coordinates.
* **No Overlapping**: Be mindful of overlapping areas. If you draw two things to the same `Point`, the last one drawn will overwrite the first. OSUI does not automatically manage Z-ordering.
* **Responsive Design**: Consider how your layouts will adapt to different terminal sizes. You can use `ctx.area.width` and `ctx.area.height` to make calculations dynamic.
* **Composition**: Layout components can themselves be children of other layout components, allowing you to build complex nested structures.
While implementing a full layout system like CSS Flexbox is beyond the scope of OSUI's core, these primitives empower you to craft highly customized and visually rich terminal interfaces by manually managing space and component placement.
**Next:** Explore the detailed [API Reference](../reference/00-crate-structure.md) for all OSUI modules and types.
+50
View File
@@ -0,0 +1,50 @@
---
sidebar_position: 0
title: Crate Structure
---
# OSUI Crate Structure
The `osui` library is organized into several modules, each responsible for a distinct aspect of TUI development. Understanding this structure helps in navigating the codebase and locating relevant functionalities.
## Top-Level Modules
The `osui` crate is composed of the following main modules:
* **`component`**: Defines the core component system, including `Context` (for component state and lifecycle), `Scope` (for child management), and the `ComponentImpl` trait.
* **`engine`**: Provides the rendering engine trait (`Engine`), command execution system (`CommandExecutor`), and concrete engine implementations like `Console` (for `crossterm`) and `Benchmark`.
* **`frontend`**: Implements the RSX (React-like Syntax) system, including the `Rsx` struct and `ToRsx` trait, which bridges the `rsx!` macro output to the rendering pipeline.
* **`render`**: Contains low-level rendering primitives such as `DrawContext`, `DrawInstruction`, `Point`, `Area`, and `Size`. This module defines *what* gets drawn.
* **`state`**: Offers React-like hooks for managing component state (`use_state`), side effects (`use_effect`), and component lifecycle (`use_mount`, `use_mount_manual`).
## `prelude` Module
The `osui::prelude` module re-exports the most commonly used items from all sub-modules. It's recommended to `use osui::prelude::*;` in your application to easily access essential types and macros without verbose imports.
```rust
pub mod prelude {
pub use crate::component::{context::*, scope::*, *};
pub use crate::engine::*;
pub use crate::frontend::*;
pub use crate::render::*;
pub use crate::state::*;
pub use crate::{sleep, Error, Result, View, ViewWrapper};
pub use crossterm;
pub use osui_macros::{component, rsx};
pub use std::sync::{Arc, Mutex};
}
```
## OSUI Core Types
Beyond the modules, `lib.rs` also defines some fundamental type aliases and error handling:
* **`View`**: `Arc<dyn Fn(&mut DrawContext) + Send + Sync>`. Represents a renderable unit, essentially a closure that takes a `DrawContext` and adds drawing instructions.
* **`ViewWrapper`**: `Arc<dyn Fn(&mut DrawContext, View) + Send + Sync>`. A higher-order view that can wrap and modify how another `View` is rendered (e.g., for applying layout or styling).
* **`Result<T>`**: `std::result::Result<T, Error>`. The standard result type for OSUI operations.
* **`Error`**: An enum defining OSUI-specific errors, currently including `PoisonError` for mutex poisoning.
* **`sleep(delay_ms: u64)`**: A utility function for pausing execution for a specified duration.
This structured approach helps keep the library organized and maintainable, allowing developers to quickly understand where to find the tools they need.
**Next:** Dive into the details of the [Component API](./component-api.md).
+117
View File
@@ -0,0 +1,117 @@
---
sidebar_position: 1
title: Component API
---
# Component Module API Reference
The `component` module is the heart of OSUI's UI system, defining how reusable UI units are structured, manage their state, and interact.
## `ComponentImpl` Trait
```rust
pub trait ComponentImpl: Send + Sync {
fn call(&self, cx: &Arc<Context>) -> View;
}
```
The `ComponentImpl` trait is implemented by all types that can act as an OSUI component.
* **`call(&self, cx: &Arc<Context>) -> View`**: The core method that renders the component. It takes a reference to the component itself (`self`) and the component's `Context` (`cx`), and returns a `View` which contains the drawing instructions.
**Implementations:**
* **`View`**: A bare `View` (an `Arc<dyn Fn(&mut DrawContext) + Send + Sync>`) can itself be a `ComponentImpl`, simply returning itself.
* **`Fn(&Arc<Context>) -> View`**: Any closure with this signature can also be a `ComponentImpl`.
* **`#[component]` macro**: The `#[component]` macro automatically generates a struct and implements `ComponentImpl` for it, wrapping your component function.
## `Component` Type Alias
```rust
pub type Component = Arc<dyn ComponentImpl>;
```
A convenience type alias for a `ComponentImpl` wrapped in an `Arc`, allowing for shared, thread-safe ownership.
## `EventHandler` Type Alias
```rust
pub type EventHandler = Arc<Mutex<dyn FnMut(&Arc<Context>, &dyn Any) + Send + Sync>>;
```
A type alias for a mutex-protected, thread-safe, mutable closure that handles events. Event handlers receive the component's `Context` and a reference to the event data (as `&dyn Any`).
## `context` Module
Contains the `Context` struct, which is central to each component instance.
### `Context` Struct
```rust
pub struct Context {
component: AccessCell<Component>,
view: AccessCell<View>,
event_handlers: AccessCell<HashMap<TypeId, Vec<EventHandler>>>,
pub(crate) scopes: Mutex<Vec<Arc<Scope>>>,
executor: Arc<dyn CommandExecutor>,
}
```
The `Context` holds the runtime state and behavior for a specific component instance. It manages the component's `View`, its registered event handlers, and its child `Scope`s.
#### Methods:
* **`fn new<F: ComponentImpl + 'static>(component: F, executor: Arc<dyn CommandExecutor>) -> Arc<Self>`**
* Creates a new `Arc` wrapped `Context` for the given component and command executor.
* **`fn refresh(self: &Arc<Self>)`**
* Re-renders the component by clearing existing event handlers and calling the component's `call` method to produce a new `View`. This is typically called automatically by the engine or `Rsx`.
* **`fn refresh_sync(self: &Arc<Self>)`**
* Synchronously re-renders the component, blocking until the view closure has finished executing.
* **`fn get_view(self: &Arc<Self>) -> View`**
* Returns a clone of the component's current `View`.
* **`fn on_event<T: Any + 'static, F: Fn(&Arc<Self>, &T) + Send + Sync + 'static>(self: &Arc<Self>, handler: F)`**
* Registers an event handler `F` for events of type `T`. When an event of type `T` is `emit`ted to this `Context`, the handler `F` will be invoked.
* **`fn emit_event<E: Send + Sync + Any + 'static>(self: &Arc<Self>, event: E)`**
* Emits an event `E`. All registered handlers for type `E` on this `Context` are called synchronously, and then the event is propagated to all child components.
* **`fn emit_event_threaded<E: Any + Send + Sync + Clone + 'static>(self: &Arc<Self>, event: &E)`**
* Emits an event `E`. Each registered handler for type `E` on this `Context` is called in a *newly spawned thread*. The event is then propagated to all child components (also using `emit_event_threaded`). Requires `E` to be `Clone`.
* **`fn scope(self: &Arc<Self>) -> Arc<Scope>`**
* Creates a new, empty child `Scope` and adds it to this `Context`. Returns the new `Scope`.
* **`fn dyn_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(self: &Arc<Self>, drawer: F, dependencies: &[&dyn HookDependency]) -> Arc<Scope>`**
* Creates a new *dynamic* child `Scope` that re-renders (by calling `drawer`) whenever any of its `dependencies` change. `drawer` is also called immediately. Returns the new `Scope`.
* **`fn add_scope(self: &Arc<Self>, scope: Arc<Scope>)`**
* Adds an already constructed `Scope` as a child to this `Context`.
* **`fn draw_children(self: &Arc<Self>, ctx: &mut DrawContext)`**
* Iterates through all child `Scope`s and their components, rendering them into the provided `DrawContext`. Handles `ViewWrapper`s if present.
* **`fn get_executor(self: &Arc<Self>) -> Arc<dyn CommandExecutor>`**
* Returns a clone of the `CommandExecutor` associated with this `Context`.
* **`fn execute<T: Command + 'static>(self: &Arc<Self>, command: T) -> crate::Result<()>`**
* Executes a given `Command` using the associated `CommandExecutor`.
* **`fn stop(self: &Arc<Self>) -> crate::Result<()>`**
* A convenience method to execute the `Stop` command, terminating the application.
## `scope` Module
Contains the `Scope` struct, which organizes child components within a `Context`.
### `Scope` Struct
```rust
pub struct Scope {
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
executor: Arc<dyn CommandExecutor>,
}
```
A `Scope` groups child components. Each entry in `children` consists of a child `Context` and an optional `ViewWrapper` that can modify its rendering.
#### Methods:
* **`fn new(executor: Arc<dyn CommandExecutor>) -> Arc<Self>`**
* Creates a new `Arc` wrapped `Scope` with the given `CommandExecutor`.
* **`fn child<F: ComponentImpl + 'static>(self: &Arc<Self>, child: F, view_wrapper: Option<ViewWrapper>)`**
* Creates a new `Context` for the provided `child` component, refreshes it, and adds it to this `Scope`'s children, optionally with a `ViewWrapper`.
* **`fn view(self: &Arc<Self>, view: View)`**
* Creates a new `Context` directly from a `View` (without an explicit `ComponentImpl`), refreshes it, and adds it to this `Scope`'s children.
This module forms the backbone of how component trees are constructed and managed in OSUI.
**Next:** Explore the [Engine API](./engine-api.md).
+163
View File
@@ -0,0 +1,163 @@
---
sidebar_position: 2
title: Engine API
---
# Engine Module API Reference
The `engine` module defines the core interfaces for how OSUI applications run, render, and execute commands. It provides abstractions for different rendering backends and includes a default console implementation.
## `Engine` Trait
```rust
pub trait Engine<Output = ()> {
fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<Output>;
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context>;
fn render(&self, cx: &Arc<Context>);
fn render_delay(&self);
fn render_view(&self, area: &Area, view: &View) -> DrawContext;
fn draw_context(&self, ctx: &DrawContext);
fn executor(&self) -> Arc<dyn CommandExecutor>;
}
```
The `Engine` trait is the primary interface for running an OSUI application. It abstracts away the specifics of how components are initialized, rendered, and how the application loop is managed.
#### Associated Types / Generics:
* **`Output`**: A generic type that allows `run` to return different results. By default, it's `()`, but for specialized engines (like `Benchmark`), it can return custom data.
#### Methods:
* **`fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<Output>`**
* The main entry point to start the OSUI application loop. It takes the root component `C`, initializes it, and continuously renders until a stop command is issued. Returns `Ok(())` by default, or a custom `Output` for specialized engines.
* **`fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context>`**
* Initializes the rendering environment and creates the root `Context` for the application's top-level component. This is typically called by `run`.
* **`fn render(&self, cx: &Arc<Context>)`**
* Performs a full render cycle for the given `Context`. This usually involves clearing the screen, calling `render_view` for the root, and then `draw_context`.
* **`fn render_delay(&self)`**
* A hook for introducing a delay between render frames. The default implementation calls `crate::sleep(16)` for approximately 60 frames per second.
* **`fn render_view(&self, area: &Area, view: &View) -> DrawContext`**
* Takes a `View` and the `Area` it should render within, executing the `View`'s closure to produce a `DrawContext` filled with `DrawInstruction`s.
* **`fn draw_context(&self, ctx: &DrawContext)`**
* Executes the drawing instructions contained within a `DrawContext` to actually draw content to the rendering target (e.g., the terminal).
* **`fn executor(&self) -> Arc<dyn CommandExecutor>`**
* Returns the `CommandExecutor` instance used by this engine.
## `Command` Trait
```rust
pub trait Command {
fn as_any(&self) -> &dyn Any;
}
```
The `Command` trait is implemented by types that represent actions or instructions that can be executed by the `CommandExecutor`. This trait enables a type-safe way to send commands between components and the engine.
* **`fn as_any(&self) -> &dyn Any`**: Allows downcasting the command to its concrete type for pattern matching and execution.
## `CommandExecutor` Trait
```rust
pub trait CommandExecutor: Send + Sync {
fn execute_command(&self, command: &Arc<dyn Command>) -> crate::Result<()>;
}
```
The `CommandExecutor` trait defines how commands are processed within the OSUI application. Engines provide their own implementations of this trait to handle system-level operations.
* **`fn execute_command(&self, command: &Arc<dyn Command>) -> crate::Result<()>`**: Takes an `Arc` wrapped `Command` and executes it. Implementations typically use `command.as_any().downcast_ref()` to identify and process specific commands.
## `console` Module: `Console` Engine and `ConsoleExecutor`
The `console` module provides OSUI's default, `crossterm`-based rendering engine.
### `Console` Struct
```rust
pub struct Console {
threads: Mutex<Vec<Arc<dyn Fn(Arc<Context>) + Send + Sync>>>,
executor: Arc<ConsoleExecutor>,
}
```
The `Console` struct implements the `Engine` trait, specifically designed to render to a terminal using `crossterm`.
#### Methods:
* **`fn new() -> Self`**: Creates a new `Console` engine.
* **`fn thread<F: Fn(Arc<Context>) + Send + Sync + 'static>(&self, run: F)`**: Registers a closure to be run in a separate thread when the engine initializes. This is useful for background tasks or input polling that needs access to the main `Context`.
#### `Engine` Trait Implementation:
The `Console` implements all methods of the `Engine` trait, handling terminal setup (raw mode, cursor hiding), screen clearing, `crossterm` cursor movements, and text output.
### `ConsoleExecutor` Struct
```rust
pub struct ConsoleExecutor {
running: Mutex<bool>,
}
```
The `ConsoleExecutor` implements the `CommandExecutor` trait for the `Console` engine. It manages the `running` state of the application.
#### Methods:
* **`fn is_running(self: &Arc<ConsoleExecutor>) -> bool`**: Checks if the application is currently running.
* **`fn stop(&self) -> crate::Result<()>`**: Sets the internal `running` flag to `false`, signaling the `Console` engine to terminate its `run` loop.
#### `CommandExecutor` Trait Implementation:
The `ConsoleExecutor` currently supports handling the `commands::Stop` command.
## `commands` Module: Built-in Commands
The `commands` module defines simple, built-in commands for the engine.
### `Stop` Command
```rust
pub struct Stop;
impl Command for Stop {
fn as_any(&self) -> &dyn std::any::Any;
}
```
A basic command used to signal the `Engine` to stop its main loop and exit the application.
## `benchmark` Module: `Benchmark` Engine
The `benchmark` module provides a wrapper engine for performance testing.
### `BenchmarkResult` Struct
```rust
pub struct BenchmarkResult {
pub average: u128,
pub min: u128,
pub max: u128,
pub total_render: u128,
pub total: u128,
}
```
Holds the statistical results of a benchmark run, including average, minimum, maximum, total render time, and total overall time in microseconds.
### `Benchmark<T: Engine>` Struct
```rust
pub struct Benchmark<T: Engine>(T);
```
A wrapper around any other `Engine` that measures its rendering performance.
#### Methods:
* **`fn new(engine: T) -> Self`**: Creates a new `Benchmark` wrapper around an existing `Engine` instance.
#### `Engine<BenchmarkResult>` Trait Implementation:
The `Benchmark` engine implements the `Engine` trait, but its `run` method performs multiple render cycles (e.g., 40 times), measures the duration of each, and returns a `BenchmarkResult` instead of `()`. It delegates all other `Engine` methods to the wrapped engine.
This comprehensive set of traits and implementations allows OSUI to be flexible regarding its rendering backend and extensible with custom command handling.
**Next:** Explore the [Frontend API](./frontend-api.md).
+77
View File
@@ -0,0 +1,77 @@
---
sidebar_position: 3
title: Frontend API
---
# Frontend Module API Reference
The `frontend` module is responsible for bridging the declarative `rsx!` macro syntax to the dynamic component rendering system. It defines how component hierarchies are constructed and managed before being translated into `View`s.
## `ToRsx` Trait
```rust
pub trait ToRsx {
fn to_rsx(&self) -> Rsx;
}
```
The `ToRsx` trait is implemented by any type that can be converted into an `Rsx` object. This is crucial for embedding various types (like strings, numbers, or other `Rsx` instances) directly into the `rsx!` macro output using the `@{expr}` syntax.
**Implementations:**
* **`&Rsx`**: Converts a reference to an `Rsx` into an owned `Rsx` by cloning its internal structure.
* **`T: std::fmt::Display`**: Any type that implements `std::fmt::Display` (e.g., `String`, `&str`, `i32`, `f64`, etc.) automatically implements `ToRsx`. It converts the displayable value into a `Rsx` containing a static scope that draws the text.
## `RsxScope` Enum
```rust
#[derive(Clone)]
pub enum RsxScope {
Static(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>),
Dynamic(
Arc<dyn Fn(&Arc<Scope>) + Send + Sync>,
Vec<Arc<dyn HookDependency>>,
),
Child(Rsx),
}
```
`RsxScope` represents the different kinds of renderable units that can be part of an `Rsx` hierarchy. These scopes dictate how and when their content is processed and updated.
* **`Static(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>)`**:
* Represents content that is processed only once. This is typically used for simple text literals or components that don't depend on reactive state within their `rsx!`.
* The contained closure is executed once to set up children within a new `Scope`.
* **`Dynamic(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>, Vec<Arc<dyn HookDependency>>)`**:
* Represents content that needs to be re-evaluated and potentially re-rendered when certain `dependencies` change. This is used for `rsx!` blocks with `@if` and `@for` that declare dependencies (`%dep`).
* The closure (`drawer`) is executed initially and then whenever any of the `HookDependency` instances in `dependencies` notify an update.
* **`Child(Rsx)`**:
* Represents a nested `Rsx` structure. This is used when an `Rsx` object is embedded directly into another `rsx!` block (e.g., via `@{other_rsx}` or when passing `children` to a component).
## `Rsx` Struct
```rust
#[derive(Clone)]
pub struct Rsx(Vec<RsxScope>);
```
The `Rsx` struct is a collection of `RsxScope`s, representing a declarative UI tree fragment. It's the primary output of the `rsx!` macro.
#### Methods:
* **`fn new() -> Self`**
* Creates a new empty `Rsx` instance.
* **`fn static_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(&mut self, scope: F)`**
* Adds a `Static` `RsxScope` to the collection. The `scope` closure will be executed once to build up the content within a dedicated `Scope`.
* **`fn dynamic_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(&mut self, drawer: F, dependencies: Vec<Arc<dyn HookDependency>>)`**
* Adds a `Dynamic` `RsxScope` to the collection. The `drawer` closure will be executed initially and then on subsequent updates of the specified `dependencies`.
* **`fn child<R: ToRsx>(&mut self, child: R)`**
* Adds a `Child` `RsxScope` to the collection, converting the input `R` (which must implement `ToRsx`) into a nested `Rsx`.
* **`fn generate_children(&self, context: &Arc<Context>)`**
* Processes the internal `Vec<RsxScope>`, converting each scope into actual component `Context`s and `Scope`s within the provided parent `context`. This method recursively builds the component tree.
* **`fn view(&self, context: &Arc<Context>) -> View`**
* The primary method used by components to turn their `rsx!` output into a renderable `View`.
* It first calls `generate_children` to build the component tree within the given `context`.
* It then returns a `View` closure that, when executed, will instruct the `context` to `draw_children`.
The `frontend` module, through `Rsx` and `RsxScope`, provides the declarative interface and the necessary translation layer to OSUI's imperative rendering core.
**Next:** Explore the [Render API](./render-api.md).
+211
View File
@@ -0,0 +1,211 @@
---
sidebar_position: 6
title: Macros API
---
# Macros Module API Reference
The `osui-macros` crate provides the procedural macros that enhance OSUI's ergonomics and enable its declarative UI syntax. These macros transform your Rust code into the necessary OSUI component and rendering structures.
## `#[component]` Attribute Macro
```rust
#[proc_macro_attribute]
pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream { /* ... */ }
```
The `#[component]` attribute macro transforms a standard Rust function into a fully-fledged OSUI component.
#### Purpose:
* **Prop Generation**: It automatically parses the function's parameters (after the initial `cx: &Arc<Context>`) and generates a `struct` with matching fields. These fields become the component's "props".
* **`ComponentImpl` Implementation**: It implements the `ComponentImpl` trait for the generated struct, making it a valid OSUI component that can be rendered. The `call` method of this trait simply invokes your original function.
* **Ergonomics**: Simplifies component definition by allowing you to write components as regular functions with clear parameters, without manually defining structs and `ComponentImpl` boilerplate.
#### Usage:
```rust
use osui::prelude::*; // For Context and View types
use std::sync::Arc;
#[component]
pub fn MyComponent(cx: &Arc<Context>, message: &str, count: &usize) -> View {
// Your component logic, accessing `message` and `count` directly
rsx! {
format!("Message: {}, Count: {}", message, count)
}.view(&cx)
}
// How `MyComponent` would be used in RSX:
// rsx! {
// MyComponent { message: "Hello", count: 123 }
// }
```
#### Generated Code (Simplified):
```rust
pub struct MyComponent {
pub message: String, // Note: `&str` becomes `String`
pub count: usize, // Note: `&usize` becomes `usize`
}
impl MyComponent {
pub fn component(
cx: &Arc<Context>,
message: &str, // Original function signature as `component` method
count: &usize,
) -> View {
// Original body of the function
rsx! {
format!("Message: {}, Count: {}", message, count)
}.view(&cx)
}
}
impl ComponentImpl for MyComponent {
fn call(&self, cx: &Arc<Context>) -> View {
Self::component(
cx,
&self.message, // Passes stored props as references
&self.count,
)
}
}
```
#### Requirements:
* The function must take `cx: &Arc<Context>` as its first parameter.
* The function must return `View`.
* Prop parameters are typically references (e.g., `&str`, `&i32`). The macro automatically converts them to their owned types (e.g., `String`, `i32`) in the generated struct.
## `rsx!` Procedural Macro
```rust
#[proc_macro]
pub fn rsx(input: TokenStream) -> TokenStream { /* ... */ }
```
The `rsx!` macro provides a declarative, React-like syntax for building UI component hierarchies directly in your Rust code. It parses the input and transforms it into calls to the `osui::frontend::Rsx` builder methods.
#### Purpose:
* **Declarative UI**: Allows you to describe *what* your UI should look like, rather than imperatively writing drawing commands.
* **Component Composition**: Enables easy nesting and passing of props/children to other components.
* **Reactive Flow**: Integrates with OSUI's state management to define dynamic UI segments.
#### Syntax Overview:
The `rsx!` macro supports several types of nodes:
1. **Text Literals**:
```rust
rsx! {
"Hello World"
"Another line of text"
}
// Generates:
// r.static_scope(move |scope| {
// scope.view(Arc::new(move |ctx| {
// ctx.draw_text(Point { x: 0, y: 0 }, &format!("Hello World"))
// }));
// // ... for another line
// });
```
2. **Rust Expressions (`@{expr}`)**:
```rust
let name = "Alice";
rsx! {
@{format!("Hello, {}!", name)}
@{1 + 2} // Any `Display` impl
}
// Generates:
// r.child(format!("Hello, {}!", name));
// r.child(1 + 2);
```
* The `expr` must evaluate to a type that implements `osui::frontend::ToRsx`.
3. **Component Instantiation (`Component { prop: value, ... children }`)**:
```rust
#[component] fn MyDiv(cx: &Arc<Context>, content: &str) -> View { /* ... */ }
rsx! {
MyDiv {
content: "Some text", // Prop
rsx! { "Child content" } // Children (if `children: &Rsx` is a prop)
}
}
// Generates:
// r.static_scope(move |scope| {
// scope.child(
// MyDiv {
// content: "Some text".to_string(), // Owned type for struct field
// children: osui::frontend::Rsx(/* ... */)
// },
// None
// );
// });
```
* `path`: The path to the component struct (e.g., `MyDiv`, `my_module::MyComponent`).
* `props`: `key: value` pairs for component properties.
* `children`: Any `rsx!` content directly inside the braces after props. This is collected into the special `children: &Rsx` prop if the component defines it.
4. **Conditional Rendering (`@if condition { ... } [else { ... }]`)**:
```rust
let show = true;
rsx! {
%show @if show {
"Content shown if 'show' is true"
} else {
"Content shown if 'show' is false"
}
}
// Generates:
// r.dynamic_scope(move |scope| {
// if show {
// // ... rsx for true branch
// } else {
// // ... rsx for false branch
// }
// }, vec![Arc::new(show) as Arc<dyn HookDependency>]);
```
* `%dep1, dep2`: Optional dependency list. The `if` block will re-evaluate when any of these dependencies (which must implement `HookDependency`, like `State<T>`) change.
* `condition`: A Rust expression evaluating to `bool`.
* `{ ... }`: An `rsx!` fragment rendered if `condition` is true.
* `else { ... }`: Optional `rsx!` fragment rendered if `condition` is false.
5. **Loop Rendering (`@for pattern in expr { ... }`)**:
```rust
let items = vec!["A", "B", "C"];
rsx! {
%items @for item in items {
format!("Item: {}", item)
}
}
// Generates:
// r.dynamic_scope(move |scope| {
// for item in items {
// // ... rsx for each item
// }
// }, vec![Arc::new(items) as Arc<dyn HookDependency>]);
```
* `%dep1, dep2`: Optional dependency list. The `for` loop will re-evaluate when any of these dependencies change.
* `pattern`: A standard Rust `for` loop pattern (e.g., `item`, `(idx, item)`).
* `expr`: A Rust expression evaluating to an `IntoIterator`.
* `{ ... }`: An `rsx!` fragment rendered for each iteration.
6. **Mount Hook (`!mount_hook_instance`)**:
```rust
let my_manual_mount = use_mount_manual();
rsx! {
!my_manual_mount
}
// Generates:
// my_manual_mount.mount();
```
* Calls the `.mount()` method on the provided `Mount` instance. This is typically used with `use_mount_manual` to trigger effects at a specific point in the render tree.
The `rsx!` macro is a powerful tool for declarative UI construction, abstracting away the underlying `Rsx` object manipulation and `Scope` creation logic.
**Next:** Explore the detailed [Render API](./render-api.md).
+99
View File
@@ -0,0 +1,99 @@
---
sidebar_position: 4
title: Render API
---
# Render Module API Reference
The `render` module provides the foundational primitives for drawing content to the terminal. It defines basic geometric types and the `DrawContext` for accumulating drawing instructions, abstracting away the specifics of the underlying terminal backend.
## Geometric Primitives
These structs define positions and dimensions within the terminal grid.
### `Point` Struct
```rust
#[derive(Clone)]
pub struct Point {
pub x: u16, // X coordinate (column)
pub y: u16, // Y coordinate (row)
}
```
Represents a specific coordinate in a 2D grid.
### `Size` Struct
```rust
#[derive(Clone)]
pub struct Size {
pub width: u16, // Width in terminal columns
pub pub height: u16, // Height in terminal rows
}
```
Represents the dimensions of a rectangular area.
### `Area` Struct
```rust
#[derive(Clone)]
pub struct Area {
pub x: u16, // X coordinate (column) of the top-left corner
pub y: u16, // Y coordinate (row) of the top-left corner
pub width: u16, // Width in terminal columns
pub height: u16, // Height in terminal rows
}
```
Represents a rectangular region defined by its top-left corner (`x`, `y`) and its `width` and `height`.
## `DrawInstruction` Enum
```rust
#[derive(Clone)]
pub enum DrawInstruction {
Text(Point, String),
View(Area, View),
Child(Point, DrawContext), // For embedding child DrawContexts at an offset
}
```
`DrawInstruction` enumerates the different types of atomic drawing operations that the rendering engine can perform.
* **`Text(Point, String)`**: Instructs the engine to draw a given `String` at a specific `Point`.
* **`View(Area, View)`**: Instructs the engine to render a nested `View` within a specified `Area`. This is how child components are rendered.
* **`Child(Point, DrawContext)`**: Instructs the engine to render a child `DrawContext` at a given offset `Point`. This is typically used internally when drawing recursively.
## `DrawContext` Struct
```rust
#[derive(Clone)]
pub struct DrawContext {
pub area: Area, // The total area available for drawing to this context
pub allocated: Area, // The union of all allocated sub-areas within this context
pub drawing: Vec<DrawInstruction>, // Accumulated drawing instructions
}
```
The `DrawContext` is the primary interface for components to issue drawing commands. Each component's `View` receives a `DrawContext` that represents its allocated drawing space. It accumulates `DrawInstruction`s which are then processed by the `Engine`.
#### Methods:
* **`fn new(area: Area) -> Self`**
* Creates a new `DrawContext` with the specified `area` as its total available space. Initializes `allocated` to an "empty" area (max `u16` for x/y, 0 for width/height).
* **`fn allocate(&mut self, x: u16, y: u16, width: u16, height: u16) -> Area`**
* Marks a sub-region within the `DrawContext`'s `area` as "allocated".
* Updates the `self.allocated` field to grow to encompass this new allocation.
* Returns the `Area` representing the newly allocated space. Coordinates (`x`, `y`) are relative to `self.area`'s top-left corner.
* **`fn draw(&mut self, inst: DrawInstruction)`**
* Adds a raw `DrawInstruction` to the `drawing` vector.
* **`fn draw_text(&mut self, point: Point, text: &str)`**
* A convenience method to add a `DrawInstruction::Text` to the context. `point` is relative to `self.area`.
* **`fn draw_view(&mut self, area: Area, view: View)`**
* A convenience method to add a `DrawInstruction::View` to the context. `area` is relative to `self.area`.
This module lays the groundwork for all visual output in OSUI, providing the necessary abstractions for components to describe what they want to render without knowing the specifics of the terminal backend.
**Next:** Explore the [State API](./state-api.md).
+165
View File
@@ -0,0 +1,165 @@
---
sidebar_position: 5
title: State API
---
# State Module API Reference
The `state` module provides OSUI's powerful, React-like hook system for managing component state and side effects. These hooks enable reactivity, allowing your UI to automatically update in response to data changes.
## `State<T>` Struct
```rust
#[derive(Debug)]
pub struct State<T> {
value: Arc<Mutex<T>>,
dependents: Arc<Mutex<Vec<HookEffect>>>,
}
```
`State<T>` is the primary type for holding reactive, component-local state. It wraps a value `T` in an `Arc<Mutex<T>>` for thread-safe access and includes a list of `HookEffect`s that should be triggered when its value changes.
#### Methods:
* **`fn get(&self) -> Inner<'_, T>`**
* Acquires a lock on the internal `Mutex` and returns an `Inner<'_, T>` guard. This guard provides mutable (`DerefMut`) access to the state value. When the `Inner` guard is dropped, if the value was mutated, all `dependents` (registered `HookEffect`s) are notified.
* **`fn get_dl(&self) -> T`**
* "Deadlock-less" getter. Acquires a lock, clones the internal value `T`, releases the lock, and returns the cloned value. Useful for reading the state when cloning `T` is cheap and you don't need mutable access, preventing potential deadlocks from holding a `MutexGuard` across `await` points or other blocking operations. Requires `T: Clone`.
* **`fn set(&self, v: T)`**
* Replaces the current state value with `v` and then explicitly notifies all `dependents`.
* **`fn update(&self)`**
* Manually triggers all registered `dependents`. Useful if you've modified the internal value without using `get()` (e.g., via `Arc::get_mut` if the `Arc` is uniquely owned, which is rare for `State<T>`).
* **`fn clone(&self) -> Self`**
* Clones the `State<T>` handle (not the internal value). This creates a new `Arc` reference to the same underlying `value` and `dependents`. Essential for moving `State<T>` into closures or passing to child components without moving the actual state.
#### `Display` Implementation:
If `T` implements `std::fmt::Display`, `State<T>` also implements `std::fmt::Display`, allowing it to be directly formatted (e.g., in `format!`) by implicitly calling `get_dl()`.
### `Inner<'a, T>` Struct
```rust
pub struct Inner<'a, T> {
value: MutexGuard<'a, T>,
dependents: Arc<Mutex<Vec<HookEffect>>>,
updated: bool,
}
```
A guard type returned by `State<T>::get()`. It provides scoped, mutable access to the internal state value.
#### `Deref` and `DerefMut` Implementations:
* Allows `Inner<'a, T>` to be treated as a `&T` or `&mut T`, giving direct access to the underlying state.
* The `DerefMut` implementation sets an internal `updated` flag.
#### `Drop` Implementation:
* When `Inner<'a, T>` is dropped, if the `updated` flag is `true`, it automatically iterates through `dependents` and calls their `call()` method, ensuring reactivity.
## `HookEffect` Struct
```rust
#[derive(Clone)]
pub struct HookEffect(Arc<Mutex<dyn FnMut() + Send + Sync>>);
```
A wrapper around a mutex-protected closure that represents a side effect. These are registered as dependents of `State<T>` or `Mount` and are triggered when dependencies change.
#### Methods:
* **`fn new<F: Fn() + Send + Sync + 'static>(f: F) -> Self`**
* Creates a new `HookEffect` from a given closure.
* **`fn call(&self)`**
* Executes the wrapped closure by acquiring its mutex.
## `HookDependency` Trait
```rust
pub trait HookDependency: Send + Sync {
fn on_update(&self, hook: HookEffect);
}
```
The `HookDependency` trait defines how an object can register an effect (`HookEffect`) to be triggered when it updates.
**Implementations:**
* **`State<T>`**: Registers the `HookEffect` to be called when `State<T>`'s value changes (via `set()`, `get()` and subsequent drop, or `update()`).
* **`Mount`**: Registers the `HookEffect` to be called when `mount()` is invoked, or immediately if already mounted.
## `Mount` Struct
```rust
#[derive(Debug, Clone)]
pub struct Mount(Arc<Mutex<bool>>, Arc<Mutex<Vec<HookEffect>>>);
```
A specialized hook for managing component lifecycle (specifically, the "mounted" state). It tracks whether a component has been mounted and queues `HookEffect`s to be run upon mounting.
#### Methods:
* **`fn mount(&self)`**
* Sets the internal flag to `true`, indicating the component is now mounted.
* Executes all currently queued `HookEffect`s and clears the queue.
* Any `HookEffect` registered after `mount()` has been called will execute immediately.
## Hooks Functions
These functions are the primary way to interact with the state management system within your components.
### `fn use_state<T>(v: T) -> State<T>`
* **Purpose**: Creates and initializes a new `State<T>` instance.
* **Usage**: `let count = use_state(0);`
### `fn use_effect<F: FnMut() + Send + Sync + 'static>(f: F, dependencies: &[&dyn HookDependency])`
* **Purpose**: Registers a side effect `f` that will run when any of the `dependencies` change, and also once initially. The effect closure is run in a `std::thread::spawn`.
* **Usage**:
```rust
let counter = use_state(0);
use_effect(
{ let counter = counter.clone(); move || println!("Counter changed: {}", counter.get_dl()) },
&[&counter] // Dependencies
);
```
* **Empty Dependencies**: If `dependencies` is `&[]`, the effect runs once on initial render and never again.
### `fn use_mount() -> Mount`
* **Purpose**: Creates a `Mount` instance that is immediately marked as "mounted". Effects registered with this `Mount` (via `use_effect`) will run once, immediately.
* **Usage**: `let mount_hook = use_mount();`
### `fn use_mount_manual() -> Mount`
* **Purpose**: Creates a `Mount` instance that starts in an "unmounted" state. Effects registered with this `Mount` will *only* run when its `.mount()` method is explicitly called (either programmatically or via `!mount_hook_instance` in `rsx!`).
* **Usage**: `let manual_mount_hook = use_mount_manual();`
### `fn use_sync_state<T, E, D>(cx: &Arc<Context>, v: T, decoder: D) -> State<T>`
* **Purpose**: Creates a `State<T>` that automatically updates its value whenever an event of type `E` is emitted to the given `Context`. The `decoder` function converts `&E` into a new `T`.
* **Usage**:
```rust
// Assume MessageChangeEvent is defined
let message_state = use_sync_state(
cx,
"Default message".to_string(),
|event: &MessageChangeEvent| event.0.clone()
);
```
### `fn use_sync_effect<T, Ev, E>(cx: &Arc<Context>, state: &State<T>, encoder: E, deps: &[&dyn HookDependency])`
* **Purpose**: Registers an effect that emits an event `Ev` to the given `Context` whenever the monitored `state` changes (or any other specified `deps`). The `encoder` function converts `&State<T>` into an `Ev`.
* **Usage**:
```rust
let count_state = use_state(0);
// Assume CounterUpdatedEvent is defined
use_sync_effect(
cx,
&count_state,
|s: &State<i32>| CounterUpdatedEvent { new_value: s.get_dl() },
&[&count_state]
);
```
These hooks provide a complete and reactive state management solution, enabling dynamic and interactive TUI applications in OSUI.
**Next:** Delve into the details of the [Macros API](./macros-api.md).
+94
View File
@@ -0,0 +1,94 @@
---
sidebar_position: 0
title: Core Architecture
---
# Core Architecture
OSUI is designed with a clear separation of concerns, drawing inspiration from modern GUI frameworks like React. This modular architecture aims for flexibility, testability, and scalability. Here's a high-level overview of how the different parts of OSUI interact to form a functional TUI application.
```mermaid
graph TD
A[Application Root (main.rs)] --> B(Engine::run(RootComponent))
B --> C(Engine)
C -- init --> D(Root Component Context)
D -- refresh --> E(Root Component View)
E -- generate_children --> F(Frontend::Rsx)
F -- create Scopes & Contexts --> D
D -- draw_children --> G(Render::DrawContext)
G -- execute DrawInstructions --> H(Engine::draw_context)
H -- actual terminal output --> I(crossterm)
subgraph State & Events
J[Component Context] -- manage state via hooks --> K(State<T>)
K -- notify dependents --> L(HookEffect)
L -- trigger callbacks --> J
J -- emit events --> M(Event Handlers)
M -- propagate to children --> J
end
subgraph User Interaction
N[Input Polling Thread] --> O(Engine::CommandExecutor)
O -- execute commands --> C
O -- emit events --> J
end
C -- continuous loop --> E
D --> J
J --> E
```
## Key Architectural Components
### 1. **Component System (`osui::component`)**
* **`ComponentImpl`**: The fundamental trait defining a renderable UI unit. Any type implementing this can be an OSUI component.
* **`Context`**: Each active component instance has its own `Context`. This is the central hub for:
* Holding the component's `View` (its rendered output).
* Managing its local state using hooks (e.g., `use_state`).
* Registering and emitting events (`on_event`, `emit_event`).
* Managing its children components and their `Scope`s.
* Accessing the `CommandExecutor` to interact with the engine.
* **`Scope`**: A `Context` can contain multiple child `Scope`s. A `Scope` is primarily a container that groups a set of child components (`Context`s) and their optional `ViewWrapper`s. `Rsx` fragments generate `Scope`s to manage their children.
**Why it works this way**: This component-based approach promotes modularity and reusability. `Context` provides a stable identity and state for each component instance, enabling independent updates and event handling. The tree structure formed by `Context`s and `Scope`s mirrors the UI hierarchy.
### 2. **State Management (`osui::state`)**
* **`State<T>`**: A reactive wrapper for any data `T`. When `State<T>` is updated, it automatically notifies all its registered "dependents".
* **`use_state`**: The primary hook to create and manage `State<T>` within a component.
* **`use_effect`**: A hook for performing side effects (e.g., logging, network requests) in response to `State<T>` changes or component mounting.
* **`HookDependency`**: A trait that `State<T>` and `Mount` implement, allowing them to be tracked by `use_effect` and dynamic `rsx!` blocks.
**Why it works this way**: Inspired by React hooks, this system provides a predictable and efficient way to manage mutable state. By declaring explicit dependencies for effects and dynamic `rsx!` blocks, OSUI can minimize re-renders and computations, only updating parts of the UI that are truly affected by state changes.
### 3. **Frontend / Declarative UI (`osui::frontend` & `osui-macros`)**
* **`rsx!` macro**: A procedural macro that allows you to write UI using a declarative, XML-like syntax directly in Rust.
* **`#[component]` macro**: A procedural macro that transforms a Rust function into an OSUI component, automatically handling prop parsing and `ComponentImpl` implementation.
* **`Rsx`**: An internal representation (produced by `rsx!`) of a UI fragment, consisting of a vector of `RsxScope`s.
* **`RsxScope`**: An enum defining different types of UI nodes (static text, components, dynamic conditional/loop blocks).
**Why it works this way**: Declarative UI is generally easier to reason about than imperative drawing commands. The `rsx!` macro provides a high-level abstraction that maps directly to the component tree and state management, significantly improving developer experience. The macro-generated `Rsx` object then serves as a blueprint for `Context` to build its children.
### 4. **Rendering Pipeline (`osui::render`)**
* **`View`**: The ultimate output of a component's rendering logic. It's a closure that, when called, populates a `DrawContext` with drawing instructions.
* **`DrawContext`**: A mutable accumulator for `DrawInstruction`s. Components add text, child views, or custom drawing commands to this context. It also tracks the available `Area` and `allocated` space.
* **`DrawInstruction`**: An enum representing atomic drawing operations (e.g., `Text`, `View`, `Child`).
* **Geometric Primitives**: `Point`, `Size`, `Area` define positions and dimensions.
**Why it works this way**: This separation allows the rendering logic to be independent of the actual display medium. Components declare *what* to draw using high-level instructions, and the `Engine` then decides *how* to execute them on the specific backend (e.g., terminal).
### 5. **Engine (`osui::engine`)**
* **`Engine` trait**: Defines the interface for running an OSUI application, including initialization, continuous rendering, and managing the render loop.
* **`Console`**: The default implementation of `Engine`, which uses `crossterm` to interact with the terminal.
* **`CommandExecutor` trait**: An interface for executing system-level commands (e.g., `Stop`).
* **`Benchmark`**: A wrapper `Engine` that measures and reports performance statistics.
**Why it works this way**: The `Engine` trait makes OSUI extensible. You can swap out the `Console` engine for a different backend (e.g., a web renderer, a headless testing engine) without changing your core component logic. The `CommandExecutor` provides a standardized way for components to request actions from the environment.
This interconnected architecture allows OSUI to offer a powerful, flexible, and developer-friendly experience for building sophisticated Terminal User Interfaces.
**Next:** Delve deeper into [The Component Model](./01-the-component-model.md).
+107
View File
@@ -0,0 +1,107 @@
---
sidebar_position: 1
title: The Component Model
---
# The Component Model
At the core of OSUI's design is a robust component model, defining how UI elements are created, composed, and managed. This model provides structure, promotes reusability, and facilitates a clear separation of concerns within your TUI application.
## `ComponentImpl`: The Building Block
The `ComponentImpl` trait is the most fundamental concept for any renderable unit in OSUI:
```rust
pub trait ComponentImpl: Send + Sync {
/// Renders the component within the given context, returning a View
fn call(&self, cx: &Arc<Context>) -> View;
}
```
* **`fn call(&self, cx: &Arc<Context>) -> View`**: This method is where a component's rendering logic resides. It takes a reference to the component instance itself and its `Context` (`cx`), and it must return a `View`. The `View` is OSUI's abstraction for "what to draw," essentially a closure that will populate a `DrawContext` with drawing instructions later in the rendering pipeline.
* **`Send + Sync`**: Components must be `Send` and `Sync` to ensure they can be safely passed between threads, as OSUI leverages concurrency for various operations (e.g., `use_effect` hooks).
Most often, you won't implement `ComponentImpl` manually. Instead, you'll use the `#[component]` procedural macro:
```rust
#[component]
fn MyComponent(cx: &Arc<Context>, some_prop: &String) -> View {
// Component logic and RSX here
rsx! {
format!("Prop: {}", some_prop)
}.view(&cx)
}
```
The `#[component]` macro automatically generates a struct for `MyComponent` (with `some_prop: String` as a field) and implements `ComponentImpl` for it, delegating the `call` method to your function's body.
## `Context`: The Component's Identity and State
Every active instance of a component in the UI tree has its own `Context` (`osui::component::context::Context`). The `Context` is the component's runtime identity and central hub for managing its internal state and interactions:
```rust
pub struct Context {
component: AccessCell<Component>,
view: AccessCell<View>,
event_handlers: AccessCell<HashMap<TypeId, Vec<EventHandler>>>,
pub(crate) scopes: Mutex<Vec<Arc<Scope>>>,
executor: Arc<dyn CommandExecutor>,
}
```
**Why `Context` is crucial**:
* **State Management**: It's the entry point for all state hooks (`use_state`, `use_effect`, etc.), ensuring that each component instance manages its own isolated, reactive state.
* **Event Handling**: `Context` provides methods (`on_event`, `emit_event`) for handling component-specific and application-wide events. Events propagate through the `Context` tree.
* **Rendering Result (`View`)**: It holds the `View` generated by the `ComponentImpl::call` method, which is later passed to the rendering engine.
* **Child Management (`scopes`)**: A `Context` aggregates child components through `Scope`s, forming the hierarchical UI tree.
* **Engine Interaction**: It provides access to the `CommandExecutor`, allowing components to send commands (like `Stop`) to the underlying engine.
When a component is instantiated (e.g., `MyComponent { ... }` in `rsx!`), a new `Context` is created for it. This `Context` lives as long as the component is part of the active UI tree.
## `Scope`: Organizing Children
While `Context` represents a single component instance, `Scope` (`osui::component::scope::Scope`) is responsible for grouping and managing collections of child `Context`s:
```rust
pub struct Scope {
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
executor: Arc<dyn CommandExecutor>,
}
```
**Relationship between `Context` and `Scope`**:
* A `Context` can contain multiple child `Scope`s (stored in `Context::scopes`).
* Each `Scope` then contains a `Vec` of `(Arc<Context>, Option<ViewWrapper>)`, representing the actual child components (and optional view modifiers) within that specific scope.
* This distinction allows for flexibility in how children are managed, particularly for dynamic `rsx!` constructs like `@if` and `@for` which might create or destroy entire `Scope`s based on conditions or iterations.
**How `Scope`s are used**:
* When you use `rsx!`, the macro emits calls to `context.scope()` or `context.dyn_scope()`, which create new `Scope`s.
* Within these `Scope`s, children are added using `scope.child()` or `scope.view()`.
* The `Context::draw_children` method then iterates through these `Scope`s and their children to orchestrate their rendering.
## The Component Tree
Together, `ComponentImpl`, `Context`, and `Scope` form a hierarchical component tree:
```
App Component (Context)
└── App Scope (from App's rsx!)
├── Child Component A (Context)
│ └── Child A Scope (from A's rsx!)
│ └── Grandchild Component X (Context)
├── Child Component B (Context)
│ └── Child B Scope
│ ├── Grandchild Component Y (Context)
│ └── Grandchild Component Z (Context)
└── Dynamic Scope (e.g., from an `@if` block)
└── (Conditionally rendered children)
```
This tree structure is fundamental to OSUI's rendering and event propagation. Events emitted by a child `Context` can traverse up the tree to parent `Context`s (if they listen for them) and always propagate down to all descendants.
The component model provides a clear, organized, and powerful way to structure your TUI applications, promoting maintainability and scalability through modularity and a well-defined lifecycle for each UI element.
**Next:** Understand how `State<T>` and `HookDependency` enable efficient UI updates in [Reactive State Flow](./02-reactive-state-flow.md).
+114
View File
@@ -0,0 +1,114 @@
---
sidebar_position: 2
title: Reactive State Flow
---
# Reactive State Flow
OSUI's reactivity model is designed to efficiently update the UI in response to changes in application data. It centers around `State<T>`, `HookDependency`, and `use_effect`, forming a flow where data changes automatically trigger re-rendering of affected components.
## 1. `State<T>`: The Source of Truth
At the heart of reactivity is the `State<T>` type. When you declare state using `use_state(initial_value)`, you get a `State<T>` instance:
```rust
let count = use_state(0); // count: State<i32>
```
`State<T>` wraps your actual data `T` in an `Arc<Mutex<T>>`, allowing it to be safely shared and mutated across multiple threads and component scopes.
## 2. Updating `State<T>`
When the value held by `State<T>` changes, this initiates the reactive flow. There are two primary ways to update `State<T>`:
* **`state.set(new_value)`**: Replaces the entire value and explicitly triggers an update.
* **`*state.get() = new_value`**: Acquires an `Inner<'_, T>` guard, which provides mutable access to the underlying value. When this `Inner` guard is dropped (goes out of scope), it automatically checks if the value was modified and, if so, triggers an update.
```rust
// Method 1: using .set()
count.set(count.get_dl() + 1);
// Method 2: using .get() for mutable access
{
let mut count_guard = count.get(); // Acquire Inner guard
*count_guard += 1; // Mutate the value
} // count_guard drops here, automatically triggering updates
```
## 3. `HookDependency`: Declaring Reactivity
For an update to `State<T>` to have an effect, there must be something "listening" for that update. This is where the `HookDependency` trait comes in:
```rust
pub trait HookDependency: Send + Sync {
fn on_update(&self, hook: HookEffect);
}
```
* `State<T>` implements `HookDependency`. This means you can register an `HookEffect` with a `State<T>` instance, and `State<T>` will ensure that effect is called whenever its value changes.
* `Mount` also implements `HookDependency` for managing component lifecycle effects.
## 4. `HookEffect`: The Callback
An `HookEffect` is essentially a wrapper around a closure (`Arc<Mutex<dyn FnMut() + Send + Sync>>`) that represents a side effect. When a `HookDependency` updates, it calls all registered `HookEffect`s.
```rust
// An effect might look something like this internally:
let my_effect = HookEffect::new(move || {
// This code runs when the dependency updates
println!("Dependency changed!");
});
```
## 5. `use_effect` and Dynamic `rsx!` Blocks: Consuming Reactivity
The `HookDependency` and `HookEffect` mechanism is consumed by two main features to enable reactive UI updates:
### a) `use_effect` Hook
`use_effect` allows you to run side effects when specified dependencies change:
```rust
use_effect(
{
let count = count.clone(); // Clone State handle for closure
move || {
// This closure runs in a spawned thread when `count` updates
println!("Count is now: {}", count.get_dl());
}
},
&[&count], // `count` is the dependency
);
```
When `use_effect` is first called, it registers its internal `HookEffect` closure with each `HookDependency` in the provided slice. Each time `count` is updated, `count` calls its registered `HookEffect`, which then executes the provided closure.
### b) Dynamic `rsx!` Blocks (`%dep @if ...`, `%dep @for ...`)
OSUI's `rsx!` macro supports special syntax for dynamic UI segments that automatically re-render when dependencies change:
```rust
rsx! {
%count @if *count.get() > 0 { // This block re-renders if `count` changes
format!("Count is positive: {}", count.get_dl())
}
%items @for item in items.get_dl() { // This block re-renders if `items` changes
format!("- {}", item)
}
}
```
* When the `rsx!` macro encounters `%dep`, it also registers a special internal `HookEffect` with that dependency.
* This `HookEffect` is responsible for re-evaluating the entire `dynamic_scope` (the `if` or `for` block) within the component's `Context`. This re-evaluation re-runs the `rsx!` logic for that block, generating potentially new children or text nodes, and thus updating the UI.
## The Reactive Flow in Summary
1. A component uses `use_state` to create a `State<T>`.
2. The `State<T>` is passed as a dependency to `use_effect` or declared in a dynamic `rsx!` block (`%state`).
3. When `State<T>`'s value is modified (`set()` or `get()` then drop), it triggers its registered `HookEffect`s.
4. These `HookEffect`s then either execute a side-effect closure (from `use_effect`) or trigger a re-evaluation of the corresponding `dynamic_scope` (from `rsx!`).
5. Re-evaluation of `dynamic_scope` leads to updated `DrawInstruction`s, which the `Engine` eventually renders to the terminal.
This elegant system ensures that your UI remains synchronized with your application's data, responding efficiently and predictably to changes, minimizing manual re-rendering logic.
**Next:** Understand how events traverse the component tree in [Event Propagation](./03-event-propagation.md).
+104
View File
@@ -0,0 +1,104 @@
---
sidebar_position: 3
title: Event Propagation
---
# Event Propagation
Event propagation in OSUI describes how events traverse the component tree after they are emitted. Understanding this model is crucial for designing effective inter-component communication and responsive user interfaces.
## Unidirectional Propagation: Downwards
OSUI employs a unidirectional event propagation model, primarily **downwards** through the component tree. When an event is emitted from a component's `Context`, it follows this path:
1. **Current Context**: All `on_event` handlers registered on the `Context` that emitted the event are invoked first.
2. **Child Contexts**: The event is then recursively propagated to all immediate children's `Context`s, and from there, further down to their children, and so on, until it reaches the leaves of the component tree. Each child `Context` will also invoke its own registered `on_event` handlers for that event type.
This means an event emitted by an ancestor component will reach all its descendants. An event emitted by a child component will reach its parents (if the parent `Context` has `on_event` handlers for it) and all its siblings and their descendants.
```mermaid
graph TD
A[Root Component Context] --> B(Child A Context)
A --> C(Child B Context)
B --> D(Grandchild A1 Context)
B --> E(Grandchild A2 Context)
C --> F(Grandchild B1 Context)
subgraph Event Propagation (emit_event from D)
D -- handlers on D --> D
D -- propagate --> B
B -- handlers on B --> B
B -- propagate --> E
E -- handlers on E --> E
B -- propagate --> A
A -- handlers on A --> A
A -- propagate --> C
C -- handlers on C --> C
C -- propagate --> F
F -- handlers on F --> F
end
```
### Methods for Event Emission
* **`cx.emit_event(event: E)`**:
* This is the standard method for emitting events.
* It processes event handlers synchronously in the current thread. This means that subsequent code execution will wait for all handlers (and their propagation to children) to complete.
* Useful for events where the order of execution matters or where the handler logic is quick.
* **`cx.emit_event_threaded(event: &E)`**:
* This method processes event handlers asynchronously by spawning a new `std::thread` for *each* registered handler.
* The event object `E` must implement `Clone` because each handler receives its own cloned copy.
* Useful for events that might trigger long-running or blocking operations, preventing them from freezing the UI. The event propagation down the tree also uses the threaded approach.
## Practical Implications
### Parent-to-Child Communication (Implicit)
If a parent component emits an event, all its child components (and their children) that have `on_event` handlers for that specific event type will receive it. This is a powerful way for ancestors to broadcast information or commands to their descendants.
```rust
// Parent emits a "Refresh" event
cx.emit_event(RefreshEvent {});
// Child listens for "Refresh" event
cx.on_event(|_cx, _event: &RefreshEvent| {
// Perform refresh logic
});
```
### Child-to-Parent/Sibling Communication (Explicit)
A child component can effectively communicate with its parent or siblings by emitting an event. Because events propagate downwards from the emitting `Context` *and then* to all its children (and subsequently to its parent's other children, if any), the parent and siblings will receive the event if they are listening for it.
```rust
// Child component
#[component]
fn Child(cx: &Arc<Context>) -> View {
// ... logic to decide when to emit
cx.emit_event(ChildActionCompleted { data: "success".to_string() });
// ...
}
// Parent component
#[component]
fn Parent(cx: &Arc<Context>) -> View {
cx.on_event(|_cx, event: &ChildActionCompleted| {
println!("Parent received action from child: {:?}", event.data);
});
rsx! { Child {} }.view(&cx)
}
```
### Decoupling Components
Event handling promotes a decoupled architecture. Components don't need direct references to each other to communicate; they only need to agree on common event types. This makes components more independent and easier to reuse.
## When to use `emit_event` vs. `emit_event_threaded`
* **`emit_event`**: Use for most general-purpose events where handlers are quick, or you need strict sequential processing, or if you don't want the overhead of spawning many threads.
* **`emit_event_threaded`**: Use for events that might trigger expensive, long-running, or I/O-bound operations in their handlers. This prevents the main rendering loop from blocking, ensuring a responsive UI. Be mindful of potential race conditions if multiple threads modify shared state (though `State<T>`'s `Mutex` helps mitigate this).
Understanding OSUI's downward event propagation is key to designing robust and reactive component interactions within your TUI applications.
**Next:** Get insights into how your UI transforms from `View`s to terminal output in [The Rendering Pipeline](./04-rendering-pipeline.md).
+75
View File
@@ -0,0 +1,75 @@
---
sidebar_position: 4
title: The Rendering Pipeline
---
# The Rendering Pipeline
The OSUI rendering pipeline is the process by which your declarative component hierarchy is transformed into concrete drawing operations on the terminal. It's an abstraction layer that allows components to describe *what* to draw, while the `Engine` handles *how* to draw it.
## Stages of the Pipeline
The pipeline can be broken down into several distinct stages:
### 1. Component `call` and `View` Generation
* **`Engine::run`**: The main application loop starts by calling `Engine::run` with your root component.
* **`Context::refresh`**: The engine initializes the root component's `Context` and calls `Context::refresh`.
* **`ComponentImpl::call`**: Inside `refresh`, the component's `ComponentImpl::call` method is invoked. This is where your component function (decorated with `#[component]`) executes.
* **`rsx!` Macro Expansion**: Within your component function, the `rsx!` macro generates an `osui::frontend::Rsx` object.
* **`Rsx::view(&cx)`**: This method converts the `Rsx` object into a `View`. Critically, `Rsx::view` also triggers `Rsx::generate_children`.
* **`Rsx::generate_children`**: This recursively processes the `Rsx` object, creating new child `Context`s and `Scope`s within the current `Context`. For `dynamic_scope`s (`@if`, `@for`), it also registers `use_effect` hooks to trigger re-evaluation when dependencies change.
* **Result**: The component function ultimately returns a `View`. This `View` is a closure that, when executed, will populate a `DrawContext` by calling `context.draw_children()`.
### 2. `DrawContext` Construction (`render_view`)
* **`Engine::render`**: In the main rendering loop, the `Engine` calls `render` for the current `Context`.
* **`Engine::render_view`**: The `Engine` creates a fresh, empty `DrawContext` for the entire screen `Area`. It then executes the root component's `View` (the closure generated in Stage 1) against this `DrawContext`.
* **`Context::draw_children`**: The root `View`'s closure invokes `context.draw_children()`. This method iterates through all child `Scope`s and their contained `Context`s. For each child `Context`, it retrieves its `View` and adds a `DrawInstruction::View` to the current `DrawContext`, recursively starting the `render_view` process for children within their allocated `Area`.
* **`DrawContext::draw_text`, `DrawContext::draw_view`, `DrawContext::allocate`**: As `View`s are executed, they add `DrawInstruction`s to the `DrawContext` using methods like `draw_text` for text, `draw_view` for child components, and `allocate` to mark used screen regions.
* **Result**: A fully populated `DrawContext` containing a flat list of `DrawInstruction`s, ready for rendering.
### 3. `DrawInstruction` Execution (`draw_context`)
* **`Engine::draw_context`**: After `render_view` has produced a complete `DrawContext`, the `Engine`'s `draw_context` method is called. This is the stage where the actual terminal output happens.
* **Instruction Iteration**: `draw_context` iterates through the `Vec<DrawInstruction>` inside the `DrawContext`.
* **`Text`**: For `DrawInstruction::Text(point, text)`, the engine translates `point` (which is relative to the `DrawContext`'s `area`) into absolute terminal coordinates and uses `crossterm` to move the cursor and print the `text`.
* **`View`**: For `DrawInstruction::View(area, view)`, the engine recursively calls `render_view` for the child `view` within its specific `area`, then processes the resulting `DrawContext`.
* **`Child`**: For `DrawInstruction::Child(point, child_ctx)`, the engine recursively calls `draw_context` for the `child_ctx`, applying the `point` offset.
* **Terminal Output**: The `Console` engine uses `crossterm` functions (like `MoveTo`, `Print`, `Clear`) to modify the terminal buffer.
* **Result**: The visible TUI on the user's screen.
## `render_delay` and Loop
After `draw_context` completes, the `Engine` typically calls `render_delay()` (defaulting to 16ms for ~60 FPS) before the entire loop restarts with the next `Engine::render` call. This continuous loop maintains a responsive and updated UI.
```mermaid
graph TD
A[Component Function (`#[component]`)] --> B(Generates `Rsx` object)
B --> C(Rsx::view(&cx))
C -- calls Rsx::generate_children --> D(Builds child Contexts & Scopes)
D --> E(Returns a `View` closure)
subgraph Engine Loop
F[Engine::render(root_cx)] --> G(Engine::render_view(full_screen_area, root_view))
G -- creates empty DrawContext --> H(Executes root_view closure)
H -- root_view calls Context::draw_children --> I(Recursively adds DrawInstruction::View for children)
I -- children's Views populate DrawContext --> J(Result: Full DrawContext with instructions)
J --> K(Engine::draw_context(full_DrawContext))
K -- iterates DrawInstructions --> L(Executes terminal ops via crossterm)
L --> M[Visible TUI]
M -- optional delay --> N(Engine::render_delay)
N --> F
end
```
## Key Principles
* **Declarative vs. Imperative**: Components declare *what* to draw (`View`, `DrawInstruction`), not *how* to directly manipulate the terminal. The engine handles the imperative *how*.
* **Separation of Concerns**: Each stage focuses on a specific responsibility: component logic, state management, UI tree construction, and final rendering.
* **Reactivity Integration**: Dynamic `rsx!` blocks and `use_effect` ensure that only affected parts of the `View` or `DrawContext` are re-generated efficiently when state changes, minimizing redundant work.
* **Extensibility**: The `Engine` trait allows for different rendering backends (e.g., to a file, to a graphical window, or for benchmarking) without modifying component logic.
Understanding this pipeline helps in debugging rendering issues, optimizing performance, and building custom rendering logic within your OSUI applications.
**Next:** Explore advanced topics like [Performance Benchmarking](../advanced/00-performance-benchmarking.md).
@@ -0,0 +1,186 @@
---
sidebar_position: 0
title: Performance Benchmarking
---
# Performance Benchmarking
Optimizing rendering performance is crucial for smooth and responsive TUI applications, especially those with complex layouts or frequent updates. OSUI provides a built-in `Benchmark` engine wrapper that allows you to easily measure the rendering speed of your components.
## Current performance
All the latest benchmark results are in [benchmark.csv](https://github.com/osui-rs/osui/blob/master/benchmark.csv)
![Dot diagram](3dplot-gen-dot.png)
![Dot diagram](3dplot-gen-surface.png)
## The `Benchmark` Engine
The `osui::engine::benchmark` module offers the `Benchmark<T: Engine>` struct, which wraps an existing engine (like `Console`) and records detailed timing information for its rendering cycles.
### How it works:
1. You instantiate a `Benchmark` by passing it another `Engine` (e.g., `Console::new()`).
2. When you call `benchmark_engine.run(YourApp {})`, the `Benchmark` engine takes over.
3. Instead of running the application indefinitely, it performs a fixed number of render cycles (defaulting to 40 in `Benchmark::run`).
4. For each cycle, it precisely measures the time taken to `render` your root component.
5. After all cycles, it clears the screen and returns a `BenchmarkResult` containing statistics.
## `BenchmarkResult`
The `BenchmarkResult` struct holds the collected performance metrics:
```rust
pub struct BenchmarkResult {
pub average: u128, // Average render time in microseconds
pub min: u128, // Minimum render time in microseconds
pub max: u128, // Maximum render time in microseconds
pub total_render: u128, // Sum of all render times in microseconds
pub total: u128, // Total time spent during the benchmark (including setup)
}
```
These values are typically in microseconds (`µs`).
## Basic Usage Example
Let's use the `simple_benchmark.rs` example to see the `Benchmark` engine in action:
```rust title="examples/simple_benchmark.rs"
use osui::prelude::*;
use std::sync::Arc; // Needed for Arc<Context>
pub fn main() {
// 1. Create a Console engine instance.
let console_engine = Console::new();
// 2. Wrap it with the Benchmark engine.
let benchmark_engine = Benchmark::new(console_engine);
// 3. Run your application (or component) through the Benchmark engine.
let benchmark_result = benchmark_engine.run(App {}).expect("Failed to run benchmark");
// 4. Print the results.
println!("Avg: {} μs", benchmark_result.average);
println!("Min: {} μs", benchmark_result.min);
println!("Max: {} μs", benchmark_result.max);
println!("Tot: {} μs", benchmark_result.total);
println!("Tot Render: {} μs", benchmark_result.total_render);
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
"Hello, world!"
}
.view(&cx)
}
```
To run this example:
```bash
cargo run --example simple_benchmark
```
You will see output similar to:
```
Avg: 1078 μs
Min: 1078 μs
Max: 1078 μs
Tot: 43120 μs
Tot Render: 43120 μs
```
*(Note: Actual values will vary based on your system and terminal emulator.)*
## Advanced Usage: Benchmarking Complex Scenarios
The `benchmark.rs` example demonstrates how to benchmark nested components and iterate through different complexity levels. This is useful for identifying performance bottlenecks in specific UI patterns.
```rust title="examples/benchmark.rs"
use osui::prelude::*;
use std::collections::HashMap; // Needed for HashMap
pub fn main() {
let engine = Arc::new(Benchmark::new(Console::new())); // Wrap Console in Benchmark, then Arc it.
let mut benchmark_results: HashMap<(usize, usize), BenchmarkResult> = HashMap::new();
// Iterate through different levels of nesting (n) and iterations (i)
for i in 0..15 {
for n in 0..15 {
let res = {
let mut results = Vec::with_capacity(6);
// Run each specific benchmark configuration multiple times (e.g., 6)
// to get more consistent results, then pick the median (index 3 after sort).
for _ in 0..6 {
results.push(
engine
.run(App {
n: n * 72, // Scale nesting depth
i: i * 72, // Scale iteration count (for loops)
})
.expect("Failed to run engine"),
);
}
results.sort_by_key(|r| r.total_render); // Sort by total render time
results[3].clone() // Take the median result
};
benchmark_results.insert((i, n as usize), res);
}
}
// Output results in CSV format
println!("Iterx72,Nestingx72,Time µs");
for ((i, n), bench) in benchmark_results.iter() {
println!("{i},{n},{}", bench.total_render);
}
}
// A recursive component that creates nested children and loops
#[component]
fn App(cx: &Arc<Context>, n: usize, i: usize) -> View {
let n = n.clone(); // Clone props for closure (necessary for #[component] macro)
let i = i.clone();
if n == 0 {
// Base case: deepest level, render a simple string
rsx! {
"Hello, world!"
}
.view(&cx)
} else {
// Recursive case: create `i` number of children, each with reduced nesting `n-1`
rsx! {
@for _ in (0..i) { // Loop `i` times
App { n: n - 1, i: 0 } // Create a nested App component
}
}
.view(&cx)
}
}
```
This example:
* Uses an `Arc<Benchmark>` to allow `run` to be called multiple times.
* Iterates through different `n` (nesting depth) and `i` (number of children in a loop) values.
* Runs each configuration multiple times and takes the median `total_render` to reduce noise.
* Prints the results in CSV format, which can be easily imported into spreadsheet software for analysis (like the `benchmark.csv` in the repository).
## Interpreting Results
* **`average`, `min`, `max`**: Provide insight into the consistency of your rendering performance. A large difference between `min` and `max` might indicate inconsistencies or external factors affecting performance.
* **`total_render`**: The sum of all individual render cycle times. This is often the most important metric for overall performance.
* **`total`**: The total time for the benchmark process, including setup. This is less about rendering speed and more about the overhead of the benchmark itself.
When analyzing benchmarks, look for:
* **Linear vs. Non-linear Scaling**: How does `total_render` increase as you increase nesting depth or the number of components? Ideally, it should scale linearly.
* **Bottlenecks**: Can you isolate which components or `rsx!` patterns (e.g., complex loops, many dynamic scopes) contribute most to render time?
* **Regression**: Use benchmarks in your CI/CD pipeline to detect performance regressions introduced by new code.
By leveraging OSUI's `Benchmark` engine, you can gain valuable insights into your TUI application's performance characteristics and make data-driven decisions for optimization.
**Next:** Learn how to customize OSUI by [Implementing a Custom Engine](./01-customizing-the-engine.md).
@@ -0,0 +1,260 @@
---
sidebar_position: 1
title: Customizing the Engine
---
# Customizing the Engine
OSUI's `Engine` and `CommandExecutor` traits are designed to be highly extensible. While the `Console` engine provides `crossterm`-based terminal rendering, you might want to create a custom engine for various reasons:
* **Different Rendering Backend**: Render to a graphical window (e.g., using `minifb` or `pixels`), a web canvas, or a specific hardware display.
* **Headless Testing**: Create a dummy engine that doesn't render anything but processes all commands and component logic, useful for fast unit or integration tests.
* **Logging/Debugging**: An engine that logs all `DrawInstruction`s to a file for analysis.
* **Specialized Behavior**: Implement custom render loops, input handling, or command processing.
This guide will walk you through the process of implementing your own `Engine` and `CommandExecutor`.
## Implementing `CommandExecutor`
First, let's define a custom `CommandExecutor`. This trait is responsible for processing commands issued by components (e.g., `cx.stop()`).
```rust
use osui::prelude::*;
use std::{
any::Any,
sync::{Arc, Mutex},
};
// Define a custom command
#[derive(Debug, Clone)]
pub struct CustomCommand(pub String);
impl Command for CustomCommand {
fn as_any(&self) -> &dyn Any {
self
}
}
pub struct MyCustomExecutor {
running: Mutex<bool>,
received_commands: Mutex<Vec<CustomCommand>>,
}
impl MyCustomExecutor {
pub fn new() -> Arc<Self> {
Arc::new(Self {
running: Mutex::new(true),
received_commands: Mutex::new(Vec::new()),
})
}
pub fn stop_engine(&self) -> crate::Result<()> {
*self.running.lock()? = false;
Ok(())
}
pub fn get_status(&self) -> bool {
*self.running.lock().unwrap()
}
pub fn get_received_commands(&self) -> Vec<CustomCommand> {
self.received_commands.lock().unwrap().clone()
}
}
impl CommandExecutor for MyCustomExecutor {
fn execute_command(&self, command: &Arc<dyn Command>) -> crate::Result<()> {
let command_any = command.as_any();
// Handle built-in Stop command
if let Some(commands::Stop) = command_any.downcast_ref::<commands::Stop>() {
println!("MyCustomExecutor: Received Stop command.");
return self.stop_engine();
}
// Handle our custom command
if let Some(custom_cmd) = command_any.downcast_ref::<CustomCommand>() {
println!("MyCustomExecutor: Received CustomCommand: {:?}", custom_cmd);
self.received_commands.lock().unwrap().push(custom_cmd.clone());
return Ok(());
}
println!("MyCustomExecutor: Unhandled command.");
Ok(())
}
}
```
## Implementing `Engine`
Now, let's create a simple "headless" engine that doesn't draw to the terminal but just logs rendering events.
```rust
use osui::prelude::*;
use std::sync::Arc;
pub struct MyHeadlessEngine {
executor: Arc<MyCustomExecutor>,
log_output: Mutex<Vec<String>>,
}
impl MyHeadlessEngine {
pub fn new() -> Self {
Self {
executor: MyCustomExecutor::new(),
log_output: Mutex::new(Vec::new()),
}
}
// Helper to log messages
fn log(&self, msg: &str) {
self.log_output.lock().unwrap().push(msg.to_string());
}
pub fn get_log(&self) -> Vec<String> {
self.log_output.lock().unwrap().clone()
}
}
impl Engine for MyHeadlessEngine {
fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<()> {
self.log("Engine: Initializing component...");
let cx = self.init(component);
while self.executor.get_status() {
self.log("Engine: Starting render cycle...");
self.render(&cx);
self.log("Engine: Render cycle complete. Delaying...");
self.render_delay(); // Use default delay or implement custom
}
self.log("Engine: Application stopped.");
Ok(())
}
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context> {
// Perform any setup needed for your custom engine
self.log("Engine: Component initialized.");
let cx = Context::new(component, self.executor.clone());
cx.refresh(); // Initial render of the component
cx
}
fn render(&self, cx: &Arc<Context>) {
let area = Area { x: 0, y: 0, width: 80, height: 24 }; // Define a virtual screen size
let draw_ctx = self.render_view(&area, &cx.get_view());
self.draw_context(&draw_ctx);
}
// No actual delay for headless, or keep default for testing loop speed
fn render_delay(&self) {
// crate::sleep(16); // Uncomment for actual delay
}
fn render_view(&self, area: &Area, view: &View) -> DrawContext {
self.log(&format!("Engine: Rendering view in area: {:?}", area));
let mut context = DrawContext::new(area.clone());
view(&mut context); // Execute the View closure to populate DrawContext
context
}
fn draw_context(&self, ctx: &DrawContext) {
self.log(&format!("Engine: Drawing context with {} instructions.", ctx.drawing.len()));
for inst in &ctx.drawing {
match inst {
DrawInstruction::Text(point, text) => self.log(&format!(" Draw Text at {:?}: '{}'", point, text)),
DrawInstruction::View(area, view) => {
self.log(&format!(" Draw Child View in area: {:?}", area));
self.draw_context(&self.render_view(area, view)); // Recursively render child views
},
DrawInstruction::Child(point, child_ctx) => {
self.log(&format!(" Draw Child DrawContext at {:?}.", point));
self.draw_context(child_ctx); // Recursively draw child contexts
},
}
}
}
fn executor(&self) -> Arc<dyn CommandExecutor> {
self.executor.clone()
}
}
```
## Using Your Custom Engine
```rust
use osui::prelude::*;
use std::sync::Arc;
// (Include MyHeadlessEngine, MyCustomExecutor, CustomCommand definitions here)
#[component]
fn MyApp(cx: &Arc<Context>) -> View {
let counter = use_state(0);
use_effect(
{
let cx = cx.clone();
let counter = counter.clone();
move || {
// Periodically increment counter and emit custom command
loop {
sleep(200);
let new_val = *counter.get() + 1;
counter.set(new_val);
if new_val >= 3 {
cx.execute(CustomCommand(format!("Counter reached {}", new_val))).expect("Cmd failed");
cx.stop().expect("Stop failed");
break;
}
}
}
},
&[], // Run once on mount
);
rsx! {
format!("Counter value: {}", counter.get_dl())
}.view(&cx)
}
fn main() {
let my_engine = MyHeadlessEngine::new();
let executor = my_engine.executor.clone(); // Get a reference to the executor
my_engine.run(MyApp {}).expect("Failed to run custom engine");
println!("\n--- Engine Log ---");
for line in my_engine.get_log() {
println!("{}", line);
}
println!("\n--- Received Commands ---");
for cmd in executor.get_received_commands() {
println!("{:?}", cmd);
}
}
```
### Explanation:
1. **`MyCustomExecutor`**: Implements `CommandExecutor`. It handles the built-in `commands::Stop` and our new `CustomCommand`. It also keeps a log of received custom commands for verification.
2. **`MyHeadlessEngine`**: Implements `Engine`.
* It takes `MyCustomExecutor` as its command executor.
* `run` method establishes a basic loop that continues as long as `executor.get_status()` is `true`.
* `render` orchestrates the `render_view` and `draw_context` calls.
* `render_view` executes the component `View` and collects `DrawInstruction`s.
* `draw_context` iterates through `DrawInstruction`s, logging them instead of actually drawing to a terminal. It recursively handles `DrawInstruction::View` and `DrawInstruction::Child`.
3. **`MyApp` Component**:
* Uses `use_state` for a counter.
* Uses `use_effect` to periodically increment the counter.
* When the counter reaches 3, it `execute`s our `CustomCommand` and then `stop()`s the engine.
4. **`main` Function**:
* Instantiates `MyHeadlessEngine`.
* Calls `my_engine.run(MyApp {})`.
* After the engine stops, it prints the internal log and received commands from the executor, allowing you to verify that component logic and commands were processed correctly.
By following this pattern, you can integrate OSUI's powerful component and state management system with virtually any rendering or execution environment you desire.
**Next:** Dive into the internals of OSUI's macro system in [Internals: Macros](./02-internals-macros.md).
+130
View File
@@ -0,0 +1,130 @@
---
sidebar_position: 2
---
# Internals: Macros
OSUI heavily relies on procedural macros to provide its ergonomic, declarative syntax. The `osui-macros` crate contains the logic for the `#[component]` attribute macro and the `rsx!` function macro. Understanding how these macros work under the hood gives you a deeper insight into OSUI's architecture and capabilities.
## Introduction to Procedural Macros
Procedural macros are functions that operate on the Rust syntax tree (Abstract Syntax Tree or AST) during compilation. They receive `TokenStream`s as input and produce `TokenStream`s as output, effectively transforming your code. OSUI's macros leverage the following crates:
* **`syn`**: A parser for Rust's syntax tree. It allows macros to parse input `TokenStream`s into structured Rust AST types (like `ItemFn`, `Expr`, `Path`, etc.).
* **`quote`**: A quasiquoting library that makes it easy to generate Rust code (as `TokenStream`s) from AST fragments.
* **`proc-macro2`**: Provides types like `TokenStream` and `Ident` that are compatible with `syn` and `quote`, enabling ergonomic manipulation of tokens.
## `#[component]` Attribute Macro (`macros/src/lib.rs`)
The `#[component]` macro transforms a Rust function into an OSUI component struct.
### Input
It takes an `ItemFn` (the parsed function definition) as input.
```rust
// Original function in user's code
#[component]
pub fn MyComponent(cx: &Arc<Context>, prop1: &String, prop2: &i32) -> View {
// ... function body ...
}
```
### Core Logic
1. **Parse Function Signature**:
* It extracts the function's name (`MyComponent`).
* It validates the first argument `cx: &Arc<Context>`.
* It iterates through the remaining arguments (`prop1: &String`, `prop2: &i32`), which become the component's props.
2. **Generate Component Struct**: For each prop parameter, it determines the *owned* type (e.g., `&String` becomes `String`, `&i32` becomes `i32`). It then uses `quote!` to generate a new struct:
```rust
pub struct MyComponent {
pub prop1: String, // Owned types
pub prop2: i32,
}
```
3. **Implement `ComponentImpl`**: It then generates an `impl ComponentImpl for MyComponent` block. The `call` method of this trait:
* Takes `&self` and `cx: &Arc<Context>`.
* Internally calls a generated `Self::component` method (which is your original function's body).
* Passes `cx` and references (`&self.prop1`, `&self.prop2`) to the stored props from the generated struct.
```rust
impl MyComponent {
// This is your original function, renamed and wrapped
pub fn component(cx: &Arc<Context>, prop1: &str, prop2: &i32) -> View { /* ... body ... */ }
}
impl ComponentImpl for MyComponent {
fn call(&self, cx: &Arc<Context>) -> View {
Self::component(cx, &self.prop1, &self.prop2) // Pass references to owned props
}
}
```
### Output
The macro replaces the original function with the generated struct, its `component` method, and the `ComponentImpl` implementation. This transformation makes `MyComponent` a valid OSUI component that can be instantiated with props in `rsx!`.
## `rsx!` Function Macro (`macros/src/parse.rs` & `macros/src/emit.rs`)
The `rsx!` macro is more complex, involving a two-step process: parsing the custom syntax and then emitting standard Rust code.
### 1. Parsing (`macros/src/parse.rs`)
The `parse` module defines an AST (Abstract Syntax Tree) for the `rsx!` syntax.
* **`RsxRoot`**: The top-level container, holding a vector of `RsxNode`s.
* **`RsxNode`**: An enum representing different types of nodes in the `rsx!` tree:
* `Text(LitStr)`: For `"Hello"` literals.
* `Expr(Expr)`: For `@{some_expression}` blocks.
* `Component { path: Path, props: Vec<RsxProp>, children: Vec<RsxNode> }`: For `MyComponent { prop: val, ... }`.
* `Mount(Ident)`: For `!my_mount_hook`.
* `If { deps: Vec<Dep>, cond: Expr, children: Vec<RsxNode> }`: For `@if condition { ... }`.
* `For { deps: Vec<Dep>, pat: Pat, expr: Expr, children: Vec<RsxNode> }`: For `@for item in items { ... }`.
* **`RsxProp`**: Represents a `name: value` pair for component props.
* **`Dep`**: Represents a dependency for dynamic blocks (`%my_state as my_alias`).
The `parse::RsxRoot::parse` method uses `syn`'s `ParseStream` to tokenize the `rsx!` input and build this AST. It intelligently differentiates between text, expressions, component names, and control flow keywords (`@if`, `@for`, `!`).
### 2. Emitting (`macros/src/emit.rs`)
The `emit` module takes the parsed `RsxRoot` AST and converts it into a `TokenStream` of standard Rust code that constructs `osui::frontend::Rsx` objects.
* **`emit_rsx(root: RsxRoot)`**: The entry point, which initializes an `osui::frontend::Rsx` object and then iterates through the `root.nodes`.
* **`emit_node_scope(node: &RsxNode)`**: For each `RsxNode`, it generates code that calls the appropriate `Rsx` builder method:
* **`RsxNode::Text`**: Emits `r.static_scope(move |scope| { scope.view(...) });` which draws text.
* **`RsxNode::Expr`**: Emits `r.child(expression);`.
* **`RsxNode::Component`**: Emits `r.static_scope(move |scope| { scope.child(ComponentName { props: ... }, None); });`. If children are present, they are recursively emitted into a nested `Rsx` object and passed as the `children` prop.
* **`RsxNode::Mount`**: Emits `mount_hook_instance.mount();`.
* **`RsxNode::If`**: Emits `r.dynamic_scope(move |scope| { if condition { ... } else { ... } }, dependencies);`. The `dependencies` are converted to `Vec<Arc<dyn HookDependency>>`.
* **`RsxNode::For`**: Similar to `If`, emits `r.dynamic_scope(move |scope| { for pattern in expr { ... } }, dependencies);`.
### Output
The `rsx!` macro produces a `TokenStream` that looks something like this (simplified):
```rust
// For: rsx! { "Hello" MyComponent { prop: value } }
{
let mut r = osui::frontend::Rsx::new();
r.static_scope(move |scope| {
scope.view(std::sync::Arc::new(move |ctx| {
ctx.draw_text(osui::render::Point { x: 0, y: 0 }, &format!("Hello"))
}));
});
r.static_scope(move |scope| {
scope.child(
MyComponent { prop: value.to_string() }, // Note: Prop value converted to owned type
None,
);
});
r
}
```
This generated code, when compiled, constructs the `osui::frontend::Rsx` object that OSUI's runtime can then interpret to build the component tree and render the UI.
## Summary
The `osui-macros` crate plays a pivotal role in shaping OSUI's developer experience. `#[component]` streamlines component definition, while `rsx!` provides a powerful, declarative way to compose UIs by transforming custom syntax into efficient runtime calls. These macros are complex but essential for creating a modern, React-like development flow in a TUI environment.
**Next:** Learn how you can contribute to the OSUI project in [Contributing](./03-contributing.md).
+115
View File
@@ -0,0 +1,115 @@
---
sidebar_position: 3
title: Contributing
---
# Contributing to OSUI
We welcome and appreciate contributions from the community! Whether it's reporting bugs, suggesting features, improving documentation, or submitting code, your help makes OSUI better for everyone.
## How to Contribute
### 1. Report Bugs
If you find a bug, please open an issue on our [GitHub repository](https://github.com/osui-rs/osui/issues). When reporting a bug, please include:
* A clear and concise description of the bug.
* Steps to reproduce the behavior.
* Expected behavior.
* Actual behavior.
* Your OSUI version, Rust version, and operating system.
* Any relevant code snippets or error messages.
### 2. Suggest Features
Have an idea for a new feature or improvement? Feel free to open an issue on GitHub to discuss it. Please provide:
* A clear and concise description of the proposed feature.
* Why you think it would be valuable to OSUI.
* Any potential use cases or examples.
### 3. Improve Documentation
Good documentation is vital for any library. If you find errors, omissions, or areas that could be explained more clearly in our docs, please consider:
* Opening an issue to point out the specific area.
* Submitting a pull request with your suggested improvements.
### 4. Contribute Code
If you'd like to contribute code, here's the general workflow:
#### Fork the Repository
First, fork the [osui-rs/osui](https://github.com/osui-rs/osui) repository to your own GitHub account.
#### Clone Your Fork
```bash
git clone https://github.com/YOUR_USERNAME/osui.git
cd osui
```
#### Create a New Branch
Create a new branch for your feature or bug fix. Use a descriptive name:
```bash
git checkout -b feature/my-awesome-feature
# or
git checkout -b bugfix/fix-rendering-issue
```
#### Make Your Changes
* Write clean, idiomatic Rust code.
* Follow existing code style and conventions.
* Add comments where necessary to explain complex logic.
* **Write Tests**: If you're adding new features or fixing bugs, please include appropriate unit and/or integration tests to cover your changes.
* **Update Documentation**: If your changes affect the public API or add new functionality, please update the relevant documentation files.
#### Run Tests
Before submitting a pull request, ensure all existing tests pass and your new tests pass:
```bash
cargo test --workspace
```
#### Format and Lint
Make sure your code is formatted correctly and passes lint checks:
```bash
cargo fmt --all
cargo clippy --all-targets --all-features
```
#### Commit Your Changes
Commit your changes with clear and concise commit messages. A good commit message explains *what* changed and *why*.
```bash
git commit -m "feat: Add new awesome feature"
# or
git commit -m "fix: Resolve rendering issue on Windows"
```
#### Push to Your Fork
```bash
git push origin feature/my-awesome-feature
```
#### Create a Pull Request
Go to the [osui-rs/osui](https://github.com/osui-rs/osui) repository on GitHub and open a new pull request.
* Provide a clear title and description for your pull request.
* Reference any related issues (e.g., "Fixes #123" or "Closes #456").
* The project maintainers will review your PR, provide feedback, and work with you to get it merged.
## Code of Conduct
Please note that this project is released with a [Contributor Code of Conduct](https://github.com/osui-rs/osui/blob/main/CODE_OF_CONDUCT.md). By participating in this project, you agree to abide by its terms.
Thank you for considering contributing to OSUI! Your efforts help foster a vibrant and robust TUI ecosystem in Rust.
Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

-1
View File
@@ -13,7 +13,6 @@ const config: Config = {
organizationName: "osui-rs",
projectName: "osui",
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn",
i18n: {
defaultLocale: "en",
locales: ["en"],
+3806 -2841
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -15,8 +15,8 @@
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "3.6.3",
"@docusaurus/preset-classic": "3.6.3",
"@docusaurus/core": "^3.9.2",
"@docusaurus/preset-classic": "^3.9.2",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"docusaurus-lunr-search": "^3.5.0",
@@ -25,7 +25,7 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.6.3",
"@docusaurus/module-type-aliases": "^3.9.2",
"@docusaurus/tsconfig": "3.6.3",
"@docusaurus/types": "3.6.3",
"typescript": "~5.6.2"
@@ -0,0 +1,33 @@
---
sidebar_position: 0
title: Introduction
slug: /
---
# Introduction to OSUI
OSUI is a powerful and flexible Rust library for building advanced Terminal User Interfaces (TUIs). It provides a component-based architecture inspired by modern web frameworks, offering a familiar and ergonomic development experience for crafting interactive command-line applications.
## What is OSUI?
OSUI stands for "Operating System User Interface" in the terminal context. It aims to bridge the gap between simple text-based applications and rich graphical user interfaces by offering a robust framework for complex TUI development.
### Key Features
* **Component System**: Build UIs using reusable, composable components that manage their own state and lifecycle.
* **Reactive State Management**: Leverage React-like hooks (`useState`, `useEffect`) for efficient and predictable state handling.
* **Declarative UI with RSX**: Define your UI structure using an intuitive, macro-based RSX (React-like Syntax) similar to JSX.
* **Event Handling**: A type-safe event system allows components to communicate and respond to user input and internal changes.
* **Pluggable Rendering Engine**: Ships with a `Console` engine for `crossterm`-based terminal rendering, and an extensible `Engine` trait for custom backends.
* **Benchmark Tooling**: Built-in benchmarking capabilities to measure and optimize rendering performance.
## Why OSUI?
Traditional TUI libraries often require imperative manipulation of the terminal buffer, which can become cumbersome for complex applications. OSUI addresses this by:
* **Promoting Modularity**: Components encapsulate UI logic and appearance, making code easier to organize, test, and maintain.
* **Simplifying State Logic**: The hook-based state management system ensures that your UI automatically reacts to data changes, reducing boilerplate and potential bugs.
* **Enhancing Developer Experience**: The `rsx!` macro and `#[component]` attribute provide a declarative way to define your UI, allowing you to focus on *what* your UI should look like, rather than *how* to draw it.
* **Encouraging Scalability**: The clear separation of concerns (components, state, rendering engine) makes OSUI suitable for applications ranging from simple utilities to complex interactive dashboards.
Whether you're building a CLI dashboard, an interactive configuration tool, or a text-based game, OSUI provides the tools to create engaging and performant terminal experiences.
@@ -0,0 +1,43 @@
---
sidebar_position: 1
title: Installation
---
# Installation
To get started with OSUI, you need to add it as a dependency to your Rust project. OSUI is available on [crates.io](https://crates.io/crates/osui).
## Adding OSUI to Your Project
Open your project in your terminal and run this command:
```bash
cargo add osui
```
The `osui` crate re-exports its procedural macros (`#[component]` and `rsx!`) through its `prelude` module, so you typically don't need to add `osui-macros` as a separate dependency.
## Enabling the `rsx` Feature (Recommended)
The `rsx` feature is crucial for using OSUI's declarative UI syntax. It is usually enabled by default when you add `osui` as a dependency. If you ever explicitly disable default features for `osui`, remember to re-enable `rsx`:
```toml
[dependencies]
osui = { version = "0.2.0", features = ["rsx"] }
```
:::info
The `rsx` feature is vital for using OSUI's `rsx!` macro and `#[component]` attribute, which are fundamental to building UIs in OSUI.
:::
## Building Your Project
Once `osui` is added to your `Cargo.toml`, you can build your project using Cargo:
```bash
cargo build
```
This will download and compile OSUI and its dependencies.
You are now ready to start building your first OSUI application! Proceed to the [Getting Started](./02-getting-started.md) guide to write your first "Hello World" component.
@@ -0,0 +1,89 @@
---
sidebar_position: 2
title: Getting Started
---
# Getting Started: Hello World
Let's write a minimal OSUI application that displays "Hello World" in the terminal. This example will introduce you to the core concepts of an OSUI application: the `Console` engine, running an application, and defining a basic component with `rsx!`.
## 1. Create a New Project
If you haven't already, create a new Rust binary project:
```bash
cargo new hello-osui
cd hello-osui
```
## 2. Add OSUI Dependency
```bash
cargo add osui
```
## 3. Write the Hello World Code
Open `src/main.rs` and replace its contents with the following:
```rust title="src/main.rs"
use osui::prelude::*; // Import commonly used OSUI items
use std::sync::Arc;
pub fn main() {
// 1. Initialize the Console engine
// The Console engine uses crossterm to interact with the terminal.
let engine = Console::new();
// 2. Run the application
// The `run` method takes your root component and starts the rendering loop.
// It returns a `Result`, which we unwrap here for simplicity.
engine.run(App {}).expect("Failed to run OSUI application");
}
/// 3. Define your root component
/// The `#[component]` attribute transforms a function into a reusable UI component.
/// - The first parameter `cx: &Arc<Context>` is mandatory and provides access to component context (state, events, children).
/// - Components must return a `View`.
#[component]
fn App(cx: &Arc<Context>) -> View {
// 4. Use RSX (React-like Syntax) to define the UI
// `rsx!` is a procedural macro for declarative UI construction.
// Here, it just renders a simple string literal.
rsx! {
"Hello World"
}
// `view(&cx)` converts the RSX into a `View` that can be rendered.
.view(&cx)
}
```
## 4. Run Your Application
Execute your application from the terminal:
```bash
cargo run
```
You should see "Hello World" displayed in your terminal, which will then clear when the application exits.
## Understanding the Code
Let's break down the key parts of the "Hello World" example:
* **`use osui::prelude::*;`**: This line imports the `prelude` module, which re-exports the most commonly used types and macros from OSUI. This includes `Console`, `Context`, `View`, `#[component]`, and `rsx!`.
* **`Console::new()`**: The `Console` struct is OSUI's default rendering engine. It uses the `crossterm` library to draw content to your terminal.
* **`engine.run(App {})`**: This starts the OSUI application lifecycle. It takes an instance of your root component (`App {}` in this case) and begins the continuous rendering loop. The `App` component will be rendered repeatedly until the application is stopped (e.g., by pressing `Ctrl+C` or a command from within the app).
* **`#[component] fn App(cx: &Arc<Context>) -> View { ... }`**:
* The `#[component]` attribute is a procedural macro that transforms a standard Rust function into an OSUI component. This enables prop handling and integration into the component tree.
* All component functions must take `cx: &Arc<Context>` as their first argument. The `Context` provides access to component-specific state, lifecycle methods, event handlers, and the ability to add child components.
* Component functions must return a `View`, which is an `Arc<dyn Fn(&mut DrawContext) + Send + Sync>`. This `View` represents the instructions for rendering the component.
* **`rsx! { "Hello World" }.view(&cx)`**:
* The `rsx!` macro is OSUI's declarative UI syntax. It allows you to define a hierarchy of components and text nodes in a way that feels similar to HTML or React's JSX.
* In this simple case, `"Hello World"` is a text literal that `rsx!` converts into a renderable element.
* The `.view(&cx)` method takes the `Rsx` object generated by the `rsx!` macro and processes it within the current `Context`, ultimately producing the final `View` to be returned by the component.
This simple example demonstrates the fundamental building blocks of an OSUI application. Next, we'll explore how to create more complex components and pass data between them.
**Next:** Learn about [Creating Components](./03-example-basic-component.md) and passing them data.
@@ -0,0 +1,112 @@
---
sidebar_position: 3
title: Basic Component with Children
---
# Basic Component with Children and Props
In OSUI, applications are built by composing smaller, reusable components. This guide expands on the "Hello World" example by demonstrating how to define a custom component, pass data to it (props), and render its children.
## 1. The `MyComponent` Example
Let's modify our `src/main.rs` to introduce `MyComponent`.
```rust title="src/main.rs"
use osui::prelude::*;
use std::sync::Arc;
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run engine");
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
// Here, MyComponent is instantiated with a string literal as its child.
// This string will be available inside MyComponent via the `children` prop.
MyComponent { "------ example" }
}
.view(&cx)
}
#[component]
fn MyComponent(cx: &Arc<Context>, children: &Rsx) -> View {
rsx! {
// `@{children}` is an RSX expression that renders the children passed to MyComponent.
// It's a special syntax to embed an `Rsx` value directly into the component's output.
@{children}
"Simple Component"
}
.view(&cx)
}
```
## 2. Run the Application
```bash
cargo run
```
You should see output similar to this:
```
------ example
Simple Component
```
The exact positioning will depend on your terminal's size and OSUI's default rendering behavior, but the text "------ example" (from the child) should appear before "Simple Component" (from `MyComponent` itself).
## Understanding the Component Pattern
### The `children` Prop
In OSUI, just like in React or other component-based frameworks, content nested inside a component's `rsx!` invocation is implicitly passed as `children`.
When you write:
```rust
rsx! {
MyComponent { "------ example" }
}
```
The string literal `"------ example"` becomes the `children` prop for `MyComponent`.
### `MyComponent` Definition
Let's look at `MyComponent`'s definition:
```rust
#[component]
fn MyComponent(cx: &Arc<Context>, children: &Rsx) -> View {
// ...
}
```
1. **`#[component]`**: Marks `MyComponent` as a reusable component.
2. **`cx: &Arc<Context>`**: The mandatory context parameter.
3. **`children: &Rsx`**: This is where the magic happens. Any content passed as children within the `rsx!` invocation for `MyComponent` (like `"------ example"`) will be collected into an `Rsx` type and passed to this `children` prop. The `Rsx` type itself is a collection of renderable nodes.
4. **`-> View`**: Components must return a `View`.
### Rendering Children with `@{children}`
Inside `MyComponent`'s `rsx!`:
```rust
rsx! {
@{children} // Renders the content passed to MyComponent
"Simple Component"
}
```
* **`@{children}`**: This is an RSX expression. The `@{...}` syntax allows you to embed arbitrary Rust expressions directly into your `rsx!` output. In this case, `children` is an `&Rsx` value which implements `ToRsx`, making it eligible to be directly rendered as part of the component's output. When `children` is rendered, it generates its own `View`, effectively embedding the child content into the parent component's render tree.
* **`"Simple Component"`**: This is a direct text literal within `MyComponent`'s own output, rendered after the children.
This pattern allows you to build highly flexible and composable components, where parents can define the overall structure and layout, while children provide specific content.
## Next Steps
You've seen how to define components and pass basic children. The next step is to explore the full power of OSUI's RSX syntax, including how to pass other types of props, use conditional rendering, and loop through data.
**Next:** Dive into the details of the [RSX Syntax Guide](../guides/01-rsx-syntax.md).
@@ -0,0 +1,123 @@
---
sidebar_position: 0
title: Creating Components
---
# Creating Components
Components are the building blocks of any OSUI application. They encapsulate UI logic, state, and rendering instructions, making your code modular and reusable. This guide explains how to define and use components effectively.
## The `#[component]` Attribute
OSUI uses the `#[component]` procedural macro to transform a regular Rust function into an OSUI component. This macro handles the boilerplate necessary for prop handling and integrating the function into the component tree.
### Basic Structure
A component function generally looks like this:
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
pub fn MyComponent(cx: &Arc<Context>) -> View {
// Component logic goes here
rsx! {
"Hello from MyComponent!"
}.view(&cx)
}
```
**Key requirements:**
1. **`#[component]`**: Always annotate your component function with this attribute.
2. **Function Signature**:
* It must take `cx: &Arc<Context>` as its *first* argument. The `Context` is essential for managing state, events, and child components.
* It must return a `View`. A `View` is an `Arc<dyn Fn(&mut DrawContext) + Send + Sync>`, essentially a closure that contains the drawing instructions for your component.
3. **Return Value**: The most common way to return a `View` is by using the `rsx!` macro followed by `.view(&cx)`.
### Component Props
Components become truly powerful when they can receive data from their parents. These are called "props" (properties). To define props for your component, simply add more parameters to your component function after `cx: &Arc<Context>`.
OSUI's `#[component]` macro automatically generates a struct for your component based on these parameters.
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
pub fn GreetUser(cx: &Arc<Context>, name: &str, age: &u8) -> View {
// Access props directly by their parameter names
rsx! {
format!("Hello, {}! You are {} years old.", name, age)
}.view(&cx)
}
#[component]
pub fn App(cx: &Arc<Context>) -> View {
let user_name = "Alice".to_string();
let user_age = 30;
rsx! {
// Instantiate GreetUser and pass props
GreetUser {
name: user_name, // Prop name matches the parameter name
age: user_age,
}
}.view(&cx)
}
```
**Prop Rules:**
* **Parameter Names**: The names of your function parameters (e.g., `name`, `age`) become the names of the props you use when instantiating the component in `rsx!`.
* **Reference Types**: Props are typically passed as references (e.g., `&str`, `&u8`). The `#[component]` macro automatically "strips" the reference when generating the internal component struct, storing the owned type. This means you don't need to manually clone values unless you intend to move them into a closure or `State`.
* **`children` Prop**: As seen in the [previous guide](/docs/intro/03-example-basic-component.md), any content nested inside a component's `rsx!` invocation is implicitly passed as a `children: &Rsx` prop. This allows for flexible content composition.
### Example: Component with `children` and custom props
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
pub fn Card(cx: &Arc<Context>, title: &str, children: &Rsx) -> View {
rsx! {
format!("--- {} ---", title) // Render the title prop
@{children} // Render the children passed to Card
"----------------"
}.view(&cx)
}
#[component]
pub fn App(cx: &Arc<Context>) -> View {
rsx! {
Card {
title: "My Awesome Card", // Pass a custom prop
// The content below is passed as the `children` prop
rsx! {
"This is the content inside the card."
"It can span multiple lines or include other components."
}
}
Card {
title: "Another Card",
"Just some simple text here." // Even a single string literal can be children
}
}.view(&cx)
}
```
In this example, the `Card` component takes a `title` prop and a `children` prop. It renders the title, then its children, and finally a footer.
## When to Create a Component
* **Reusability**: If you find yourself writing the same UI structure multiple times, extract it into a component.
* **Separation of Concerns**: When a part of your UI has its own state or complex logic, it's a good candidate for a component.
* **Readability**: Breaking down large `rsx!` blocks into smaller components improves the readability and maintainability of your code.
* **Performance (Reactivity)**: Components, especially when using state hooks, allow OSUI to efficiently re-render only the parts of the UI that have changed.
By following these guidelines, you can build well-structured and scalable OSUI applications.
**Next:** Deep dive into the `rsx!` macro and its full capabilities in the [RSX Syntax Guide](./01-rsx-syntax.md).
@@ -0,0 +1,192 @@
---
sidebar_position: 1
title: RSX Syntax Guide
---
# RSX Syntax Guide
The `rsx!` macro is the cornerstone of OSUI's declarative UI system. Inspired by React's JSX, it provides an ergonomic way to define your component hierarchies directly in Rust code. This guide covers all the features of the `rsx!` syntax.
## Basic Structure
The `rsx!` macro produces an `Rsx` object, which is a collection of renderable nodes. You typically call `.view(&cx)` on the `Rsx` object to convert it into a `View` that your component returns.
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
// Your UI elements go here
"Hello, RSX!" // A simple text literal
}.view(&cx)
}
```
## Types of Nodes
`rsx!` supports several types of nodes:
### 1. Text Literals
Plain string literals are rendered as text.
```rust
rsx! {
"This is some text."
"This is another line of text."
}
```
### 2. Rust Expressions: `@{expr}`
You can embed any Rust expression that evaluates to a renderable type (one that implements `ToRsx`) using the `@{...}` syntax. This is useful for dynamic content or rendering other `Rsx` objects.
```rust
let dynamic_text = format!("The current time is: {:?}", std::time::SystemTime::now());
let other_rsx = rsx! { "Some nested content" };
rsx! {
@{dynamic_text} // Renders the string from the expression
@{other_rsx} // Renders another Rsx object
@{123 + 456} // Renders the result of the arithmetic operation (as a string)
}
```
:::tip
Any type that implements `std::fmt::Display` (like `String`, `&str`, `i32`, `f64`, etc.) automatically implements `ToRsx` and can be used directly within `rsx!`.
:::
### 3. Component Instantiation: `ComponentName { prop: value, ... }`
To use another component, specify its name (path) followed by an optional braced block containing its props and children.
```rust
#[component]
fn MyButton(cx: &Arc<Context>, text: &str) -> View {
rsx! {
format!("[ {} ]", text)
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
MyButton { text: "Click Me" } // Component with a prop
MyButton { "Another Button" } // Component with children (which becomes `text` if `children: &Rsx` is also defined)
}.view(&cx)
}
```
* **Props**: Key-value pairs (`prop_name: value`) inside the braces. The `prop_name` must match a parameter name in the target component's function signature.
* **Children**: Any `rsx!` content (text, expressions, other components) directly nested inside the component's braces after props will be collected into the special `children: &Rsx` prop, if defined by the component.
### 4. Conditional Rendering: `@if condition { ... }`
Conditionally render parts of your UI based on a boolean expression.
```rust
let show_message = true;
let is_admin = false;
rsx! {
@if show_message {
"This message is always shown."
}
@if is_admin {
"Admin panel access granted."
} else {
"Access denied." // `else` is optional
}
}
```
* The `condition` must be a Rust expression that evaluates to a `bool`.
* The content inside the `{...}` block is an `rsx!` fragment that will be rendered if the condition is true.
* An optional `else { ... }` block can follow for false conditions.
#### Reactivity with Dependencies: `%$dep @if condition { ... }`
For conditional rendering to react to state changes, you need to explicitly declare dependencies using the `%$dep` syntax.
```rust
let count = use_state(0); // A reactive state
rsx! {
%count @if *count.get() > 0 { // This block re-renders if `count` changes
format!("Count is: {}", count.get_dl())
} else {
"Count is zero."
}
}
```
* **`%count`**: Declares `count` as a dependency for this `if` block. When `count`'s value changes (via `count.set()` or `*count.get_mut()`), this entire `if` block will be re-evaluated and re-rendered.
* You can declare multiple dependencies: `%dep1, dep2, dep3 @if ...`
* You can also rename dependencies for clarity: `%original_name as new_name @if ...`
### 5. Loop Rendering: `@for pattern in expr { ... }`
Render a list of items by iterating over a collection.
```rust
let items = vec!["Apple", "Banana", "Cherry"];
rsx! {
"Fruits:"
@for item in items { // Loops over the `items` vector
format!("- {}", item)
}
"Numbers:"
@for i in (0..3) {
format!("Number: {}", i)
}
}
```
* `pattern` is a standard Rust `for` loop pattern (e.g., `item`, `(index, item)`, `_`).
* `expr` is a Rust expression that evaluates to an `IntoIterator`.
* The content inside the `{...}` block is an `rsx!` fragment that will be rendered for each iteration.
#### Reactivity with Dependencies: `%$dep @for pattern in expr { ... }`
Similar to `@if`, `@for` loops also support dependency tracking for reactive updates.
```rust
let my_list = use_state(vec!["One".to_string(), "Two".to_string()]);
// Later, you might update my_list.set(new_vec);
// or my_list.get_mut().push("Three");
rsx! {
"My Dynamic List:"
%my_list @for item in my_list.get_dl() { // This block re-renders if `my_list` changes
format!("- {}", item)
}
}
```
* **`%my_list`**: Declares `my_list` as a dependency. When `my_list` is updated, the loop will be re-executed, rendering the new list.
### 6. Mount Hook: `!mount_hook_instance`
This syntax is used to explicitly "mount" a component's lifecycle hook. This is specifically for `use_mount_manual`.
```rust
let my_mount_hook = use_mount_manual();
rsx! {
"This text is always visible."
!my_mount_hook // Explicitly triggers the mount effects for `my_mount_hook`
}
```
* When `!my_mount_hook` is encountered in the `rsx!` output, it calls `my_mount_hook.mount()`, triggering any `use_effect` callbacks registered with that specific `Mount` instance.
## Summary
The `rsx!` macro is a powerful tool for building declarative user interfaces in OSUI. By combining text, expressions, components, conditionals, and loops with reactive dependencies, you can create complex and dynamic TUIs with a clean and familiar syntax.
**Next:** Learn how to manage component data over time with OSUI's [State Management hooks](./02-state-management.md).
@@ -0,0 +1,195 @@
---
sidebar_position: 2
title: State Management
---
# State Management
Effective state management is crucial for building interactive and dynamic TUI applications. OSUI provides a React-like hook system that enables components to hold mutable state and react to changes efficiently. This guide covers the core state management hooks: `use_state` and `use_effect`.
## `use_state`: Managing Component-Local State
The `use_state` hook allows your components to declare and manage mutable, reactive state. When state managed by `use_state` changes, OSUI automatically re-renders affected parts of your UI.
### Basic Usage
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn Counter(cx: &Arc<Context>) -> View {
// 1. Initialize state with `use_state`
// `count` is a `State<i32>`, initialized to 0.
let count = use_state(0);
// 2. Define a button that increments the count
// This is a placeholder for actual interactive elements.
// In a real app, an event handler would trigger `count.set()` or `*count.get_mut()`.
let increment_button_simulated = {
let count = count.clone(); // Clone the State handle to move into the closure
move || {
// Option 1: Using `set()` for direct replacement
// count.set(*count.get() + 1);
// Option 2: Using `get()` for mutable access (recommended for complex changes)
*count.get() += 1; // `Inner` guard automatically calls `update()` on drop
}
};
// Simulate clicking the button once per render for demonstration
// In a real app, this would be triggered by user input or other events.
increment_button_simulated();
rsx! {
// 3. Display the state value
// `count.get_dl()` gets a cloned, deadlock-less copy of the value.
// `count.get()` returns a guard for mutable access, also deadlock-less.
format!("Count: {}", count.get_dl())
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(Counter {}).expect("Failed to run Counter app");
}
```
In this example, the `Counter` component's state (`count`) is incremented on each render cycle (simulated). The `rsx!` macro automatically updates to reflect the new `count` value because the `Counter` component is re-rendered by the engine and the `count` state is used.
### `State<T>` and `Inner<'a, T>`
* **`State<T>`**: This is the primary handle to your reactive state. It's an `Arc<Mutex<T>>` internally, allowing safe shared ownership and mutation across threads. When you call `use_state(value)`, you get a `State<T>`.
* **`count.get()`**: This method returns an `Inner<'a, T>` guard. `Inner` implements `Deref` and `DerefMut`, so you can treat it almost like a direct reference to your data (`*count.get()`). The `Inner` guard is crucial because it automatically triggers updates to dependents when it's dropped *if* the value was mutated.
* **`count.set(value)`**: A convenience method to replace the entire state value and then trigger updates.
* **`count.get_dl()`**: Returns a cloned copy of the state value. The "dl" stands for "deadlock-less", as it doesn't hold the mutex lock for an extended period, making it safer for quick reads. Use this when you only need to read the value and cloning is cheap.
* **`count.update()`**: Manually notifies all dependents that the state *might* have changed, even if you didn't use `get_mut()` or `set()`. Useful if you modify the internal `Arc<Mutex<T>>` directly (not recommended) or a complex part of `T` without triggering the `DerefMut` auto-update.
### Important Considerations for `State<T>`:
* **Cloning `State` handles**: `State<T>` itself can be cloned (`count.clone()`). This creates a new `Arc` reference to the *same* underlying state. This is essential when moving `State` into closures or child components.
* **`Send + Sync`**: The type `T` held by `State<T>` must implement `Send` and `Sync` for thread-safe access.
* **Reactivity**: `use_state` makes state reactive. When the value changes, any `use_effect` or `rsx!` dynamic scope (`%state @if...` or `%state @for...`) that declared this `State` as a dependency will be re-evaluated.
## `use_effect`: Performing Side Effects
The `use_effect` hook allows you to perform side effects (e.g., logging, network requests, setting up event listeners) in response to state changes or component mounting.
### Basic Usage
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn EffectExample(cx: &Arc<Context>) -> View {
let count = use_state(0);
let message = use_state("Initial message".to_string());
// Effect 1: Logs when `count` changes
use_effect(
{
let count = count.clone(); // Clone for the closure
move || {
println!("Effect 1: Count changed to {}", count.get_dl());
}
},
&[&count], // Dependencies: &[&dyn HookDependency]
);
// Effect 2: Logs when `message` changes (and also on initial render)
use_effect(
{
let message = message.clone();
move || {
println!("Effect 2: Message is now '{}'", message.get_dl());
}
},
&[&message], // Dependencies: &[&dyn HookDependency]
);
// Simulate state changes (e.g., from user input or timers)
let _ = {
let count = count.clone();
let message = message.clone();
std::thread::spawn(move || {
sleep(500); // Wait 0.5 seconds
*count.get() += 1; // Triggers Effect 1
sleep(500);
message.set("Updated message!".to_string()); // Triggers Effect 2
sleep(500);
*count.get() += 1; // Triggers Effect 1 again
});
};
rsx! {
format!("Count: {}", count.get_dl())
format!("Message: {}", message.get_dl())
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(EffectExample {}).expect("Failed to run EffectExample app");
}
```
### `use_effect` Parameters:
1. **`f: F`**: A closure (`FnMut() + Send + Sync + 'static`) that represents the side effect. This closure will be executed when any of its dependencies change. The closure is run in a separate spawned thread to avoid blocking the main rendering loop.
2. **`dependencies: &[&dyn HookDependency]`**: A slice of references to objects that implement the `HookDependency` trait. These are typically `State<T>` instances. The effect closure will be called whenever any of these dependencies notify an update.
### Understanding Dependencies:
* **Empty Dependency List (`&[]`)**: If you pass an empty slice (`&[]`), the effect will only run once when the component is first mounted. This is useful for setup logic like initializing global resources or subscriptions.
* **Specific Dependencies**: When you list `State` objects as dependencies, the effect will run:
* Once, immediately when `use_effect` is called (during the initial component render).
* Again, whenever any of the listed `State` objects trigger an update.
* **`HookDependency` Trait**: This trait defines how an object can register an `HookEffect` to be called upon update. `State<T>` and `Mount` both implement this trait, making them usable as dependencies.
## Lifecycle Hooks: `use_mount` and `use_mount_manual`
These hooks are special cases of `use_effect` for managing actions tied to a component's "mounting" lifecycle.
* **`use_mount()`**: Returns a `Mount` instance that automatically calls `mount()` immediately upon its creation. Effects registered with this `Mount` instance will run once when the component is first rendered.
* **`use_mount_manual()`**: Returns a `Mount` instance that starts in an unmounted state. Effects registered with it will *only* run when you explicitly call `mount_instance.mount()`. This is useful for controlling the mount event from specific `rsx!` nodes (`!mount_instance`) or from other logic.
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn LifecycleExample(cx: &Arc<Context>) -> View {
let mount_hook = use_mount(); // Automatically mounted
let manual_mount_hook = use_mount_manual(); // Manual mount needed
use_effect(
move || {
println!("Component mounted (automatic hook)!");
},
&[&mount_hook],
);
use_effect(
move || {
println!("Component manually mounted!");
},
&[&manual_mount_hook],
);
rsx! {
"Lifecycle example"
// This will trigger the `manual_mount_hook`'s effects
!manual_mount_hook
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(LifecycleExample {}).expect("Failed to run LifecycleExample app");
}
```
This guide has laid the foundation for managing state and side effects in your OSUI applications. By mastering `use_state` and `use_effect`, you can build sophisticated and responsive TUI experiences.
**Next:** Learn how to handle user interactions and other events with OSUI's [Event Handling system](./03-event-handling.md).
@@ -0,0 +1,284 @@
---
sidebar_position: 3
title: Event Handling
---
# Event Handling
OSUI provides a flexible and type-safe event system that allows components to react to various occurrences, such as user input, internal state changes, or custom application-specific events. This guide explains how to define, emit, and listen for events using `on_event`, `emit_event`, and `emit_event_threaded`.
## Event Propagation Model
In OSUI, events are propagated downwards through the component tree. When an event is emitted from a `Context`, it first triggers all registered handlers on that `Context`, and then recursively calls `emit_event` on all its child `Context`s.
## Defining Custom Events
Any Rust type that implements `Send + Sync + Any + 'static` can be used as an event. Typically, you'll define custom `struct`s or `enum`s for your events to carry specific data.
```rust
// Define a custom event
#[derive(Debug, Clone)]
pub struct ButtonClickEvent {
pub button_id: usize,
pub timestamp: std::time::Instant,
}
// Another custom event
#[derive(Debug, Clone)]
pub enum CustomAppEvent {
Tick,
ReloadData,
}
```
## Listening for Events: `cx.on_event()`
Components can register event handlers using `cx.on_event()` to respond to specific event types.
```rust
use osui::prelude::*;
use std::sync::Arc;
use std::time::Instant;
// Custom event definition
#[derive(Debug, Clone)]
pub struct MyCustomEvent {
pub value: String,
}
#[component]
fn EventListener(cx: &Arc<Context>) -> View {
let received_events = use_state(Vec::<String>::new());
// Register an event handler for `MyCustomEvent`
cx.on_event({
let received_events = received_events.clone(); // Clone State for the closure
move |_ctx, event: &MyCustomEvent| {
// This closure runs when MyCustomEvent is emitted
let mut events_guard = received_events.get();
events_guard.push(format!("Received: {}", event.value));
// No need to call `update()` manually, `Inner` guard handles it on drop
}
});
rsx! {
"Event Listener Component"
@for msg in received_events.get_dl() {
format!("- {}", msg)
}
}.view(&cx)
}
```
* `cx.on_event<T: Any + 'static, F: Fn(&Arc<Self>, &T) + Send + Sync + 'static>(self: &Arc<Self>, handler: F)`:
* `T`: The type of event you want to listen for (e.g., `MyCustomEvent`).
* `F`: A closure that takes `&Arc<Context>` (the component's context) and `&T` (a reference to the event data).
* You pass a closure that captures the necessary state (`received_events` in this case) and logic to execute when the event fires.
## Emitting Events: `cx.emit_event()` and `cx.emit_event_threaded()`
Components or other parts of your application can send events using `cx.emit_event()` or `cx.emit_event_threaded()`.
### `cx.emit_event()` (Synchronous)
`emit_event` processes event handlers sequentially in the current thread.
```rust
// ... (MyCustomEvent and EventListener component definitions from above)
#[component]
fn EventSender(cx: &Arc<Context>) -> View {
let button_clicks = use_state(0);
// Simulate an action that emits an event
let emit_click_event = {
let cx = cx.clone(); // Clone Context for the closure
let button_clicks = button_clicks.clone();
move || {
let current_clicks = *button_clicks.get();
*button_clicks.get() += 1;
let event = MyCustomEvent {
value: format!("Button clicked {} times", current_clicks + 1),
};
cx.emit_event(event); // Emit the event
}
};
// Simulate clicking the button every second
use_effect(
{
let emit_click_event = emit_click_event.clone();
move || {
loop {
sleep(1000); // Wait 1 second
emit_click_event();
}
}
},
&[], // No dependencies, run once on mount
);
rsx! {
format!("Button clicks: {}", button_clicks.get_dl())
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
// EventSender and EventListener are siblings in the tree.
// Events emitted by EventSender will propagate down to EventListener.
EventSender {}
EventListener {}
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run App");
}
```
### `cx.emit_event_threaded()` (Asynchronous)
`emit_event_threaded` spawns a new thread for each registered event handler. This is useful for long-running or potentially blocking event handlers, preventing them from freezing your UI.
```rust
// ... (MyCustomEvent, EventListener definitions)
#[component]
fn ThreadedEventSender(cx: &Arc<Context>) -> View {
let cx_clone = cx.clone();
// Emit an event using `emit_event_threaded` on mount
use_effect(
move || {
println!("Emitting threaded event on mount!");
let event = MyCustomEvent {
value: "Threaded mount event".to_string(),
};
cx_clone.emit_event_threaded(&event); // Notice the `&event` for threaded
},
&[], // Run once on mount
);
rsx! {
"Threaded Event Sender"
}.view(&cx)
}
#[component]
fn AppWithThreaded(cx: &Arc<Context>) -> View {
rsx! {
ThreadedEventSender {}
EventListener {} // This listener will receive the threaded event
}.view(&cx)
}
// To run: engine.run(AppWithThreaded {})
```
* `cx.emit_event_threaded<E: Any + Send + Sync + Clone + 'static>(self: &Arc<Self>, event: &E)`:
* Takes `&E` (a reference to the event), which must implement `Clone` because each spawned thread receives a clone of the event data.
* Each handler for `E` will be executed in its own `std::thread::spawn`.
## Realistic Usage Scenario: Interactive Counter
Let's create a more interactive counter that responds to keyboard input.
```rust
use osui::prelude::*;
use std::sync::Arc;
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
// Define a custom event for counter actions
#[derive(Debug, Clone)]
pub enum CounterAction {
Increment,
Decrement,
}
#[component]
fn InteractiveCounter(cx: &Arc<Context>) -> View {
let count = use_state(0);
// Listen for CounterAction events
cx.on_event({
let count = count.clone();
move |_ctx, action: &CounterAction| {
let mut count_guard = count.get();
match action {
CounterAction::Increment => *count_guard += 1,
CounterAction::Decrement => *count_guard -= 1,
}
}
});
rsx! {
"Press 'q' to quit, '+' to increment, '-' to decrement."
format!("Current Count: {}", count.get_dl())
}.view(&cx)
}
// A component (or `main` function logic) to poll keyboard input
// and emit CounterAction events.
#[component]
fn KeyboardInputHandler(cx: &Arc<Context>) -> View {
// This effect runs once on mount to start the keyboard polling thread
use_effect(
{
let cx = cx.clone();
move || {
let _ = crossterm::terminal::enable_raw_mode();
loop {
if event::poll(std::time::Duration::from_millis(50)).unwrap() {
if let Event::Key(key_event) = event::read().unwrap() {
if key_event.kind == KeyEventKind::Press {
match key_event.code {
KeyCode::Char('+') => cx.emit_event(CounterAction::Increment),
KeyCode::Char('-') => cx.emit_event(CounterAction::Decrement),
KeyCode::Char('q') => {
let _ = crossterm::terminal::disable_raw_mode();
cx.stop().expect("Failed to stop engine");
break;
},
_ => {}
}
}
}
}
}
}
},
&[], // Empty deps: run once on mount
);
// This component doesn't render anything visible itself.
// Its purpose is purely to handle input and emit events.
rsx! { "" }.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(AppWithKeyboardInput {}).expect("Failed to run interactive app");
let _ = crossterm::terminal::disable_raw_mode(); // Ensure raw mode is disabled on exit
}
#[component]
fn AppWithKeyboardInput(cx: &Arc<Context>) -> View {
rsx! {
KeyboardInputHandler {} // Handles input and emits events
InteractiveCounter {} // Listens for events and updates UI
}.view(&cx)
}
```
In this example:
* `KeyboardInputHandler` runs in a separate thread (due to `use_effect`'s spawning behavior).
* It polls for keyboard events using `crossterm`.
* Upon detecting `+`, `-`, or `q`, it emits a `CounterAction` or `Stop` command to its `Context`.
* `InteractiveCounter` listens for `CounterAction` events and updates its internal `count` state, which then causes it to re-render.
* The `stop()` command, emitted by `KeyboardInputHandler`, instructs the `Console` engine to terminate its rendering loop.
This showcases a full interaction loop using custom events for communication between components.
**Next:** Explore specialized lifecycle management with `use_mount` and `use_mount_manual` in [Lifecycle Hooks](./04-lifecycle-hooks.md).
@@ -0,0 +1,149 @@
---
sidebar_position: 4
title: Lifecycle Hooks
---
# Lifecycle Hooks
In OSUI, components have a lifecycle, meaning they go through various stages from creation to destruction. While OSUI doesn't expose a full set of lifecycle methods like some frameworks, it provides powerful hooks for managing effects related to a component's "mounting" phase: `use_mount` and `use_mount_manual`. These are special applications of the `use_effect` hook, designed for setup logic.
## The "Mounted" Concept
A component is considered "mounted" when it has been rendered for the first time and is part of the active component tree. Lifecycle hooks allow you to run code precisely at this point.
## `use_mount()`: Automatic Mounting
The `use_mount()` hook provides a `Mount` instance that is automatically marked as "mounted" upon its creation. Any `use_effect` that depends on this `Mount` instance will execute its effect closure immediately when the component renders.
### When to use `use_mount()`:
* **Initial Setup**: Performing actions that should only happen once when the component first appears on screen, such as fetching initial data, setting up global event listeners, or initializing complex external resources.
* **No Cleanup Required**: For effects that don't require any cleanup logic. If cleanup is needed, ensure your effect closure handles it or consider using `use_effect` with an empty dependency array (which is similar in behavior for initial execution, but offers more control over subsequent runs if dependencies are added).
### Example: Initial Data Fetch
```rust
use osui::prelude::*;
use std::sync::Arc;
use std::time::Duration;
// Simulate a data fetching operation
async fn fetch_data_async() -> String {
// In a real application, this would be an actual network request or disk read.
tokio::time::sleep(Duration::from_secs(1)).await; // Simulate network delay
"Data loaded successfully!".to_string()
}
#[component]
fn DataLoader(cx: &Arc<Context>) -> View {
let data_state = use_state("Loading data...".to_string());
let mount_hook = use_mount(); // Automatically initializes as "mounted"
// Use `use_effect` with `mount_hook` as a dependency
use_effect(
{
let data_state = data_state.clone();
move || {
// This effect runs once when `mount_hook` is initialized (component mounts)
println!("DataLoader: Component mounted, starting data fetch...");
// Spawn a new async task (requires a runtime like tokio)
tokio::spawn(async move {
let data = fetch_data_async().await;
data_state.set(data); // Update state, triggering re-render
println!("DataLoader: Data fetch complete.");
});
}
},
&[&mount_hook], // Effect runs when `mount_hook` updates (i.e., on mount)
);
rsx! {
format!("Status: {}", data_state.get_dl())
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
DataLoader {}
}.view(&cx)
}
pub fn main() {
// For async operations, you need a runtime like tokio.
// Add `tokio = { version = "1", features = ["full"] }` to your Cargo.toml
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let engine = Console::new();
engine.run(App {}).expect("Failed to run async app");
});
}
```
## `use_mount_manual()`: Explicit Mounting
The `use_mount_manual()` hook provides a `Mount` instance that starts in an "unmounted" state. Its associated `use_effect` callbacks will *not* run until you explicitly call the `mount()` method on that specific `Mount` instance. This offers finer control over when initial setup logic executes.
### When to use `use_mount_manual()`:
* **Conditional Mounting**: When you want to delay the "mount" logic until a specific condition is met or an interaction occurs.
* **`rsx!`-driven Mounting**: You can trigger the `mount()` method directly from your `rsx!` template using the `!mount_hook_instance` syntax. This is useful if the mount event is tied to the presence of a specific element or a complex rendering path.
### Example: Delayed Mount with `rsx!`
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn DelayedSetup(cx: &Arc<Context>) -> View {
let setup_status = use_state("Waiting for manual mount...".to_string());
let manual_mount_hook = use_mount_manual(); // Starts unmounted
use_effect(
{
let setup_status = setup_status.clone();
move || {
// This effect will only run when `manual_mount_hook.mount()` is called.
println!("DelayedSetup: Manual mount triggered, performing setup!");
setup_status.set("Setup complete!".to_string());
}
},
&[&manual_mount_hook], // Depends on the manual mount hook
);
rsx! {
format!("Status: {}", setup_status.get_dl())
// The `!manual_mount_hook` in RSX triggers `manual_mount_hook.mount()`
// which then causes the `use_effect` to run.
!manual_mount_hook
"This component is now mounted."
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
DelayedSetup {}
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run delayed mount app");
}
```
In this example, the "Setup complete!" message will appear only after the `!manual_mount_hook` line is processed by the `rsx!` renderer, explicitly calling `mount_hook.mount()`.
## Summary of Lifecycle Hooks
* `use_mount()`: For effects that should run once immediately when the component is initially rendered.
* `use_mount_manual()`: For effects that you want to explicitly control when they run, either through programmatic calls to `.mount()` or via the `!mount_hook_instance` syntax in `rsx!`.
These hooks, combined with `use_effect`, give you fine-grained control over when side effects are performed during your component's lifetime.
**Next:** Understand how to synchronize component state with events for sophisticated data flow patterns in [Data Flow and Sync](./05-data-flow-and-sync.md).
@@ -0,0 +1,215 @@
---
sidebar_position: 5
title: Data Flow and Sync
---
# Data Flow and Synchronization
OSUI's reactive state system and event handling capabilities provide a robust foundation for managing data flow within your application. The `use_sync_state` and `use_sync_effect` hooks offer powerful patterns for synchronizing component state with external events and vice versa, enabling sophisticated inter-component communication and data management.
## `use_sync_state`: State from Events
`use_sync_state` allows a component's internal `State` to be automatically updated whenever a specific type of event is emitted to its `Context`. This is a powerful way to inject external data or changes into a component's reactive state.
### How it works:
It combines `use_state` and `cx.on_event`.
1. You provide an initial value for the state.
2. You specify the event type (`E`) and a `decoder` function.
3. The `decoder` function takes an `&E` event and returns a new value `T` for the state.
4. Whenever an event of type `E` is emitted to the current `Context`, the `decoder` runs, and the internal `State<T>` is updated.
```rust
use osui::prelude::*;
use std::sync::Arc;
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
// Define a simple event to change a message
#[derive(Debug, Clone)]
pub struct MessageChangeEvent(pub String);
#[component]
fn MessageDisplay(cx: &Arc<Context>) -> View {
// Use `use_sync_state` to update the message based on `MessageChangeEvent`
let message = use_sync_state(
cx,
"Initial Message".to_string(), // Initial state value
|event: &MessageChangeEvent| event.0.clone(), // Decoder: extract string from event
);
rsx! {
"Received Message:"
format!(" {}", message.get_dl())
}.view(&cx)
}
#[component]
fn MessageInput(cx: &Arc<Context>) -> View {
// Simulate input by reacting to key presses and emitting MessageChangeEvent
use_effect(
{
let cx = cx.clone();
move || {
let _ = crossterm::terminal::enable_raw_mode();
loop {
if event::poll(std::time::Duration::from_millis(50)).unwrap() {
if let Event::Key(key_event) = event::read().unwrap() {
if key_event.kind == KeyEventKind::Press {
match key_event.code {
KeyCode::Char(c) => {
let msg = format!("Typed: {}", c);
cx.emit_event(MessageChangeEvent(msg));
},
KeyCode::Enter => {
cx.emit_event(MessageChangeEvent("Enter pressed!".to_string()));
},
KeyCode::Esc => {
let _ = crossterm::terminal::disable_raw_mode();
cx.stop().expect("Failed to stop engine");
break;
},
_ => {}
}
}
}
}
}
}
},
&[], // Run once on mount
);
rsx! {
"Type something to change the message (Esc to quit):"
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
MessageInput {} // Emits MessageChangeEvent
MessageDisplay {} // Synchronizes its state with MessageChangeEvent
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run app");
let _ = crossterm::terminal::disable_raw_mode();
}
```
In this example, `MessageInput` emits `MessageChangeEvent`s based on keyboard input. `MessageDisplay` automatically updates its `message` state whenever it receives one of these events from its parent `Context`, thanks to `use_sync_state`.
## `use_sync_effect`: Events from State
`use_sync_effect` allows changes in a component's internal `State` to automatically trigger the emission of a specific type of event to its `Context`. This is useful for communicating state changes upwards or to sibling components.
### How it works:
It combines `use_effect` and `cx.emit_event`.
1. You provide an `State<T>` instance you want to monitor.
2. You specify an `encoder` function and optional dependencies.
3. The `encoder` function takes a `&State<T>` and returns an event `Ev`.
4. Whenever the `State<T>` changes (or any specified dependencies), the `encoder` runs, and the generated event `Ev` is emitted to the current `Context`.
```rust
use osui::prelude::*;
use std::sync::Arc;
use std::collections::HashMap;
// Event to signal a counter has changed
#[derive(Debug, Clone)]
pub struct CounterUpdatedEvent {
pub id: usize,
pub new_value: i32,
}
#[component]
fn ChildCounter(cx: &Arc<Context>, id: &usize, initial_value: &i32) -> View {
let count = use_state(*initial_value);
// Use `use_sync_effect` to emit `CounterUpdatedEvent` when `count` changes
use_sync_effect(
cx,
&count, // Monitor this state
move |state_ref: &State<i32>| {
// Encoder: create an event from the state
CounterUpdatedEvent {
id: *id,
new_value: state_ref.get_dl(),
}
},
&[&count], // Effect runs when `count` changes
);
// Simulate incrementing the counter periodically
use_effect(
{
let count = count.clone();
move || {
loop {
sleep(1000); // Increment every second
*count.get() += 1;
}
}
},
&[], // Run once on mount
);
rsx! {
format!("Counter {}: {}", id, count.get_dl())
}.view(&cx)
}
#[component]
fn ParentDashboard(cx: &Arc<Context>) -> View {
let all_counts = use_state(HashMap::<usize, i32>::new());
// Listen for `CounterUpdatedEvent` from children
cx.on_event({
let all_counts = all_counts.clone();
move |_ctx, event: &CounterUpdatedEvent| {
let mut counts_guard = all_counts.get();
counts_guard.insert(event.id, event.new_value);
}
});
rsx! {
"Dashboard Overview:"
@for (id, value) in all_counts.get_dl() {
format!(" Counter {}: {}", id, value)
}
"---"
ChildCounter { id: 1, initial_value: 0 }
ChildCounter { id: 2, initial_value: 10 }
}.view(&cx)
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
ParentDashboard {}
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run app");
}
```
In this example:
* `ChildCounter` uses `use_sync_effect` to emit a `CounterUpdatedEvent` every time its internal `count` state changes.
* `ParentDashboard` listens for these `CounterUpdatedEvent`s (which bubble up from its children) using `cx.on_event` and updates its own `all_counts` `HashMap` state. This `HashMap` then drives the display in the dashboard.
## When to use `use_sync_state` and `use_sync_effect`:
* **Inter-component Communication**: When components need to communicate beyond simple prop passing. Events are excellent for sibling-to-sibling or child-to-ancestor communication without prop drilling.
* **Centralized State Management**: You can have a central "store" component that emits events, and other components `use_sync_state` to react to those events.
* **External System Integration**: When your TUI needs to react to external system events (e.g., file changes, network updates) by mapping them to internal `State`.
* **Decoupling**: They help decouple components, as they don't need direct references to each other, only awareness of event types.
By combining `use_state`, `use_effect`, `use_sync_state`, and `use_sync_effect` with OSUI's event system, you can build powerful and maintainable data flow architectures for your TUI applications.
**Next:** Learn how to arrange your components visually using OSUI's rendering primitives in [Building Complex Layouts](./06-building-complex-layouts.md).
@@ -0,0 +1,153 @@
---
sidebar_position: 6
title: Building Complex Layouts
---
# Building Complex Layouts
OSUI provides foundational primitives for rendering text and components, allowing you to compose them into complex layouts. While OSUI doesn't include a built-in layout engine (like flexbox or grid), it exposes the `DrawContext` and geometric types (`Point`, `Area`, `Size`) that enable you to manually position elements or build your own layout components.
## The Rendering Pipeline
At its core, OSUI's rendering works by accumulating `DrawInstruction`s into a `DrawContext`. The engine then takes this `DrawContext` and executes the instructions to draw to the terminal.
1. **`View`**: A component returns a `View`, which is essentially a closure that takes a mutable `DrawContext` and adds drawing instructions to it.
2. **`DrawContext`**: This is the canvas for your component. It has an `area` (the total space available to the current component) and an `allocated` area (the space currently used by drawing instructions within that component).
3. **`DrawInstruction`**: The actual commands to draw, like `Text`, `View` (for child components), or `Child` (for nested `DrawContext`s).
## Core Rendering Primitives
These types, found in the `osui::render` module, are essential for manual layout.
* ### `Point`
Represents a position `(x, y)` in terminal coordinates. `x` is column, `y` is row.
```rust
pub struct Point {
pub x: u16,
pub y: u16,
}
```
* ### `Size`
Represents `width` and `height` in terminal columns and rows.
```rust
pub struct Size {
pub width: u16,
pub height: u16,
}
```
* ### `Area`
Combines `Point` and `Size` to define a rectangular region.
```rust
pub struct Area {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
```
## `DrawContext`: Your Drawing Canvas
Inside a `View` closure, you receive a mutable `DrawContext`. This is how you interact with the rendering system.
```rust
use osui::prelude::*;
use std::sync::Arc;
#[component]
fn MyCustomLayout(cx: &Arc<Context>, children: &Rsx) -> View {
let children_view = children.view(&cx); // Convert Rsx children to a View
Arc::new(move |ctx: &mut DrawContext| {
// `ctx.area` gives you the total available space for this component.
let available_width = ctx.area.width;
let available_height = ctx.area.height;
// --- Manual layout example: Two columns ---
// Allocate space for the left column
let left_col_area = ctx.allocate(
ctx.area.x,
ctx.area.y,
available_width / 2,
available_height,
);
// Draw some text in the left column
ctx.draw_text(
Point { x: left_col_area.x + 1, y: left_col_area.y + 1 },
"Left Panel",
);
ctx.draw_text(
Point { x: left_col_area.x + 1, y: left_col_area.y + 2 },
&format!("Available: {}x{}", left_col_area.width, left_col_area.height),
);
// Allocate space for the right column
let right_col_area = ctx.allocate(
ctx.area.x + available_width / 2, // Start x at half width
ctx.area.y,
available_width / 2,
available_height,
);
// Draw the children (passed to MyCustomLayout) into the right column
// This effectively "moves" the children's rendering into this specific area.
ctx.draw_view(
right_col_area, // Children will render relative to this new area
children_view.clone(),
);
// Draw more text in the right column
ctx.draw_text(
Point { x: right_col_area.x + 1, y: right_col_area.y + 1 },
"Right Panel (Children Area)",
);
})
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
MyCustomLayout {
rsx! {
"Hello from the child content!"
"This text should appear in the right panel."
}
}
}.view(&cx)
}
pub fn main() {
let engine = Console::new();
engine.run(App {}).expect("Failed to run app");
}
```
### Key `DrawContext` Methods:
* **`ctx.area`**: The `Area` that the *current component* has been allocated by its parent. All drawing coordinates are relative to `ctx.area.x` and `ctx.area.y`.
* **`ctx.allocate(x, y, width, height)`**: This method marks a region within `ctx.area` as "used". It takes coordinates relative to `ctx.area`'s top-left corner (0,0 of its own space) and returns a new `Area` representing the allocated sub-region. It also updates `ctx.allocated` to be the union of all allocations so far within this `DrawContext`.
* **`ctx.draw_text(point, text)`**: Adds a `Text` instruction. `point` is relative to `ctx.area`.
* **`ctx.draw_view(area, view)`**: Adds a `View` instruction. This is how you tell the renderer to draw a child component (or another `View`) within a specific `area`. The `area` here is also relative to `ctx.area`. The child view will then receive this `area` as its own `ctx.area`.
### Building a Simple Layout Component
The `MyCustomLayout` component above demonstrates a basic two-column layout. You can create more sophisticated layout components by:
1. **Calculating Sub-Regions**: Based on `ctx.area.width` and `ctx.area.height`, divide the space into logical sub-regions (e.g., header, footer, sidebar, main content).
2. **Allocating Space**: Use `ctx.allocate()` to define these sub-regions.
3. **Drawing Content**:
* For static text or background elements, use `ctx.draw_text()`.
* For child components, call `child_rsx.view(&cx)` to get their `View`, and then use `ctx.draw_view(sub_area, child_view)` to render them in their designated space.
### Tips for Layouts:
* **Relative Positioning**: Always think of `Point` and `Area` coordinates as being *relative* to the `ctx.area` of the current `View` being rendered. The `Console` engine handles translating these relative coordinates to absolute terminal coordinates.
* **No Overlapping**: Be mindful of overlapping areas. If you draw two things to the same `Point`, the last one drawn will overwrite the first. OSUI does not automatically manage Z-ordering.
* **Responsive Design**: Consider how your layouts will adapt to different terminal sizes. You can use `ctx.area.width` and `ctx.area.height` to make calculations dynamic.
* **Composition**: Layout components can themselves be children of other layout components, allowing you to build complex nested structures.
While implementing a full layout system like CSS Flexbox is beyond the scope of OSUI's core, these primitives empower you to craft highly customized and visually rich terminal interfaces by manually managing space and component placement.
@@ -0,0 +1,50 @@
---
sidebar_position: 0
title: Crate Structure
---
# OSUI Crate Structure
The `osui` library is organized into several modules, each responsible for a distinct aspect of TUI development. Understanding this structure helps in navigating the codebase and locating relevant functionalities.
## Top-Level Modules
The `osui` crate is composed of the following main modules:
* **`component`**: Defines the core component system, including `Context` (for component state and lifecycle), `Scope` (for child management), and the `ComponentImpl` trait.
* **`engine`**: Provides the rendering engine trait (`Engine`), command execution system (`CommandExecutor`), and concrete engine implementations like `Console` (for `crossterm`) and `Benchmark`.
* **`frontend`**: Implements the RSX (React-like Syntax) system, including the `Rsx` struct and `ToRsx` trait, which bridges the `rsx!` macro output to the rendering pipeline.
* **`render`**: Contains low-level rendering primitives such as `DrawContext`, `DrawInstruction`, `Point`, `Area`, and `Size`. This module defines *what* gets drawn.
* **`state`**: Offers React-like hooks for managing component state (`use_state`), side effects (`use_effect`), and component lifecycle (`use_mount`, `use_mount_manual`).
## `prelude` Module
The `osui::prelude` module re-exports the most commonly used items from all sub-modules. It's recommended to `use osui::prelude::*;` in your application to easily access essential types and macros without verbose imports.
```rust
pub mod prelude {
pub use crate::component::{context::*, scope::*, *};
pub use crate::engine::*;
pub use crate::frontend::*;
pub use crate::render::*;
pub use crate::state::*;
pub use crate::{sleep, Error, Result, View, ViewWrapper};
pub use crossterm;
pub use osui_macros::{component, rsx};
pub use std::sync::{Arc, Mutex};
}
```
## OSUI Core Types
Beyond the modules, `lib.rs` also defines some fundamental type aliases and error handling:
* **`View`**: `Arc<dyn Fn(&mut DrawContext) + Send + Sync>`. Represents a renderable unit, essentially a closure that takes a `DrawContext` and adds drawing instructions.
* **`ViewWrapper`**: `Arc<dyn Fn(&mut DrawContext, View) + Send + Sync>`. A higher-order view that can wrap and modify how another `View` is rendered (e.g., for applying layout or styling).
* **`Result<T>`**: `std::result::Result<T, Error>`. The standard result type for OSUI operations.
* **`Error`**: An enum defining OSUI-specific errors, currently including `PoisonError` for mutex poisoning.
* **`sleep(delay_ms: u64)`**: A utility function for pausing execution for a specified duration.
This structured approach helps keep the library organized and maintainable, allowing developers to quickly understand where to find the tools they need.
**Next:** Dive into the details of the [Component API](./component-api.md).
@@ -0,0 +1,117 @@
---
sidebar_position: 1
title: Component API
---
# Component Module API Reference
The `component` module is the heart of OSUI's UI system, defining how reusable UI units are structured, manage their state, and interact.
## `ComponentImpl` Trait
```rust
pub trait ComponentImpl: Send + Sync {
fn call(&self, cx: &Arc<Context>) -> View;
}
```
The `ComponentImpl` trait is implemented by all types that can act as an OSUI component.
* **`call(&self, cx: &Arc<Context>) -> View`**: The core method that renders the component. It takes a reference to the component itself (`self`) and the component's `Context` (`cx`), and returns a `View` which contains the drawing instructions.
**Implementations:**
* **`View`**: A bare `View` (an `Arc<dyn Fn(&mut DrawContext) + Send + Sync>`) can itself be a `ComponentImpl`, simply returning itself.
* **`Fn(&Arc<Context>) -> View`**: Any closure with this signature can also be a `ComponentImpl`.
* **`#[component]` macro**: The `#[component]` macro automatically generates a struct and implements `ComponentImpl` for it, wrapping your component function.
## `Component` Type Alias
```rust
pub type Component = Arc<dyn ComponentImpl>;
```
A convenience type alias for a `ComponentImpl` wrapped in an `Arc`, allowing for shared, thread-safe ownership.
## `EventHandler` Type Alias
```rust
pub type EventHandler = Arc<Mutex<dyn FnMut(&Arc<Context>, &dyn Any) + Send + Sync>>;
```
A type alias for a mutex-protected, thread-safe, mutable closure that handles events. Event handlers receive the component's `Context` and a reference to the event data (as `&dyn Any`).
## `context` Module
Contains the `Context` struct, which is central to each component instance.
### `Context` Struct
```rust
pub struct Context {
component: AccessCell<Component>,
view: AccessCell<View>,
event_handlers: AccessCell<HashMap<TypeId, Vec<EventHandler>>>,
pub(crate) scopes: Mutex<Vec<Arc<Scope>>>,
executor: Arc<dyn CommandExecutor>,
}
```
The `Context` holds the runtime state and behavior for a specific component instance. It manages the component's `View`, its registered event handlers, and its child `Scope`s.
#### Methods:
* **`fn new<F: ComponentImpl + 'static>(component: F, executor: Arc<dyn CommandExecutor>) -> Arc<Self>`**
* Creates a new `Arc` wrapped `Context` for the given component and command executor.
* **`fn refresh(self: &Arc<Self>)`**
* Re-renders the component by clearing existing event handlers and calling the component's `call` method to produce a new `View`. This is typically called automatically by the engine or `Rsx`.
* **`fn refresh_sync(self: &Arc<Self>)`**
* Synchronously re-renders the component, blocking until the view closure has finished executing.
* **`fn get_view(self: &Arc<Self>) -> View`**
* Returns a clone of the component's current `View`.
* **`fn on_event<T: Any + 'static, F: Fn(&Arc<Self>, &T) + Send + Sync + 'static>(self: &Arc<Self>, handler: F)`**
* Registers an event handler `F` for events of type `T`. When an event of type `T` is `emit`ted to this `Context`, the handler `F` will be invoked.
* **`fn emit_event<E: Send + Sync + Any + 'static>(self: &Arc<Self>, event: E)`**
* Emits an event `E`. All registered handlers for type `E` on this `Context` are called synchronously, and then the event is propagated to all child components.
* **`fn emit_event_threaded<E: Any + Send + Sync + Clone + 'static>(self: &Arc<Self>, event: &E)`**
* Emits an event `E`. Each registered handler for type `E` on this `Context` is called in a *newly spawned thread*. The event is then propagated to all child components (also using `emit_event_threaded`). Requires `E` to be `Clone`.
* **`fn scope(self: &Arc<Self>) -> Arc<Scope>`**
* Creates a new, empty child `Scope` and adds it to this `Context`. Returns the new `Scope`.
* **`fn dyn_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(self: &Arc<Self>, drawer: F, dependencies: &[&dyn HookDependency]) -> Arc<Scope>`**
* Creates a new *dynamic* child `Scope` that re-renders (by calling `drawer`) whenever any of its `dependencies` change. `drawer` is also called immediately. Returns the new `Scope`.
* **`fn add_scope(self: &Arc<Self>, scope: Arc<Scope>)`**
* Adds an already constructed `Scope` as a child to this `Context`.
* **`fn draw_children(self: &Arc<Self>, ctx: &mut DrawContext)`**
* Iterates through all child `Scope`s and their components, rendering them into the provided `DrawContext`. Handles `ViewWrapper`s if present.
* **`fn get_executor(self: &Arc<Self>) -> Arc<dyn CommandExecutor>`**
* Returns a clone of the `CommandExecutor` associated with this `Context`.
* **`fn execute<T: Command + 'static>(self: &Arc<Self>, command: T) -> crate::Result<()>`**
* Executes a given `Command` using the associated `CommandExecutor`.
* **`fn stop(self: &Arc<Self>) -> crate::Result<()>`**
* A convenience method to execute the `Stop` command, terminating the application.
## `scope` Module
Contains the `Scope` struct, which organizes child components within a `Context`.
### `Scope` Struct
```rust
pub struct Scope {
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
executor: Arc<dyn CommandExecutor>,
}
```
A `Scope` groups child components. Each entry in `children` consists of a child `Context` and an optional `ViewWrapper` that can modify its rendering.
#### Methods:
* **`fn new(executor: Arc<dyn CommandExecutor>) -> Arc<Self>`**
* Creates a new `Arc` wrapped `Scope` with the given `CommandExecutor`.
* **`fn child<F: ComponentImpl + 'static>(self: &Arc<Self>, child: F, view_wrapper: Option<ViewWrapper>)`**
* Creates a new `Context` for the provided `child` component, refreshes it, and adds it to this `Scope`'s children, optionally with a `ViewWrapper`.
* **`fn view(self: &Arc<Self>, view: View)`**
* Creates a new `Context` directly from a `View` (without an explicit `ComponentImpl`), refreshes it, and adds it to this `Scope`'s children.
This module forms the backbone of how component trees are constructed and managed in OSUI.
**Next:** Explore the [Engine API](./engine-api.md).
@@ -0,0 +1,163 @@
---
sidebar_position: 2
title: Engine API
---
# Engine Module API Reference
The `engine` module defines the core interfaces for how OSUI applications run, render, and execute commands. It provides abstractions for different rendering backends and includes a default console implementation.
## `Engine` Trait
```rust
pub trait Engine<Output = ()> {
fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<Output>;
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context>;
fn render(&self, cx: &Arc<Context>);
fn render_delay(&self);
fn render_view(&self, area: &Area, view: &View) -> DrawContext;
fn draw_context(&self, ctx: &DrawContext);
fn executor(&self) -> Arc<dyn CommandExecutor>;
}
```
The `Engine` trait is the primary interface for running an OSUI application. It abstracts away the specifics of how components are initialized, rendered, and how the application loop is managed.
#### Associated Types / Generics:
* **`Output`**: A generic type that allows `run` to return different results. By default, it's `()`, but for specialized engines (like `Benchmark`), it can return custom data.
#### Methods:
* **`fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<Output>`**
* The main entry point to start the OSUI application loop. It takes the root component `C`, initializes it, and continuously renders until a stop command is issued. Returns `Ok(())` by default, or a custom `Output` for specialized engines.
* **`fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context>`**
* Initializes the rendering environment and creates the root `Context` for the application's top-level component. This is typically called by `run`.
* **`fn render(&self, cx: &Arc<Context>)`**
* Performs a full render cycle for the given `Context`. This usually involves clearing the screen, calling `render_view` for the root, and then `draw_context`.
* **`fn render_delay(&self)`**
* A hook for introducing a delay between render frames. The default implementation calls `crate::sleep(16)` for approximately 60 frames per second.
* **`fn render_view(&self, area: &Area, view: &View) -> DrawContext`**
* Takes a `View` and the `Area` it should render within, executing the `View`'s closure to produce a `DrawContext` filled with `DrawInstruction`s.
* **`fn draw_context(&self, ctx: &DrawContext)`**
* Executes the drawing instructions contained within a `DrawContext` to actually draw content to the rendering target (e.g., the terminal).
* **`fn executor(&self) -> Arc<dyn CommandExecutor>`**
* Returns the `CommandExecutor` instance used by this engine.
## `Command` Trait
```rust
pub trait Command {
fn as_any(&self) -> &dyn Any;
}
```
The `Command` trait is implemented by types that represent actions or instructions that can be executed by the `CommandExecutor`. This trait enables a type-safe way to send commands between components and the engine.
* **`fn as_any(&self) -> &dyn Any`**: Allows downcasting the command to its concrete type for pattern matching and execution.
## `CommandExecutor` Trait
```rust
pub trait CommandExecutor: Send + Sync {
fn execute_command(&self, command: &Arc<dyn Command>) -> crate::Result<()>;
}
```
The `CommandExecutor` trait defines how commands are processed within the OSUI application. Engines provide their own implementations of this trait to handle system-level operations.
* **`fn execute_command(&self, command: &Arc<dyn Command>) -> crate::Result<()>`**: Takes an `Arc` wrapped `Command` and executes it. Implementations typically use `command.as_any().downcast_ref()` to identify and process specific commands.
## `console` Module: `Console` Engine and `ConsoleExecutor`
The `console` module provides OSUI's default, `crossterm`-based rendering engine.
### `Console` Struct
```rust
pub struct Console {
threads: Mutex<Vec<Arc<dyn Fn(Arc<Context>) + Send + Sync>>>,
executor: Arc<ConsoleExecutor>,
}
```
The `Console` struct implements the `Engine` trait, specifically designed to render to a terminal using `crossterm`.
#### Methods:
* **`fn new() -> Self`**: Creates a new `Console` engine.
* **`fn thread<F: Fn(Arc<Context>) + Send + Sync + 'static>(&self, run: F)`**: Registers a closure to be run in a separate thread when the engine initializes. This is useful for background tasks or input polling that needs access to the main `Context`.
#### `Engine` Trait Implementation:
The `Console` implements all methods of the `Engine` trait, handling terminal setup (raw mode, cursor hiding), screen clearing, `crossterm` cursor movements, and text output.
### `ConsoleExecutor` Struct
```rust
pub struct ConsoleExecutor {
running: Mutex<bool>,
}
```
The `ConsoleExecutor` implements the `CommandExecutor` trait for the `Console` engine. It manages the `running` state of the application.
#### Methods:
* **`fn is_running(self: &Arc<ConsoleExecutor>) -> bool`**: Checks if the application is currently running.
* **`fn stop(&self) -> crate::Result<()>`**: Sets the internal `running` flag to `false`, signaling the `Console` engine to terminate its `run` loop.
#### `CommandExecutor` Trait Implementation:
The `ConsoleExecutor` currently supports handling the `commands::Stop` command.
## `commands` Module: Built-in Commands
The `commands` module defines simple, built-in commands for the engine.
### `Stop` Command
```rust
pub struct Stop;
impl Command for Stop {
fn as_any(&self) -> &dyn std::any::Any;
}
```
A basic command used to signal the `Engine` to stop its main loop and exit the application.
## `benchmark` Module: `Benchmark` Engine
The `benchmark` module provides a wrapper engine for performance testing.
### `BenchmarkResult` Struct
```rust
pub struct BenchmarkResult {
pub average: u128,
pub min: u128,
pub max: u128,
pub total_render: u128,
pub total: u128,
}
```
Holds the statistical results of a benchmark run, including average, minimum, maximum, total render time, and total overall time in microseconds.
### `Benchmark<T: Engine>` Struct
```rust
pub struct Benchmark<T: Engine>(T);
```
A wrapper around any other `Engine` that measures its rendering performance.
#### Methods:
* **`fn new(engine: T) -> Self`**: Creates a new `Benchmark` wrapper around an existing `Engine` instance.
#### `Engine<BenchmarkResult>` Trait Implementation:
The `Benchmark` engine implements the `Engine` trait, but its `run` method performs multiple render cycles (e.g., 40 times), measures the duration of each, and returns a `BenchmarkResult` instead of `()`. It delegates all other `Engine` methods to the wrapped engine.
This comprehensive set of traits and implementations allows OSUI to be flexible regarding its rendering backend and extensible with custom command handling.
**Next:** Explore the [Frontend API](./frontend-api.md).
@@ -0,0 +1,77 @@
---
sidebar_position: 3
title: Frontend API
---
# Frontend Module API Reference
The `frontend` module is responsible for bridging the declarative `rsx!` macro syntax to the dynamic component rendering system. It defines how component hierarchies are constructed and managed before being translated into `View`s.
## `ToRsx` Trait
```rust
pub trait ToRsx {
fn to_rsx(&self) -> Rsx;
}
```
The `ToRsx` trait is implemented by any type that can be converted into an `Rsx` object. This is crucial for embedding various types (like strings, numbers, or other `Rsx` instances) directly into the `rsx!` macro output using the `@{expr}` syntax.
**Implementations:**
* **`&Rsx`**: Converts a reference to an `Rsx` into an owned `Rsx` by cloning its internal structure.
* **`T: std::fmt::Display`**: Any type that implements `std::fmt::Display` (e.g., `String`, `&str`, `i32`, `f64`, etc.) automatically implements `ToRsx`. It converts the displayable value into a `Rsx` containing a static scope that draws the text.
## `RsxScope` Enum
```rust
#[derive(Clone)]
pub enum RsxScope {
Static(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>),
Dynamic(
Arc<dyn Fn(&Arc<Scope>) + Send + Sync>,
Vec<Arc<dyn HookDependency>>,
),
Child(Rsx),
}
```
`RsxScope` represents the different kinds of renderable units that can be part of an `Rsx` hierarchy. These scopes dictate how and when their content is processed and updated.
* **`Static(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>)`**:
* Represents content that is processed only once. This is typically used for simple text literals or components that don't depend on reactive state within their `rsx!`.
* The contained closure is executed once to set up children within a new `Scope`.
* **`Dynamic(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>, Vec<Arc<dyn HookDependency>>)`**:
* Represents content that needs to be re-evaluated and potentially re-rendered when certain `dependencies` change. This is used for `rsx!` blocks with `@if` and `@for` that declare dependencies (`%dep`).
* The closure (`drawer`) is executed initially and then whenever any of the `HookDependency` instances in `dependencies` notify an update.
* **`Child(Rsx)`**:
* Represents a nested `Rsx` structure. This is used when an `Rsx` object is embedded directly into another `rsx!` block (e.g., via `@{other_rsx}` or when passing `children` to a component).
## `Rsx` Struct
```rust
#[derive(Clone)]
pub struct Rsx(Vec<RsxScope>);
```
The `Rsx` struct is a collection of `RsxScope`s, representing a declarative UI tree fragment. It's the primary output of the `rsx!` macro.
#### Methods:
* **`fn new() -> Self`**
* Creates a new empty `Rsx` instance.
* **`fn static_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(&mut self, scope: F)`**
* Adds a `Static` `RsxScope` to the collection. The `scope` closure will be executed once to build up the content within a dedicated `Scope`.
* **`fn dynamic_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(&mut self, drawer: F, dependencies: Vec<Arc<dyn HookDependency>>)`**
* Adds a `Dynamic` `RsxScope` to the collection. The `drawer` closure will be executed initially and then on subsequent updates of the specified `dependencies`.
* **`fn child<R: ToRsx>(&mut self, child: R)`**
* Adds a `Child` `RsxScope` to the collection, converting the input `R` (which must implement `ToRsx`) into a nested `Rsx`.
* **`fn generate_children(&self, context: &Arc<Context>)`**
* Processes the internal `Vec<RsxScope>`, converting each scope into actual component `Context`s and `Scope`s within the provided parent `context`. This method recursively builds the component tree.
* **`fn view(&self, context: &Arc<Context>) -> View`**
* The primary method used by components to turn their `rsx!` output into a renderable `View`.
* It first calls `generate_children` to build the component tree within the given `context`.
* It then returns a `View` closure that, when executed, will instruct the `context` to `draw_children`.
The `frontend` module, through `Rsx` and `RsxScope`, provides the declarative interface and the necessary translation layer to OSUI's imperative rendering core.
**Next:** Explore the [Render API](./render-api.md).
@@ -0,0 +1,211 @@
---
sidebar_position: 6
title: Macros API
---
# Macros Module API Reference
The `osui-macros` crate provides the procedural macros that enhance OSUI's ergonomics and enable its declarative UI syntax. These macros transform your Rust code into the necessary OSUI component and rendering structures.
## `#[component]` Attribute Macro
```rust
#[proc_macro_attribute]
pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream { /* ... */ }
```
The `#[component]` attribute macro transforms a standard Rust function into a fully-fledged OSUI component.
#### Purpose:
* **Prop Generation**: It automatically parses the function's parameters (after the initial `cx: &Arc<Context>`) and generates a `struct` with matching fields. These fields become the component's "props".
* **`ComponentImpl` Implementation**: It implements the `ComponentImpl` trait for the generated struct, making it a valid OSUI component that can be rendered. The `call` method of this trait simply invokes your original function.
* **Ergonomics**: Simplifies component definition by allowing you to write components as regular functions with clear parameters, without manually defining structs and `ComponentImpl` boilerplate.
#### Usage:
```rust
use osui::prelude::*; // For Context and View types
use std::sync::Arc;
#[component]
pub fn MyComponent(cx: &Arc<Context>, message: &str, count: &usize) -> View {
// Your component logic, accessing `message` and `count` directly
rsx! {
format!("Message: {}, Count: {}", message, count)
}.view(&cx)
}
// How `MyComponent` would be used in RSX:
// rsx! {
// MyComponent { message: "Hello", count: 123 }
// }
```
#### Generated Code (Simplified):
```rust
pub struct MyComponent {
pub message: String, // Note: `&str` becomes `String`
pub count: usize, // Note: `&usize` becomes `usize`
}
impl MyComponent {
pub fn component(
cx: &Arc<Context>,
message: &str, // Original function signature as `component` method
count: &usize,
) -> View {
// Original body of the function
rsx! {
format!("Message: {}, Count: {}", message, count)
}.view(&cx)
}
}
impl ComponentImpl for MyComponent {
fn call(&self, cx: &Arc<Context>) -> View {
Self::component(
cx,
&self.message, // Passes stored props as references
&self.count,
)
}
}
```
#### Requirements:
* The function must take `cx: &Arc<Context>` as its first parameter.
* The function must return `View`.
* Prop parameters are typically references (e.g., `&str`, `&i32`). The macro automatically converts them to their owned types (e.g., `String`, `i32`) in the generated struct.
## `rsx!` Procedural Macro
```rust
#[proc_macro]
pub fn rsx(input: TokenStream) -> TokenStream { /* ... */ }
```
The `rsx!` macro provides a declarative, React-like syntax for building UI component hierarchies directly in your Rust code. It parses the input and transforms it into calls to the `osui::frontend::Rsx` builder methods.
#### Purpose:
* **Declarative UI**: Allows you to describe *what* your UI should look like, rather than imperatively writing drawing commands.
* **Component Composition**: Enables easy nesting and passing of props/children to other components.
* **Reactive Flow**: Integrates with OSUI's state management to define dynamic UI segments.
#### Syntax Overview:
The `rsx!` macro supports several types of nodes:
1. **Text Literals**:
```rust
rsx! {
"Hello World"
"Another line of text"
}
// Generates:
// r.static_scope(move |scope| {
// scope.view(Arc::new(move |ctx| {
// ctx.draw_text(Point { x: 0, y: 0 }, &format!("Hello World"))
// }));
// // ... for another line
// });
```
2. **Rust Expressions (`@{expr}`)**:
```rust
let name = "Alice";
rsx! {
@{format!("Hello, {}!", name)}
@{1 + 2} // Any `Display` impl
}
// Generates:
// r.child(format!("Hello, {}!", name));
// r.child(1 + 2);
```
* The `expr` must evaluate to a type that implements `osui::frontend::ToRsx`.
3. **Component Instantiation (`Component { prop: value, ... children }`)**:
```rust
#[component] fn MyDiv(cx: &Arc<Context>, content: &str) -> View { /* ... */ }
rsx! {
MyDiv {
content: "Some text", // Prop
rsx! { "Child content" } // Children (if `children: &Rsx` is a prop)
}
}
// Generates:
// r.static_scope(move |scope| {
// scope.child(
// MyDiv {
// content: "Some text".to_string(), // Owned type for struct field
// children: osui::frontend::Rsx(/* ... */)
// },
// None
// );
// });
```
* `path`: The path to the component struct (e.g., `MyDiv`, `my_module::MyComponent`).
* `props`: `key: value` pairs for component properties.
* `children`: Any `rsx!` content directly inside the braces after props. This is collected into the special `children: &Rsx` prop if the component defines it.
4. **Conditional Rendering (`@if condition { ... } [else { ... }]`)**:
```rust
let show = true;
rsx! {
%show @if show {
"Content shown if 'show' is true"
} else {
"Content shown if 'show' is false"
}
}
// Generates:
// r.dynamic_scope(move |scope| {
// if show {
// // ... rsx for true branch
// } else {
// // ... rsx for false branch
// }
// }, vec![Arc::new(show) as Arc<dyn HookDependency>]);
```
* `%dep1, dep2`: Optional dependency list. The `if` block will re-evaluate when any of these dependencies (which must implement `HookDependency`, like `State<T>`) change.
* `condition`: A Rust expression evaluating to `bool`.
* `{ ... }`: An `rsx!` fragment rendered if `condition` is true.
* `else { ... }`: Optional `rsx!` fragment rendered if `condition` is false.
5. **Loop Rendering (`@for pattern in expr { ... }`)**:
```rust
let items = vec!["A", "B", "C"];
rsx! {
%items @for item in items {
format!("Item: {}", item)
}
}
// Generates:
// r.dynamic_scope(move |scope| {
// for item in items {
// // ... rsx for each item
// }
// }, vec![Arc::new(items) as Arc<dyn HookDependency>]);
```
* `%dep1, dep2`: Optional dependency list. The `for` loop will re-evaluate when any of these dependencies change.
* `pattern`: A standard Rust `for` loop pattern (e.g., `item`, `(idx, item)`).
* `expr`: A Rust expression evaluating to an `IntoIterator`.
* `{ ... }`: An `rsx!` fragment rendered for each iteration.
6. **Mount Hook (`!mount_hook_instance`)**:
```rust
let my_manual_mount = use_mount_manual();
rsx! {
!my_manual_mount
}
// Generates:
// my_manual_mount.mount();
```
* Calls the `.mount()` method on the provided `Mount` instance. This is typically used with `use_mount_manual` to trigger effects at a specific point in the render tree.
The `rsx!` macro is a powerful tool for declarative UI construction, abstracting away the underlying `Rsx` object manipulation and `Scope` creation logic.
**Next:** Explore the detailed [Render API](./render-api.md).
@@ -0,0 +1,99 @@
---
sidebar_position: 4
title: Render API
---
# Render Module API Reference
The `render` module provides the foundational primitives for drawing content to the terminal. It defines basic geometric types and the `DrawContext` for accumulating drawing instructions, abstracting away the specifics of the underlying terminal backend.
## Geometric Primitives
These structs define positions and dimensions within the terminal grid.
### `Point` Struct
```rust
#[derive(Clone)]
pub struct Point {
pub x: u16, // X coordinate (column)
pub y: u16, // Y coordinate (row)
}
```
Represents a specific coordinate in a 2D grid.
### `Size` Struct
```rust
#[derive(Clone)]
pub struct Size {
pub width: u16, // Width in terminal columns
pub pub height: u16, // Height in terminal rows
}
```
Represents the dimensions of a rectangular area.
### `Area` Struct
```rust
#[derive(Clone)]
pub struct Area {
pub x: u16, // X coordinate (column) of the top-left corner
pub y: u16, // Y coordinate (row) of the top-left corner
pub width: u16, // Width in terminal columns
pub height: u16, // Height in terminal rows
}
```
Represents a rectangular region defined by its top-left corner (`x`, `y`) and its `width` and `height`.
## `DrawInstruction` Enum
```rust
#[derive(Clone)]
pub enum DrawInstruction {
Text(Point, String),
View(Area, View),
Child(Point, DrawContext), // For embedding child DrawContexts at an offset
}
```
`DrawInstruction` enumerates the different types of atomic drawing operations that the rendering engine can perform.
* **`Text(Point, String)`**: Instructs the engine to draw a given `String` at a specific `Point`.
* **`View(Area, View)`**: Instructs the engine to render a nested `View` within a specified `Area`. This is how child components are rendered.
* **`Child(Point, DrawContext)`**: Instructs the engine to render a child `DrawContext` at a given offset `Point`. This is typically used internally when drawing recursively.
## `DrawContext` Struct
```rust
#[derive(Clone)]
pub struct DrawContext {
pub area: Area, // The total area available for drawing to this context
pub allocated: Area, // The union of all allocated sub-areas within this context
pub drawing: Vec<DrawInstruction>, // Accumulated drawing instructions
}
```
The `DrawContext` is the primary interface for components to issue drawing commands. Each component's `View` receives a `DrawContext` that represents its allocated drawing space. It accumulates `DrawInstruction`s which are then processed by the `Engine`.
#### Methods:
* **`fn new(area: Area) -> Self`**
* Creates a new `DrawContext` with the specified `area` as its total available space. Initializes `allocated` to an "empty" area (max `u16` for x/y, 0 for width/height).
* **`fn allocate(&mut self, x: u16, y: u16, width: u16, height: u16) -> Area`**
* Marks a sub-region within the `DrawContext`'s `area` as "allocated".
* Updates the `self.allocated` field to grow to encompass this new allocation.
* Returns the `Area` representing the newly allocated space. Coordinates (`x`, `y`) are relative to `self.area`'s top-left corner.
* **`fn draw(&mut self, inst: DrawInstruction)`**
* Adds a raw `DrawInstruction` to the `drawing` vector.
* **`fn draw_text(&mut self, point: Point, text: &str)`**
* A convenience method to add a `DrawInstruction::Text` to the context. `point` is relative to `self.area`.
* **`fn draw_view(&mut self, area: Area, view: View)`**
* A convenience method to add a `DrawInstruction::View` to the context. `area` is relative to `self.area`.
This module lays the groundwork for all visual output in OSUI, providing the necessary abstractions for components to describe what they want to render without knowing the specifics of the terminal backend.
**Next:** Explore the [State API](./state-api.md).
@@ -0,0 +1,165 @@
---
sidebar_position: 5
title: State API
---
# State Module API Reference
The `state` module provides OSUI's powerful, React-like hook system for managing component state and side effects. These hooks enable reactivity, allowing your UI to automatically update in response to data changes.
## `State<T>` Struct
```rust
#[derive(Debug)]
pub struct State<T> {
value: Arc<Mutex<T>>,
dependents: Arc<Mutex<Vec<HookEffect>>>,
}
```
`State<T>` is the primary type for holding reactive, component-local state. It wraps a value `T` in an `Arc<Mutex<T>>` for thread-safe access and includes a list of `HookEffect`s that should be triggered when its value changes.
#### Methods:
* **`fn get(&self) -> Inner<'_, T>`**
* Acquires a lock on the internal `Mutex` and returns an `Inner<'_, T>` guard. This guard provides mutable (`DerefMut`) access to the state value. When the `Inner` guard is dropped, if the value was mutated, all `dependents` (registered `HookEffect`s) are notified.
* **`fn get_dl(&self) -> T`**
* "Deadlock-less" getter. Acquires a lock, clones the internal value `T`, releases the lock, and returns the cloned value. Useful for reading the state when cloning `T` is cheap and you don't need mutable access, preventing potential deadlocks from holding a `MutexGuard` across `await` points or other blocking operations. Requires `T: Clone`.
* **`fn set(&self, v: T)`**
* Replaces the current state value with `v` and then explicitly notifies all `dependents`.
* **`fn update(&self)`**
* Manually triggers all registered `dependents`. Useful if you've modified the internal value without using `get()` (e.g., via `Arc::get_mut` if the `Arc` is uniquely owned, which is rare for `State<T>`).
* **`fn clone(&self) -> Self`**
* Clones the `State<T>` handle (not the internal value). This creates a new `Arc` reference to the same underlying `value` and `dependents`. Essential for moving `State<T>` into closures or passing to child components without moving the actual state.
#### `Display` Implementation:
If `T` implements `std::fmt::Display`, `State<T>` also implements `std::fmt::Display`, allowing it to be directly formatted (e.g., in `format!`) by implicitly calling `get_dl()`.
### `Inner<'a, T>` Struct
```rust
pub struct Inner<'a, T> {
value: MutexGuard<'a, T>,
dependents: Arc<Mutex<Vec<HookEffect>>>,
updated: bool,
}
```
A guard type returned by `State<T>::get()`. It provides scoped, mutable access to the internal state value.
#### `Deref` and `DerefMut` Implementations:
* Allows `Inner<'a, T>` to be treated as a `&T` or `&mut T`, giving direct access to the underlying state.
* The `DerefMut` implementation sets an internal `updated` flag.
#### `Drop` Implementation:
* When `Inner<'a, T>` is dropped, if the `updated` flag is `true`, it automatically iterates through `dependents` and calls their `call()` method, ensuring reactivity.
## `HookEffect` Struct
```rust
#[derive(Clone)]
pub struct HookEffect(Arc<Mutex<dyn FnMut() + Send + Sync>>);
```
A wrapper around a mutex-protected closure that represents a side effect. These are registered as dependents of `State<T>` or `Mount` and are triggered when dependencies change.
#### Methods:
* **`fn new<F: Fn() + Send + Sync + 'static>(f: F) -> Self`**
* Creates a new `HookEffect` from a given closure.
* **`fn call(&self)`**
* Executes the wrapped closure by acquiring its mutex.
## `HookDependency` Trait
```rust
pub trait HookDependency: Send + Sync {
fn on_update(&self, hook: HookEffect);
}
```
The `HookDependency` trait defines how an object can register an effect (`HookEffect`) to be triggered when it updates.
**Implementations:**
* **`State<T>`**: Registers the `HookEffect` to be called when `State<T>`'s value changes (via `set()`, `get()` and subsequent drop, or `update()`).
* **`Mount`**: Registers the `HookEffect` to be called when `mount()` is invoked, or immediately if already mounted.
## `Mount` Struct
```rust
#[derive(Debug, Clone)]
pub struct Mount(Arc<Mutex<bool>>, Arc<Mutex<Vec<HookEffect>>>);
```
A specialized hook for managing component lifecycle (specifically, the "mounted" state). It tracks whether a component has been mounted and queues `HookEffect`s to be run upon mounting.
#### Methods:
* **`fn mount(&self)`**
* Sets the internal flag to `true`, indicating the component is now mounted.
* Executes all currently queued `HookEffect`s and clears the queue.
* Any `HookEffect` registered after `mount()` has been called will execute immediately.
## Hooks Functions
These functions are the primary way to interact with the state management system within your components.
### `fn use_state<T>(v: T) -> State<T>`
* **Purpose**: Creates and initializes a new `State<T>` instance.
* **Usage**: `let count = use_state(0);`
### `fn use_effect<F: FnMut() + Send + Sync + 'static>(f: F, dependencies: &[&dyn HookDependency])`
* **Purpose**: Registers a side effect `f` that will run when any of the `dependencies` change, and also once initially. The effect closure is run in a `std::thread::spawn`.
* **Usage**:
```rust
let counter = use_state(0);
use_effect(
{ let counter = counter.clone(); move || println!("Counter changed: {}", counter.get_dl()) },
&[&counter] // Dependencies
);
```
* **Empty Dependencies**: If `dependencies` is `&[]`, the effect runs once on initial render and never again.
### `fn use_mount() -> Mount`
* **Purpose**: Creates a `Mount` instance that is immediately marked as "mounted". Effects registered with this `Mount` (via `use_effect`) will run once, immediately.
* **Usage**: `let mount_hook = use_mount();`
### `fn use_mount_manual() -> Mount`
* **Purpose**: Creates a `Mount` instance that starts in an "unmounted" state. Effects registered with this `Mount` will *only* run when its `.mount()` method is explicitly called (either programmatically or via `!mount_hook_instance` in `rsx!`).
* **Usage**: `let manual_mount_hook = use_mount_manual();`
### `fn use_sync_state<T, E, D>(cx: &Arc<Context>, v: T, decoder: D) -> State<T>`
* **Purpose**: Creates a `State<T>` that automatically updates its value whenever an event of type `E` is emitted to the given `Context`. The `decoder` function converts `&E` into a new `T`.
* **Usage**:
```rust
// Assume MessageChangeEvent is defined
let message_state = use_sync_state(
cx,
"Default message".to_string(),
|event: &MessageChangeEvent| event.0.clone()
);
```
### `fn use_sync_effect<T, Ev, E>(cx: &Arc<Context>, state: &State<T>, encoder: E, deps: &[&dyn HookDependency])`
* **Purpose**: Registers an effect that emits an event `Ev` to the given `Context` whenever the monitored `state` changes (or any other specified `deps`). The `encoder` function converts `&State<T>` into an `Ev`.
* **Usage**:
```rust
let count_state = use_state(0);
// Assume CounterUpdatedEvent is defined
use_sync_effect(
cx,
&count_state,
|s: &State<i32>| CounterUpdatedEvent { new_value: s.get_dl() },
&[&count_state]
);
```
These hooks provide a complete and reactive state management solution, enabling dynamic and interactive TUI applications in OSUI.
**Next:** Delve into the details of the [Macros API](./macros-api.md).
@@ -0,0 +1,94 @@
---
sidebar_position: 0
title: Core Architecture
---
# Core Architecture
OSUI is designed with a clear separation of concerns, drawing inspiration from modern GUI frameworks like React. This modular architecture aims for flexibility, testability, and scalability. Here's a high-level overview of how the different parts of OSUI interact to form a functional TUI application.
```mermaid
graph TD
A[Application Root (main.rs)] --> B(Engine::run(RootComponent))
B --> C(Engine)
C -- init --> D(Root Component Context)
D -- refresh --> E(Root Component View)
E -- generate_children --> F(Frontend::Rsx)
F -- create Scopes & Contexts --> D
D -- draw_children --> G(Render::DrawContext)
G -- execute DrawInstructions --> H(Engine::draw_context)
H -- actual terminal output --> I(crossterm)
subgraph State & Events
J[Component Context] -- manage state via hooks --> K(State<T>)
K -- notify dependents --> L(HookEffect)
L -- trigger callbacks --> J
J -- emit events --> M(Event Handlers)
M -- propagate to children --> J
end
subgraph User Interaction
N[Input Polling Thread] --> O(Engine::CommandExecutor)
O -- execute commands --> C
O -- emit events --> J
end
C -- continuous loop --> E
D --> J
J --> E
```
## Key Architectural Components
### 1. **Component System (`osui::component`)**
* **`ComponentImpl`**: The fundamental trait defining a renderable UI unit. Any type implementing this can be an OSUI component.
* **`Context`**: Each active component instance has its own `Context`. This is the central hub for:
* Holding the component's `View` (its rendered output).
* Managing its local state using hooks (e.g., `use_state`).
* Registering and emitting events (`on_event`, `emit_event`).
* Managing its children components and their `Scope`s.
* Accessing the `CommandExecutor` to interact with the engine.
* **`Scope`**: A `Context` can contain multiple child `Scope`s. A `Scope` is primarily a container that groups a set of child components (`Context`s) and their optional `ViewWrapper`s. `Rsx` fragments generate `Scope`s to manage their children.
**Why it works this way**: This component-based approach promotes modularity and reusability. `Context` provides a stable identity and state for each component instance, enabling independent updates and event handling. The tree structure formed by `Context`s and `Scope`s mirrors the UI hierarchy.
### 2. **State Management (`osui::state`)**
* **`State<T>`**: A reactive wrapper for any data `T`. When `State<T>` is updated, it automatically notifies all its registered "dependents".
* **`use_state`**: The primary hook to create and manage `State<T>` within a component.
* **`use_effect`**: A hook for performing side effects (e.g., logging, network requests) in response to `State<T>` changes or component mounting.
* **`HookDependency`**: A trait that `State<T>` and `Mount` implement, allowing them to be tracked by `use_effect` and dynamic `rsx!` blocks.
**Why it works this way**: Inspired by React hooks, this system provides a predictable and efficient way to manage mutable state. By declaring explicit dependencies for effects and dynamic `rsx!` blocks, OSUI can minimize re-renders and computations, only updating parts of the UI that are truly affected by state changes.
### 3. **Frontend / Declarative UI (`osui::frontend` & `osui-macros`)**
* **`rsx!` macro**: A procedural macro that allows you to write UI using a declarative, XML-like syntax directly in Rust.
* **`#[component]` macro**: A procedural macro that transforms a Rust function into an OSUI component, automatically handling prop parsing and `ComponentImpl` implementation.
* **`Rsx`**: An internal representation (produced by `rsx!`) of a UI fragment, consisting of a vector of `RsxScope`s.
* **`RsxScope`**: An enum defining different types of UI nodes (static text, components, dynamic conditional/loop blocks).
**Why it works this way**: Declarative UI is generally easier to reason about than imperative drawing commands. The `rsx!` macro provides a high-level abstraction that maps directly to the component tree and state management, significantly improving developer experience. The macro-generated `Rsx` object then serves as a blueprint for `Context` to build its children.
### 4. **Rendering Pipeline (`osui::render`)**
* **`View`**: The ultimate output of a component's rendering logic. It's a closure that, when called, populates a `DrawContext` with drawing instructions.
* **`DrawContext`**: A mutable accumulator for `DrawInstruction`s. Components add text, child views, or custom drawing commands to this context. It also tracks the available `Area` and `allocated` space.
* **`DrawInstruction`**: An enum representing atomic drawing operations (e.g., `Text`, `View`, `Child`).
* **Geometric Primitives**: `Point`, `Size`, `Area` define positions and dimensions.
**Why it works this way**: This separation allows the rendering logic to be independent of the actual display medium. Components declare *what* to draw using high-level instructions, and the `Engine` then decides *how* to execute them on the specific backend (e.g., terminal).
### 5. **Engine (`osui::engine`)**
* **`Engine` trait**: Defines the interface for running an OSUI application, including initialization, continuous rendering, and managing the render loop.
* **`Console`**: The default implementation of `Engine`, which uses `crossterm` to interact with the terminal.
* **`CommandExecutor` trait**: An interface for executing system-level commands (e.g., `Stop`).
* **`Benchmark`**: A wrapper `Engine` that measures and reports performance statistics.
**Why it works this way**: The `Engine` trait makes OSUI extensible. You can swap out the `Console` engine for a different backend (e.g., a web renderer, a headless testing engine) without changing your core component logic. The `CommandExecutor` provides a standardized way for components to request actions from the environment.
This interconnected architecture allows OSUI to offer a powerful, flexible, and developer-friendly experience for building sophisticated Terminal User Interfaces.
**Next:** Delve deeper into [The Component Model](./01-the-component-model.md).
@@ -0,0 +1,107 @@
---
sidebar_position: 1
title: The Component Model
---
# The Component Model
At the core of OSUI's design is a robust component model, defining how UI elements are created, composed, and managed. This model provides structure, promotes reusability, and facilitates a clear separation of concerns within your TUI application.
## `ComponentImpl`: The Building Block
The `ComponentImpl` trait is the most fundamental concept for any renderable unit in OSUI:
```rust
pub trait ComponentImpl: Send + Sync {
/// Renders the component within the given context, returning a View
fn call(&self, cx: &Arc<Context>) -> View;
}
```
* **`fn call(&self, cx: &Arc<Context>) -> View`**: This method is where a component's rendering logic resides. It takes a reference to the component instance itself and its `Context` (`cx`), and it must return a `View`. The `View` is OSUI's abstraction for "what to draw," essentially a closure that will populate a `DrawContext` with drawing instructions later in the rendering pipeline.
* **`Send + Sync`**: Components must be `Send` and `Sync` to ensure they can be safely passed between threads, as OSUI leverages concurrency for various operations (e.g., `use_effect` hooks).
Most often, you won't implement `ComponentImpl` manually. Instead, you'll use the `#[component]` procedural macro:
```rust
#[component]
fn MyComponent(cx: &Arc<Context>, some_prop: &String) -> View {
// Component logic and RSX here
rsx! {
format!("Prop: {}", some_prop)
}.view(&cx)
}
```
The `#[component]` macro automatically generates a struct for `MyComponent` (with `some_prop: String` as a field) and implements `ComponentImpl` for it, delegating the `call` method to your function's body.
## `Context`: The Component's Identity and State
Every active instance of a component in the UI tree has its own `Context` (`osui::component::context::Context`). The `Context` is the component's runtime identity and central hub for managing its internal state and interactions:
```rust
pub struct Context {
component: AccessCell<Component>,
view: AccessCell<View>,
event_handlers: AccessCell<HashMap<TypeId, Vec<EventHandler>>>,
pub(crate) scopes: Mutex<Vec<Arc<Scope>>>,
executor: Arc<dyn CommandExecutor>,
}
```
**Why `Context` is crucial**:
* **State Management**: It's the entry point for all state hooks (`use_state`, `use_effect`, etc.), ensuring that each component instance manages its own isolated, reactive state.
* **Event Handling**: `Context` provides methods (`on_event`, `emit_event`) for handling component-specific and application-wide events. Events propagate through the `Context` tree.
* **Rendering Result (`View`)**: It holds the `View` generated by the `ComponentImpl::call` method, which is later passed to the rendering engine.
* **Child Management (`scopes`)**: A `Context` aggregates child components through `Scope`s, forming the hierarchical UI tree.
* **Engine Interaction**: It provides access to the `CommandExecutor`, allowing components to send commands (like `Stop`) to the underlying engine.
When a component is instantiated (e.g., `MyComponent { ... }` in `rsx!`), a new `Context` is created for it. This `Context` lives as long as the component is part of the active UI tree.
## `Scope`: Organizing Children
While `Context` represents a single component instance, `Scope` (`osui::component::scope::Scope`) is responsible for grouping and managing collections of child `Context`s:
```rust
pub struct Scope {
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
executor: Arc<dyn CommandExecutor>,
}
```
**Relationship between `Context` and `Scope`**:
* A `Context` can contain multiple child `Scope`s (stored in `Context::scopes`).
* Each `Scope` then contains a `Vec` of `(Arc<Context>, Option<ViewWrapper>)`, representing the actual child components (and optional view modifiers) within that specific scope.
* This distinction allows for flexibility in how children are managed, particularly for dynamic `rsx!` constructs like `@if` and `@for` which might create or destroy entire `Scope`s based on conditions or iterations.
**How `Scope`s are used**:
* When you use `rsx!`, the macro emits calls to `context.scope()` or `context.dyn_scope()`, which create new `Scope`s.
* Within these `Scope`s, children are added using `scope.child()` or `scope.view()`.
* The `Context::draw_children` method then iterates through these `Scope`s and their children to orchestrate their rendering.
## The Component Tree
Together, `ComponentImpl`, `Context`, and `Scope` form a hierarchical component tree:
```
App Component (Context)
└── App Scope (from App's rsx!)
├── Child Component A (Context)
│ └── Child A Scope (from A's rsx!)
│ └── Grandchild Component X (Context)
├── Child Component B (Context)
│ └── Child B Scope
│ ├── Grandchild Component Y (Context)
│ └── Grandchild Component Z (Context)
└── Dynamic Scope (e.g., from an `@if` block)
└── (Conditionally rendered children)
```
This tree structure is fundamental to OSUI's rendering and event propagation. Events emitted by a child `Context` can traverse up the tree to parent `Context`s (if they listen for them) and always propagate down to all descendants.
The component model provides a clear, organized, and powerful way to structure your TUI applications, promoting maintainability and scalability through modularity and a well-defined lifecycle for each UI element.
**Next:** Understand how `State<T>` and `HookDependency` enable efficient UI updates in [Reactive State Flow](./02-reactive-state-flow.md).
@@ -0,0 +1,114 @@
---
sidebar_position: 2
title: Reactive State Flow
---
# Reactive State Flow
OSUI's reactivity model is designed to efficiently update the UI in response to changes in application data. It centers around `State<T>`, `HookDependency`, and `use_effect`, forming a flow where data changes automatically trigger re-rendering of affected components.
## 1. `State<T>`: The Source of Truth
At the heart of reactivity is the `State<T>` type. When you declare state using `use_state(initial_value)`, you get a `State<T>` instance:
```rust
let count = use_state(0); // count: State<i32>
```
`State<T>` wraps your actual data `T` in an `Arc<Mutex<T>>`, allowing it to be safely shared and mutated across multiple threads and component scopes.
## 2. Updating `State<T>`
When the value held by `State<T>` changes, this initiates the reactive flow. There are two primary ways to update `State<T>`:
* **`state.set(new_value)`**: Replaces the entire value and explicitly triggers an update.
* **`*state.get() = new_value`**: Acquires an `Inner<'_, T>` guard, which provides mutable access to the underlying value. When this `Inner` guard is dropped (goes out of scope), it automatically checks if the value was modified and, if so, triggers an update.
```rust
// Method 1: using .set()
count.set(count.get_dl() + 1);
// Method 2: using .get() for mutable access
{
let mut count_guard = count.get(); // Acquire Inner guard
*count_guard += 1; // Mutate the value
} // count_guard drops here, automatically triggering updates
```
## 3. `HookDependency`: Declaring Reactivity
For an update to `State<T>` to have an effect, there must be something "listening" for that update. This is where the `HookDependency` trait comes in:
```rust
pub trait HookDependency: Send + Sync {
fn on_update(&self, hook: HookEffect);
}
```
* `State<T>` implements `HookDependency`. This means you can register an `HookEffect` with a `State<T>` instance, and `State<T>` will ensure that effect is called whenever its value changes.
* `Mount` also implements `HookDependency` for managing component lifecycle effects.
## 4. `HookEffect`: The Callback
An `HookEffect` is essentially a wrapper around a closure (`Arc<Mutex<dyn FnMut() + Send + Sync>>`) that represents a side effect. When a `HookDependency` updates, it calls all registered `HookEffect`s.
```rust
// An effect might look something like this internally:
let my_effect = HookEffect::new(move || {
// This code runs when the dependency updates
println!("Dependency changed!");
});
```
## 5. `use_effect` and Dynamic `rsx!` Blocks: Consuming Reactivity
The `HookDependency` and `HookEffect` mechanism is consumed by two main features to enable reactive UI updates:
### a) `use_effect` Hook
`use_effect` allows you to run side effects when specified dependencies change:
```rust
use_effect(
{
let count = count.clone(); // Clone State handle for closure
move || {
// This closure runs in a spawned thread when `count` updates
println!("Count is now: {}", count.get_dl());
}
},
&[&count], // `count` is the dependency
);
```
When `use_effect` is first called, it registers its internal `HookEffect` closure with each `HookDependency` in the provided slice. Each time `count` is updated, `count` calls its registered `HookEffect`, which then executes the provided closure.
### b) Dynamic `rsx!` Blocks (`%dep @if ...`, `%dep @for ...`)
OSUI's `rsx!` macro supports special syntax for dynamic UI segments that automatically re-render when dependencies change:
```rust
rsx! {
%count @if *count.get() > 0 { // This block re-renders if `count` changes
format!("Count is positive: {}", count.get_dl())
}
%items @for item in items.get_dl() { // This block re-renders if `items` changes
format!("- {}", item)
}
}
```
* When the `rsx!` macro encounters `%dep`, it also registers a special internal `HookEffect` with that dependency.
* This `HookEffect` is responsible for re-evaluating the entire `dynamic_scope` (the `if` or `for` block) within the component's `Context`. This re-evaluation re-runs the `rsx!` logic for that block, generating potentially new children or text nodes, and thus updating the UI.
## The Reactive Flow in Summary
1. A component uses `use_state` to create a `State<T>`.
2. The `State<T>` is passed as a dependency to `use_effect` or declared in a dynamic `rsx!` block (`%state`).
3. When `State<T>`'s value is modified (`set()` or `get()` then drop), it triggers its registered `HookEffect`s.
4. These `HookEffect`s then either execute a side-effect closure (from `use_effect`) or trigger a re-evaluation of the corresponding `dynamic_scope` (from `rsx!`).
5. Re-evaluation of `dynamic_scope` leads to updated `DrawInstruction`s, which the `Engine` eventually renders to the terminal.
This elegant system ensures that your UI remains synchronized with your application's data, responding efficiently and predictably to changes, minimizing manual re-rendering logic.
**Next:** Understand how events traverse the component tree in [Event Propagation](./03-event-propagation.md).
@@ -0,0 +1,104 @@
---
sidebar_position: 3
title: Event Propagation
---
# Event Propagation
Event propagation in OSUI describes how events traverse the component tree after they are emitted. Understanding this model is crucial for designing effective inter-component communication and responsive user interfaces.
## Unidirectional Propagation: Downwards
OSUI employs a unidirectional event propagation model, primarily **downwards** through the component tree. When an event is emitted from a component's `Context`, it follows this path:
1. **Current Context**: All `on_event` handlers registered on the `Context` that emitted the event are invoked first.
2. **Child Contexts**: The event is then recursively propagated to all immediate children's `Context`s, and from there, further down to their children, and so on, until it reaches the leaves of the component tree. Each child `Context` will also invoke its own registered `on_event` handlers for that event type.
This means an event emitted by an ancestor component will reach all its descendants. An event emitted by a child component will reach its parents (if the parent `Context` has `on_event` handlers for it) and all its siblings and their descendants.
```mermaid
graph TD
A[Root Component Context] --> B(Child A Context)
A --> C(Child B Context)
B --> D(Grandchild A1 Context)
B --> E(Grandchild A2 Context)
C --> F(Grandchild B1 Context)
subgraph Event Propagation (emit_event from D)
D -- handlers on D --> D
D -- propagate --> B
B -- handlers on B --> B
B -- propagate --> E
E -- handlers on E --> E
B -- propagate --> A
A -- handlers on A --> A
A -- propagate --> C
C -- handlers on C --> C
C -- propagate --> F
F -- handlers on F --> F
end
```
### Methods for Event Emission
* **`cx.emit_event(event: E)`**:
* This is the standard method for emitting events.
* It processes event handlers synchronously in the current thread. This means that subsequent code execution will wait for all handlers (and their propagation to children) to complete.
* Useful for events where the order of execution matters or where the handler logic is quick.
* **`cx.emit_event_threaded(event: &E)`**:
* This method processes event handlers asynchronously by spawning a new `std::thread` for *each* registered handler.
* The event object `E` must implement `Clone` because each handler receives its own cloned copy.
* Useful for events that might trigger long-running or blocking operations, preventing them from freezing the UI. The event propagation down the tree also uses the threaded approach.
## Practical Implications
### Parent-to-Child Communication (Implicit)
If a parent component emits an event, all its child components (and their children) that have `on_event` handlers for that specific event type will receive it. This is a powerful way for ancestors to broadcast information or commands to their descendants.
```rust
// Parent emits a "Refresh" event
cx.emit_event(RefreshEvent {});
// Child listens for "Refresh" event
cx.on_event(|_cx, _event: &RefreshEvent| {
// Perform refresh logic
});
```
### Child-to-Parent/Sibling Communication (Explicit)
A child component can effectively communicate with its parent or siblings by emitting an event. Because events propagate downwards from the emitting `Context` *and then* to all its children (and subsequently to its parent's other children, if any), the parent and siblings will receive the event if they are listening for it.
```rust
// Child component
#[component]
fn Child(cx: &Arc<Context>) -> View {
// ... logic to decide when to emit
cx.emit_event(ChildActionCompleted { data: "success".to_string() });
// ...
}
// Parent component
#[component]
fn Parent(cx: &Arc<Context>) -> View {
cx.on_event(|_cx, event: &ChildActionCompleted| {
println!("Parent received action from child: {:?}", event.data);
});
rsx! { Child {} }.view(&cx)
}
```
### Decoupling Components
Event handling promotes a decoupled architecture. Components don't need direct references to each other to communicate; they only need to agree on common event types. This makes components more independent and easier to reuse.
## When to use `emit_event` vs. `emit_event_threaded`
* **`emit_event`**: Use for most general-purpose events where handlers are quick, or you need strict sequential processing, or if you don't want the overhead of spawning many threads.
* **`emit_event_threaded`**: Use for events that might trigger expensive, long-running, or I/O-bound operations in their handlers. This prevents the main rendering loop from blocking, ensuring a responsive UI. Be mindful of potential race conditions if multiple threads modify shared state (though `State<T>`'s `Mutex` helps mitigate this).
Understanding OSUI's downward event propagation is key to designing robust and reactive component interactions within your TUI applications.
**Next:** Get insights into how your UI transforms from `View`s to terminal output in [The Rendering Pipeline](./04-rendering-pipeline.md).
@@ -0,0 +1,73 @@
---
sidebar_position: 4
title: The Rendering Pipeline
---
# The Rendering Pipeline
The OSUI rendering pipeline is the process by which your declarative component hierarchy is transformed into concrete drawing operations on the terminal. It's an abstraction layer that allows components to describe *what* to draw, while the `Engine` handles *how* to draw it.
## Stages of the Pipeline
The pipeline can be broken down into several distinct stages:
### 1. Component `call` and `View` Generation
* **`Engine::run`**: The main application loop starts by calling `Engine::run` with your root component.
* **`Context::refresh`**: The engine initializes the root component's `Context` and calls `Context::refresh`.
* **`ComponentImpl::call`**: Inside `refresh`, the component's `ComponentImpl::call` method is invoked. This is where your component function (decorated with `#[component]`) executes.
* **`rsx!` Macro Expansion**: Within your component function, the `rsx!` macro generates an `osui::frontend::Rsx` object.
* **`Rsx::view(&cx)`**: This method converts the `Rsx` object into a `View`. Critically, `Rsx::view` also triggers `Rsx::generate_children`.
* **`Rsx::generate_children`**: This recursively processes the `Rsx` object, creating new child `Context`s and `Scope`s within the current `Context`. For `dynamic_scope`s (`@if`, `@for`), it also registers `use_effect` hooks to trigger re-evaluation when dependencies change.
* **Result**: The component function ultimately returns a `View`. This `View` is a closure that, when executed, will populate a `DrawContext` by calling `context.draw_children()`.
### 2. `DrawContext` Construction (`render_view`)
* **`Engine::render`**: In the main rendering loop, the `Engine` calls `render` for the current `Context`.
* **`Engine::render_view`**: The `Engine` creates a fresh, empty `DrawContext` for the entire screen `Area`. It then executes the root component's `View` (the closure generated in Stage 1) against this `DrawContext`.
* **`Context::draw_children`**: The root `View`'s closure invokes `context.draw_children()`. This method iterates through all child `Scope`s and their contained `Context`s. For each child `Context`, it retrieves its `View` and adds a `DrawInstruction::View` to the current `DrawContext`, recursively starting the `render_view` process for children within their allocated `Area`.
* **`DrawContext::draw_text`, `DrawContext::draw_view`, `DrawContext::allocate`**: As `View`s are executed, they add `DrawInstruction`s to the `DrawContext` using methods like `draw_text` for text, `draw_view` for child components, and `allocate` to mark used screen regions.
* **Result**: A fully populated `DrawContext` containing a flat list of `DrawInstruction`s, ready for rendering.
### 3. `DrawInstruction` Execution (`draw_context`)
* **`Engine::draw_context`**: After `render_view` has produced a complete `DrawContext`, the `Engine`'s `draw_context` method is called. This is the stage where the actual terminal output happens.
* **Instruction Iteration**: `draw_context` iterates through the `Vec<DrawInstruction>` inside the `DrawContext`.
* **`Text`**: For `DrawInstruction::Text(point, text)`, the engine translates `point` (which is relative to the `DrawContext`'s `area`) into absolute terminal coordinates and uses `crossterm` to move the cursor and print the `text`.
* **`View`**: For `DrawInstruction::View(area, view)`, the engine recursively calls `render_view` for the child `view` within its specific `area`, then processes the resulting `DrawContext`.
* **`Child`**: For `DrawInstruction::Child(point, child_ctx)`, the engine recursively calls `draw_context` for the `child_ctx`, applying the `point` offset.
* **Terminal Output**: The `Console` engine uses `crossterm` functions (like `MoveTo`, `Print`, `Clear`) to modify the terminal buffer.
* **Result**: The visible TUI on the user's screen.
## `render_delay` and Loop
After `draw_context` completes, the `Engine` typically calls `render_delay()` (defaulting to 16ms for ~60 FPS) before the entire loop restarts with the next `Engine::render` call. This continuous loop maintains a responsive and updated UI.
```mermaid
graph TD
A[Component Function (`#[component]`)] --> B(Generates `Rsx` object)
B --> C(Rsx::view(&cx))
C -- calls Rsx::generate_children --> D(Builds child Contexts & Scopes)
D --> E(Returns a `View` closure)
subgraph Engine Loop
F[Engine::render(root_cx)] --> G(Engine::render_view(full_screen_area, root_view))
G -- creates empty DrawContext --> H(Executes root_view closure)
H -- root_view calls Context::draw_children --> I(Recursively adds DrawInstruction::View for children)
I -- children's Views populate DrawContext --> J(Result: Full DrawContext with instructions)
J --> K(Engine::draw_context(full_DrawContext))
K -- iterates DrawInstructions --> L(Executes terminal ops via crossterm)
L --> M[Visible TUI]
M -- optional delay --> N(Engine::render_delay)
N --> F
end
```
## Key Principles
* **Declarative vs. Imperative**: Components declare *what* to draw (`View`, `DrawInstruction`), not *how* to directly manipulate the terminal. The engine handles the imperative *how*.
* **Separation of Concerns**: Each stage focuses on a specific responsibility: component logic, state management, UI tree construction, and final rendering.
* **Reactivity Integration**: Dynamic `rsx!` blocks and `use_effect` ensure that only affected parts of the `View` or `DrawContext` are re-generated efficiently when state changes, minimizing redundant work.
* **Extensibility**: The `Engine` trait allows for different rendering backends (e.g., to a file, to a graphical window, or for benchmarking) without modifying component logic.
Understanding this pipeline helps in debugging rendering issues, optimizing performance, and building custom rendering logic within your OSUI applications.
@@ -0,0 +1,186 @@
---
sidebar_position: 0
title: Performance Benchmarking
---
# Performance Benchmarking
Optimizing rendering performance is crucial for smooth and responsive TUI applications, especially those with complex layouts or frequent updates. OSUI provides a built-in `Benchmark` engine wrapper that allows you to easily measure the rendering speed of your components.
## Current performance
All the latest benchmark results are in [benchmark.csv](https://github.com/osui-rs/osui/blob/master/benchmark.csv)
![Dot diagram](3dplot-gen-dot.png)
![Dot diagram](3dplot-gen-surface.png)
## The `Benchmark` Engine
The `osui::engine::benchmark` module offers the `Benchmark<T: Engine>` struct, which wraps an existing engine (like `Console`) and records detailed timing information for its rendering cycles.
### How it works:
1. You instantiate a `Benchmark` by passing it another `Engine` (e.g., `Console::new()`).
2. When you call `benchmark_engine.run(YourApp {})`, the `Benchmark` engine takes over.
3. Instead of running the application indefinitely, it performs a fixed number of render cycles (defaulting to 40 in `Benchmark::run`).
4. For each cycle, it precisely measures the time taken to `render` your root component.
5. After all cycles, it clears the screen and returns a `BenchmarkResult` containing statistics.
## `BenchmarkResult`
The `BenchmarkResult` struct holds the collected performance metrics:
```rust
pub struct BenchmarkResult {
pub average: u128, // Average render time in microseconds
pub min: u128, // Minimum render time in microseconds
pub max: u128, // Maximum render time in microseconds
pub total_render: u128, // Sum of all render times in microseconds
pub total: u128, // Total time spent during the benchmark (including setup)
}
```
These values are typically in microseconds (`µs`).
## Basic Usage Example
Let's use the `simple_benchmark.rs` example to see the `Benchmark` engine in action:
```rust title="examples/simple_benchmark.rs"
use osui::prelude::*;
use std::sync::Arc; // Needed for Arc<Context>
pub fn main() {
// 1. Create a Console engine instance.
let console_engine = Console::new();
// 2. Wrap it with the Benchmark engine.
let benchmark_engine = Benchmark::new(console_engine);
// 3. Run your application (or component) through the Benchmark engine.
let benchmark_result = benchmark_engine.run(App {}).expect("Failed to run benchmark");
// 4. Print the results.
println!("Avg: {} μs", benchmark_result.average);
println!("Min: {} μs", benchmark_result.min);
println!("Max: {} μs", benchmark_result.max);
println!("Tot: {} μs", benchmark_result.total);
println!("Tot Render: {} μs", benchmark_result.total_render);
}
#[component]
fn App(cx: &Arc<Context>) -> View {
rsx! {
"Hello, world!"
}
.view(&cx)
}
```
To run this example:
```bash
cargo run --example simple_benchmark
```
You will see output similar to:
```
Avg: 1078 μs
Min: 1078 μs
Max: 1078 μs
Tot: 43120 μs
Tot Render: 43120 μs
```
*(Note: Actual values will vary based on your system and terminal emulator.)*
## Advanced Usage: Benchmarking Complex Scenarios
The `benchmark.rs` example demonstrates how to benchmark nested components and iterate through different complexity levels. This is useful for identifying performance bottlenecks in specific UI patterns.
```rust title="examples/benchmark.rs"
use osui::prelude::*;
use std::collections::HashMap; // Needed for HashMap
pub fn main() {
let engine = Arc::new(Benchmark::new(Console::new())); // Wrap Console in Benchmark, then Arc it.
let mut benchmark_results: HashMap<(usize, usize), BenchmarkResult> = HashMap::new();
// Iterate through different levels of nesting (n) and iterations (i)
for i in 0..15 {
for n in 0..15 {
let res = {
let mut results = Vec::with_capacity(6);
// Run each specific benchmark configuration multiple times (e.g., 6)
// to get more consistent results, then pick the median (index 3 after sort).
for _ in 0..6 {
results.push(
engine
.run(App {
n: n * 72, // Scale nesting depth
i: i * 72, // Scale iteration count (for loops)
})
.expect("Failed to run engine"),
);
}
results.sort_by_key(|r| r.total_render); // Sort by total render time
results[3].clone() // Take the median result
};
benchmark_results.insert((i, n as usize), res);
}
}
// Output results in CSV format
println!("Iterx72,Nestingx72,Time µs");
for ((i, n), bench) in benchmark_results.iter() {
println!("{i},{n},{}", bench.total_render);
}
}
// A recursive component that creates nested children and loops
#[component]
fn App(cx: &Arc<Context>, n: usize, i: usize) -> View {
let n = n.clone(); // Clone props for closure (necessary for #[component] macro)
let i = i.clone();
if n == 0 {
// Base case: deepest level, render a simple string
rsx! {
"Hello, world!"
}
.view(&cx)
} else {
// Recursive case: create `i` number of children, each with reduced nesting `n-1`
rsx! {
@for _ in (0..i) { // Loop `i` times
App { n: n - 1, i: 0 } // Create a nested App component
}
}
.view(&cx)
}
}
```
This example:
* Uses an `Arc<Benchmark>` to allow `run` to be called multiple times.
* Iterates through different `n` (nesting depth) and `i` (number of children in a loop) values.
* Runs each configuration multiple times and takes the median `total_render` to reduce noise.
* Prints the results in CSV format, which can be easily imported into spreadsheet software for analysis (like the `benchmark.csv` in the repository).
## Interpreting Results
* **`average`, `min`, `max`**: Provide insight into the consistency of your rendering performance. A large difference between `min` and `max` might indicate inconsistencies or external factors affecting performance.
* **`total_render`**: The sum of all individual render cycle times. This is often the most important metric for overall performance.
* **`total`**: The total time for the benchmark process, including setup. This is less about rendering speed and more about the overhead of the benchmark itself.
When analyzing benchmarks, look for:
* **Linear vs. Non-linear Scaling**: How does `total_render` increase as you increase nesting depth or the number of components? Ideally, it should scale linearly.
* **Bottlenecks**: Can you isolate which components or `rsx!` patterns (e.g., complex loops, many dynamic scopes) contribute most to render time?
* **Regression**: Use benchmarks in your CI/CD pipeline to detect performance regressions introduced by new code.
By leveraging OSUI's `Benchmark` engine, you can gain valuable insights into your TUI application's performance characteristics and make data-driven decisions for optimization.
**Next:** Learn how to customize OSUI by [Implementing a Custom Engine](./01-customizing-the-engine.md).
@@ -0,0 +1,260 @@
---
sidebar_position: 1
title: Customizing the Engine
---
# Customizing the Engine
OSUI's `Engine` and `CommandExecutor` traits are designed to be highly extensible. While the `Console` engine provides `crossterm`-based terminal rendering, you might want to create a custom engine for various reasons:
* **Different Rendering Backend**: Render to a graphical window (e.g., using `minifb` or `pixels`), a web canvas, or a specific hardware display.
* **Headless Testing**: Create a dummy engine that doesn't render anything but processes all commands and component logic, useful for fast unit or integration tests.
* **Logging/Debugging**: An engine that logs all `DrawInstruction`s to a file for analysis.
* **Specialized Behavior**: Implement custom render loops, input handling, or command processing.
This guide will walk you through the process of implementing your own `Engine` and `CommandExecutor`.
## Implementing `CommandExecutor`
First, let's define a custom `CommandExecutor`. This trait is responsible for processing commands issued by components (e.g., `cx.stop()`).
```rust
use osui::prelude::*;
use std::{
any::Any,
sync::{Arc, Mutex},
};
// Define a custom command
#[derive(Debug, Clone)]
pub struct CustomCommand(pub String);
impl Command for CustomCommand {
fn as_any(&self) -> &dyn Any {
self
}
}
pub struct MyCustomExecutor {
running: Mutex<bool>,
received_commands: Mutex<Vec<CustomCommand>>,
}
impl MyCustomExecutor {
pub fn new() -> Arc<Self> {
Arc::new(Self {
running: Mutex::new(true),
received_commands: Mutex::new(Vec::new()),
})
}
pub fn stop_engine(&self) -> crate::Result<()> {
*self.running.lock()? = false;
Ok(())
}
pub fn get_status(&self) -> bool {
*self.running.lock().unwrap()
}
pub fn get_received_commands(&self) -> Vec<CustomCommand> {
self.received_commands.lock().unwrap().clone()
}
}
impl CommandExecutor for MyCustomExecutor {
fn execute_command(&self, command: &Arc<dyn Command>) -> crate::Result<()> {
let command_any = command.as_any();
// Handle built-in Stop command
if let Some(commands::Stop) = command_any.downcast_ref::<commands::Stop>() {
println!("MyCustomExecutor: Received Stop command.");
return self.stop_engine();
}
// Handle our custom command
if let Some(custom_cmd) = command_any.downcast_ref::<CustomCommand>() {
println!("MyCustomExecutor: Received CustomCommand: {:?}", custom_cmd);
self.received_commands.lock().unwrap().push(custom_cmd.clone());
return Ok(());
}
println!("MyCustomExecutor: Unhandled command.");
Ok(())
}
}
```
## Implementing `Engine`
Now, let's create a simple "headless" engine that doesn't draw to the terminal but just logs rendering events.
```rust
use osui::prelude::*;
use std::sync::Arc;
pub struct MyHeadlessEngine {
executor: Arc<MyCustomExecutor>,
log_output: Mutex<Vec<String>>,
}
impl MyHeadlessEngine {
pub fn new() -> Self {
Self {
executor: MyCustomExecutor::new(),
log_output: Mutex::new(Vec::new()),
}
}
// Helper to log messages
fn log(&self, msg: &str) {
self.log_output.lock().unwrap().push(msg.to_string());
}
pub fn get_log(&self) -> Vec<String> {
self.log_output.lock().unwrap().clone()
}
}
impl Engine for MyHeadlessEngine {
fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<()> {
self.log("Engine: Initializing component...");
let cx = self.init(component);
while self.executor.get_status() {
self.log("Engine: Starting render cycle...");
self.render(&cx);
self.log("Engine: Render cycle complete. Delaying...");
self.render_delay(); // Use default delay or implement custom
}
self.log("Engine: Application stopped.");
Ok(())
}
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context> {
// Perform any setup needed for your custom engine
self.log("Engine: Component initialized.");
let cx = Context::new(component, self.executor.clone());
cx.refresh(); // Initial render of the component
cx
}
fn render(&self, cx: &Arc<Context>) {
let area = Area { x: 0, y: 0, width: 80, height: 24 }; // Define a virtual screen size
let draw_ctx = self.render_view(&area, &cx.get_view());
self.draw_context(&draw_ctx);
}
// No actual delay for headless, or keep default for testing loop speed
fn render_delay(&self) {
// crate::sleep(16); // Uncomment for actual delay
}
fn render_view(&self, area: &Area, view: &View) -> DrawContext {
self.log(&format!("Engine: Rendering view in area: {:?}", area));
let mut context = DrawContext::new(area.clone());
view(&mut context); // Execute the View closure to populate DrawContext
context
}
fn draw_context(&self, ctx: &DrawContext) {
self.log(&format!("Engine: Drawing context with {} instructions.", ctx.drawing.len()));
for inst in &ctx.drawing {
match inst {
DrawInstruction::Text(point, text) => self.log(&format!(" Draw Text at {:?}: '{}'", point, text)),
DrawInstruction::View(area, view) => {
self.log(&format!(" Draw Child View in area: {:?}", area));
self.draw_context(&self.render_view(area, view)); // Recursively render child views
},
DrawInstruction::Child(point, child_ctx) => {
self.log(&format!(" Draw Child DrawContext at {:?}.", point));
self.draw_context(child_ctx); // Recursively draw child contexts
},
}
}
}
fn executor(&self) -> Arc<dyn CommandExecutor> {
self.executor.clone()
}
}
```
## Using Your Custom Engine
```rust
use osui::prelude::*;
use std::sync::Arc;
// (Include MyHeadlessEngine, MyCustomExecutor, CustomCommand definitions here)
#[component]
fn MyApp(cx: &Arc<Context>) -> View {
let counter = use_state(0);
use_effect(
{
let cx = cx.clone();
let counter = counter.clone();
move || {
// Periodically increment counter and emit custom command
loop {
sleep(200);
let new_val = *counter.get() + 1;
counter.set(new_val);
if new_val >= 3 {
cx.execute(CustomCommand(format!("Counter reached {}", new_val))).expect("Cmd failed");
cx.stop().expect("Stop failed");
break;
}
}
}
},
&[], // Run once on mount
);
rsx! {
format!("Counter value: {}", counter.get_dl())
}.view(&cx)
}
fn main() {
let my_engine = MyHeadlessEngine::new();
let executor = my_engine.executor.clone(); // Get a reference to the executor
my_engine.run(MyApp {}).expect("Failed to run custom engine");
println!("\n--- Engine Log ---");
for line in my_engine.get_log() {
println!("{}", line);
}
println!("\n--- Received Commands ---");
for cmd in executor.get_received_commands() {
println!("{:?}", cmd);
}
}
```
### Explanation:
1. **`MyCustomExecutor`**: Implements `CommandExecutor`. It handles the built-in `commands::Stop` and our new `CustomCommand`. It also keeps a log of received custom commands for verification.
2. **`MyHeadlessEngine`**: Implements `Engine`.
* It takes `MyCustomExecutor` as its command executor.
* `run` method establishes a basic loop that continues as long as `executor.get_status()` is `true`.
* `render` orchestrates the `render_view` and `draw_context` calls.
* `render_view` executes the component `View` and collects `DrawInstruction`s.
* `draw_context` iterates through `DrawInstruction`s, logging them instead of actually drawing to a terminal. It recursively handles `DrawInstruction::View` and `DrawInstruction::Child`.
3. **`MyApp` Component**:
* Uses `use_state` for a counter.
* Uses `use_effect` to periodically increment the counter.
* When the counter reaches 3, it `execute`s our `CustomCommand` and then `stop()`s the engine.
4. **`main` Function**:
* Instantiates `MyHeadlessEngine`.
* Calls `my_engine.run(MyApp {})`.
* After the engine stops, it prints the internal log and received commands from the executor, allowing you to verify that component logic and commands were processed correctly.
By following this pattern, you can integrate OSUI's powerful component and state management system with virtually any rendering or execution environment you desire.
**Next:** Dive into the internals of OSUI's macro system in [Internals: Macros](./02-internals-macros.md).
@@ -0,0 +1,130 @@
---
sidebar_position: 2
---
# Internals: Macros
OSUI heavily relies on procedural macros to provide its ergonomic, declarative syntax. The `osui-macros` crate contains the logic for the `#[component]` attribute macro and the `rsx!` function macro. Understanding how these macros work under the hood gives you a deeper insight into OSUI's architecture and capabilities.
## Introduction to Procedural Macros
Procedural macros are functions that operate on the Rust syntax tree (Abstract Syntax Tree or AST) during compilation. They receive `TokenStream`s as input and produce `TokenStream`s as output, effectively transforming your code. OSUI's macros leverage the following crates:
* **`syn`**: A parser for Rust's syntax tree. It allows macros to parse input `TokenStream`s into structured Rust AST types (like `ItemFn`, `Expr`, `Path`, etc.).
* **`quote`**: A quasiquoting library that makes it easy to generate Rust code (as `TokenStream`s) from AST fragments.
* **`proc-macro2`**: Provides types like `TokenStream` and `Ident` that are compatible with `syn` and `quote`, enabling ergonomic manipulation of tokens.
## `#[component]` Attribute Macro (`macros/src/lib.rs`)
The `#[component]` macro transforms a Rust function into an OSUI component struct.
### Input
It takes an `ItemFn` (the parsed function definition) as input.
```rust
// Original function in user's code
#[component]
pub fn MyComponent(cx: &Arc<Context>, prop1: &String, prop2: &i32) -> View {
// ... function body ...
}
```
### Core Logic
1. **Parse Function Signature**:
* It extracts the function's name (`MyComponent`).
* It validates the first argument `cx: &Arc<Context>`.
* It iterates through the remaining arguments (`prop1: &String`, `prop2: &i32`), which become the component's props.
2. **Generate Component Struct**: For each prop parameter, it determines the *owned* type (e.g., `&String` becomes `String`, `&i32` becomes `i32`). It then uses `quote!` to generate a new struct:
```rust
pub struct MyComponent {
pub prop1: String, // Owned types
pub prop2: i32,
}
```
3. **Implement `ComponentImpl`**: It then generates an `impl ComponentImpl for MyComponent` block. The `call` method of this trait:
* Takes `&self` and `cx: &Arc<Context>`.
* Internally calls a generated `Self::component` method (which is your original function's body).
* Passes `cx` and references (`&self.prop1`, `&self.prop2`) to the stored props from the generated struct.
```rust
impl MyComponent {
// This is your original function, renamed and wrapped
pub fn component(cx: &Arc<Context>, prop1: &str, prop2: &i32) -> View { /* ... body ... */ }
}
impl ComponentImpl for MyComponent {
fn call(&self, cx: &Arc<Context>) -> View {
Self::component(cx, &self.prop1, &self.prop2) // Pass references to owned props
}
}
```
### Output
The macro replaces the original function with the generated struct, its `component` method, and the `ComponentImpl` implementation. This transformation makes `MyComponent` a valid OSUI component that can be instantiated with props in `rsx!`.
## `rsx!` Function Macro (`macros/src/parse.rs` & `macros/src/emit.rs`)
The `rsx!` macro is more complex, involving a two-step process: parsing the custom syntax and then emitting standard Rust code.
### 1. Parsing (`macros/src/parse.rs`)
The `parse` module defines an AST (Abstract Syntax Tree) for the `rsx!` syntax.
* **`RsxRoot`**: The top-level container, holding a vector of `RsxNode`s.
* **`RsxNode`**: An enum representing different types of nodes in the `rsx!` tree:
* `Text(LitStr)`: For `"Hello"` literals.
* `Expr(Expr)`: For `@{some_expression}` blocks.
* `Component { path: Path, props: Vec<RsxProp>, children: Vec<RsxNode> }`: For `MyComponent { prop: val, ... }`.
* `Mount(Ident)`: For `!my_mount_hook`.
* `If { deps: Vec<Dep>, cond: Expr, children: Vec<RsxNode> }`: For `@if condition { ... }`.
* `For { deps: Vec<Dep>, pat: Pat, expr: Expr, children: Vec<RsxNode> }`: For `@for item in items { ... }`.
* **`RsxProp`**: Represents a `name: value` pair for component props.
* **`Dep`**: Represents a dependency for dynamic blocks (`%my_state as my_alias`).
The `parse::RsxRoot::parse` method uses `syn`'s `ParseStream` to tokenize the `rsx!` input and build this AST. It intelligently differentiates between text, expressions, component names, and control flow keywords (`@if`, `@for`, `!`).
### 2. Emitting (`macros/src/emit.rs`)
The `emit` module takes the parsed `RsxRoot` AST and converts it into a `TokenStream` of standard Rust code that constructs `osui::frontend::Rsx` objects.
* **`emit_rsx(root: RsxRoot)`**: The entry point, which initializes an `osui::frontend::Rsx` object and then iterates through the `root.nodes`.
* **`emit_node_scope(node: &RsxNode)`**: For each `RsxNode`, it generates code that calls the appropriate `Rsx` builder method:
* **`RsxNode::Text`**: Emits `r.static_scope(move |scope| { scope.view(...) });` which draws text.
* **`RsxNode::Expr`**: Emits `r.child(expression);`.
* **`RsxNode::Component`**: Emits `r.static_scope(move |scope| { scope.child(ComponentName { props: ... }, None); });`. If children are present, they are recursively emitted into a nested `Rsx` object and passed as the `children` prop.
* **`RsxNode::Mount`**: Emits `mount_hook_instance.mount();`.
* **`RsxNode::If`**: Emits `r.dynamic_scope(move |scope| { if condition { ... } else { ... } }, dependencies);`. The `dependencies` are converted to `Vec<Arc<dyn HookDependency>>`.
* **`RsxNode::For`**: Similar to `If`, emits `r.dynamic_scope(move |scope| { for pattern in expr { ... } }, dependencies);`.
### Output
The `rsx!` macro produces a `TokenStream` that looks something like this (simplified):
```rust
// For: rsx! { "Hello" MyComponent { prop: value } }
{
let mut r = osui::frontend::Rsx::new();
r.static_scope(move |scope| {
scope.view(std::sync::Arc::new(move |ctx| {
ctx.draw_text(osui::render::Point { x: 0, y: 0 }, &format!("Hello"))
}));
});
r.static_scope(move |scope| {
scope.child(
MyComponent { prop: value.to_string() }, // Note: Prop value converted to owned type
None,
);
});
r
}
```
This generated code, when compiled, constructs the `osui::frontend::Rsx` object that OSUI's runtime can then interpret to build the component tree and render the UI.
## Summary
The `osui-macros` crate plays a pivotal role in shaping OSUI's developer experience. `#[component]` streamlines component definition, while `rsx!` provides a powerful, declarative way to compose UIs by transforming custom syntax into efficient runtime calls. These macros are complex but essential for creating a modern, React-like development flow in a TUI environment.
**Next:** Learn how you can contribute to the OSUI project in [Contributing](./03-contributing.md).
@@ -0,0 +1,115 @@
---
sidebar_position: 3
title: Contributing
---
# Contributing to OSUI
We welcome and appreciate contributions from the community! Whether it's reporting bugs, suggesting features, improving documentation, or submitting code, your help makes OSUI better for everyone.
## How to Contribute
### 1. Report Bugs
If you find a bug, please open an issue on our [GitHub repository](https://github.com/osui-rs/osui/issues). When reporting a bug, please include:
* A clear and concise description of the bug.
* Steps to reproduce the behavior.
* Expected behavior.
* Actual behavior.
* Your OSUI version, Rust version, and operating system.
* Any relevant code snippets or error messages.
### 2. Suggest Features
Have an idea for a new feature or improvement? Feel free to open an issue on GitHub to discuss it. Please provide:
* A clear and concise description of the proposed feature.
* Why you think it would be valuable to OSUI.
* Any potential use cases or examples.
### 3. Improve Documentation
Good documentation is vital for any library. If you find errors, omissions, or areas that could be explained more clearly in our docs, please consider:
* Opening an issue to point out the specific area.
* Submitting a pull request with your suggested improvements.
### 4. Contribute Code
If you'd like to contribute code, here's the general workflow:
#### Fork the Repository
First, fork the [osui-rs/osui](https://github.com/osui-rs/osui) repository to your own GitHub account.
#### Clone Your Fork
```bash
git clone https://github.com/YOUR_USERNAME/osui.git
cd osui
```
#### Create a New Branch
Create a new branch for your feature or bug fix. Use a descriptive name:
```bash
git checkout -b feature/my-awesome-feature
# or
git checkout -b bugfix/fix-rendering-issue
```
#### Make Your Changes
* Write clean, idiomatic Rust code.
* Follow existing code style and conventions.
* Add comments where necessary to explain complex logic.
* **Write Tests**: If you're adding new features or fixing bugs, please include appropriate unit and/or integration tests to cover your changes.
* **Update Documentation**: If your changes affect the public API or add new functionality, please update the relevant documentation files.
#### Run Tests
Before submitting a pull request, ensure all existing tests pass and your new tests pass:
```bash
cargo test --workspace
```
#### Format and Lint
Make sure your code is formatted correctly and passes lint checks:
```bash
cargo fmt --all
cargo clippy --all-targets --all-features
```
#### Commit Your Changes
Commit your changes with clear and concise commit messages. A good commit message explains *what* changed and *why*.
```bash
git commit -m "feat: Add new awesome feature"
# or
git commit -m "fix: Resolve rendering issue on Windows"
```
#### Push to Your Fork
```bash
git push origin feature/my-awesome-feature
```
#### Create a Pull Request
Go to the [osui-rs/osui](https://github.com/osui-rs/osui) repository on GitHub and open a new pull request.
* Provide a clear title and description for your pull request.
* Reference any related issues (e.g., "Fixes #123" or "Closes #456").
* The project maintainers will review your PR, provide feedback, and work with you to get it merged.
## Code of Conduct
Please note that this project is released with a [Contributor Code of Conduct](https://github.com/osui-rs/osui/blob/main/CODE_OF_CONDUCT.md). By participating in this project, you agree to abide by its terms.
Thank you for considering contributing to OSUI! Your efforts help foster a vibrant and robust TUI ecosystem in Rust.
Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

@@ -0,0 +1,8 @@
{
"docsSidebar": [
{
"type": "autogenerated",
"dirName": "."
}
]
}
+1
View File
@@ -1,4 +1,5 @@
[
"0.2.0",
"0.1.1",
"0.1.0",
"0.0.9",