Version 0.1.1
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
---
|
||||
slug: /
|
||||
---
|
||||
|
||||
# Getting Started with OSUI
|
||||
|
||||
This guide will walk you through setting up your Rust project to use OSUI and creating your first interactive terminal application.
|
||||
|
||||
## 1. Project Setup
|
||||
|
||||
First, create a new Rust project (if you haven't already):
|
||||
|
||||
```bash
|
||||
cargo new my-osui-app
|
||||
cd my-osui-app
|
||||
```
|
||||
|
||||
## 2. Add OSUI to Your `Cargo.toml`
|
||||
|
||||
Open your `Cargo.toml` file and add `osui` to your `[dependencies]` section. You can also specify a particular version or use `*` for the latest compatible version.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[package]
|
||||
name = "my-osui-app"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
osui = "0.1.1" # Use the latest version available on crates.io
|
||||
crossterm = "0.28.1" # OSUI internally uses crossterm for terminal interactions
|
||||
figlet-rs = "0.1.5" # Optional: for Heading element (if you need it)
|
||||
```
|
||||
|
||||
The `crossterm` and `figlet-rs` dependencies are automatically included by OSUI, but explicitly listing them doesn't hurt. OSUI uses `crossterm` for low-level terminal control and `figlet-rs` for the `Heading` element, which can render ASCII art text.
|
||||
|
||||
## 3. Basic OSUI Application
|
||||
|
||||
Now, let's create a minimal OSUI application. Open `src/main.rs` and replace its contents with the following:
|
||||
|
||||
```rust
|
||||
use osui::prelude::*; // Import all necessary OSUI items
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
// Initialize the main screen abstraction.
|
||||
// The Screen manages all widgets and extensions.
|
||||
let screen = Screen::new();
|
||||
|
||||
// Register essential extensions:
|
||||
// InputExtension: Handles keyboard input and dispatches events.
|
||||
screen.extension(InputExtension);
|
||||
// RelativeFocusExtension: Manages focus between widgets based on relative position,
|
||||
// enabling navigation with arrow keys (e.g., in Flex layouts).
|
||||
screen.extension(RelativeFocusExtension::new());
|
||||
|
||||
// Define your UI declaratively using the rsx! macro.
|
||||
// This example creates a simple text string.
|
||||
rsx! {
|
||||
"Hello, OSUI!"
|
||||
}
|
||||
// Draw the defined UI tree onto the screen.
|
||||
.draw(&screen);
|
||||
|
||||
// Start the main event loop. This blocks until the application is closed.
|
||||
// It handles rendering, event processing, and extension updates.
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Run Your Application
|
||||
|
||||
Save the file and run your application from the terminal:
|
||||
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
You should see "Hello, OSUI!" displayed in your terminal. You can press `Ctrl+C` or `Esc` (if you add an event handler for `Esc` like in the [Demo Application Guide](../guides/building-a-demo-app.md)) to exit the application.
|
||||
|
||||
## Next Steps
|
||||
|
||||
* **[Concepts: Widget Model](../concepts/widget-model.md)**: Understand the core building blocks of OSUI: Elements, Components, and Widgets.
|
||||
* **[Guides: Layout and Styling](../guides/layout-and-styling.md)**: Learn how to position and style your UI elements.
|
||||
* **[Guides: Common Elements](../guides/common-elements.md)**: Explore the built-in UI elements provided by OSUI like Divs, Flex containers, and Input fields.
|
||||
* **[Guides: State and Reactivity](../guides/state-and-reactivity.md)**: Discover how to make your UI dynamic and responsive to data changes.
|
||||
@@ -0,0 +1,42 @@
|
||||
# OSUI: A Rust Terminal User Interface Library
|
||||
|
||||
OSUI is a powerful and flexible library for building interactive and customizable terminal user interfaces (TUIs) in Rust. It provides a declarative component system, real-time keyboard input handling, and a JSX-like `rsx!` macro for defining UI elements, making TUI development intuitive and efficient.
|
||||
|
||||
## Key Features
|
||||
|
||||
* **Declarative UI with `rsx!`**: Define your user interfaces using a familiar, expressive syntax similar to JSX, enhancing readability and maintainability.
|
||||
* **Component-Based Design**: Build complex UIs from reusable, self-contained widgets and components, promoting modularity and reusability.
|
||||
* **Reactive State Management**: Integrate dynamic behavior into your widgets with a built-in state management system that automatically triggers UI updates when data changes.
|
||||
* **Flexible Layout and Styling**: Control widget positioning, dimensions, padding, margins, and visual appearance with a comprehensive styling API.
|
||||
* **Extensible Architecture**: Customize and extend OSUI's core functionality through a robust extension system, allowing you to add custom behaviors, event handlers, and rendering logic.
|
||||
* **Real-time Interaction**: Seamlessly handle keyboard inputs to create responsive and interactive command-line applications.
|
||||
* **Virtual Screen Abstraction**: OSUI manages the underlying terminal drawing, providing a high-level abstraction for rendering your UI elements efficiently.
|
||||
|
||||
## Quick Example
|
||||
|
||||
Get started quickly with a simple "Hello, World!" example:
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
// 1. Create a new Screen instance, which manages the TUI.
|
||||
let screen = Screen::new();
|
||||
|
||||
// 2. Define your UI using the rsx! macro.
|
||||
// "👋 Hello, World!" is a simple text element.
|
||||
rsx! {
|
||||
"👋 Hello, World!"
|
||||
}
|
||||
// 3. Draw the defined UI onto the screen.
|
||||
.draw(&screen);
|
||||
|
||||
// 4. Run the main rendering loop. This will keep the TUI active
|
||||
// and handle rendering and events until the application exits.
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
|
||||
This example initializes a `Screen`, uses the `rsx!` macro to define a basic text element, draws it to the screen, and then starts the rendering loop.
|
||||
|
||||
For more detailed information on installation and setting up your project, refer to the [Getting Started Guide](../intro/getting-started.md). For comprehensive API details, explore the [Reference section](../reference/index.md).
|
||||
@@ -0,0 +1,140 @@
|
||||
# Building a Comprehensive Demo Application
|
||||
|
||||
This guide walks through the `src/demos/mod.rs` file provided in the OSUI source, explaining how various features are combined to create a multi-page interactive application. This demo showcases reactive state, input handling, custom elements, and layout components.
|
||||
|
||||
## Overview of `src/demos/mod.rs`
|
||||
|
||||
The `demos::app` function returns an `Rsx` tree, which is the declarative representation of our UI. This `Rsx` tree is then drawn to the `Screen`.
|
||||
|
||||
```rust
|
||||
// src/demos/mod.rs
|
||||
use std::sync::Arc;
|
||||
use osui::prelude::*;
|
||||
|
||||
pub fn app(screen: Arc<Screen>) -> Rsx {
|
||||
// 1. Reactive State
|
||||
let count = use_state(0);
|
||||
|
||||
// Spawn a background thread to increment the counter
|
||||
std::thread::spawn({
|
||||
let count = count.clone();
|
||||
move || loop {
|
||||
**count.get() += 1; // Increment and mark as changed
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Main UI Definition
|
||||
rsx! {
|
||||
// Global event handler for Escape key to close the app
|
||||
@Handler::new({
|
||||
let screen = screen.clone();
|
||||
move |_, e: &crossterm::event::Event| {
|
||||
if let crossterm::event::Event::Key(crossterm::event::KeyEvent { code, .. }) = e {
|
||||
if *code == crossterm::event::KeyCode::Esc {
|
||||
screen.close();
|
||||
}
|
||||
}
|
||||
}});
|
||||
// Keep the root Paginator widget always focused
|
||||
@AlwaysFocused;
|
||||
Paginator { // Main page manager
|
||||
// --- Page 1: Welcome and Instructions ---
|
||||
FlexRow { // Horizontal layout for elements on this page
|
||||
Heading, smooth: false, { "OSUI" } // Large ASCII art title
|
||||
"Welcome to the OSUI demo!"
|
||||
"Press tab to switch to the next page or shift+tab to the previous page"
|
||||
}
|
||||
|
||||
// --- Page 2: Divs with different outlines ---
|
||||
FlexCol, gap: 3, { // Vertical layout for elements on this page
|
||||
@Transform::new().padding(2, 2);
|
||||
@Style { foreground: None, background: Background::RoundedOutline(0x00ff00) };
|
||||
Div {
|
||||
"This is text inside a div with rounded outlines"
|
||||
}
|
||||
|
||||
@Transform::new().padding(2, 2);
|
||||
@Style { foreground: None, background: Background::Outline(0x00ff00) };
|
||||
Div {
|
||||
"This is text inside a div with square outlines"
|
||||
}
|
||||
}
|
||||
|
||||
// --- Page 3: Reactive Counter & Input Fields ---
|
||||
FlexRow, gap: 1, {
|
||||
%count // This section depends on the 'count' state
|
||||
"This will increment every second: {count}"
|
||||
|
||||
FlexRow { // Nested FlexRow for Username input
|
||||
"Username"
|
||||
@Transform::new().padding(1, 1).dimensions(40, 1);
|
||||
@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xff0000) };
|
||||
@Focused; // Initially focus this input field
|
||||
Input { }
|
||||
}
|
||||
|
||||
@Transform::new().margin(0, 1); // Add a margin below previous element
|
||||
FlexRow { // Nested FlexRow for Password input
|
||||
"Password"
|
||||
@Transform::new().padding(1, 1).dimensions(40, 1);
|
||||
@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xffff00) };
|
||||
Input { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dissecting the Demo
|
||||
|
||||
### 1. Central `Screen` and Extensions
|
||||
|
||||
* In `src/main.rs`, the `Screen` is initialized, and `InputExtension` and `RelativeFocusExtension` are registered.
|
||||
* `InputExtension` is vital as it enables raw mode terminal input and dispatches `crossterm::event::Event`s throughout the OSUI system. Without it, keyboard input won't work.
|
||||
* `RelativeFocusExtension` manages which widget is "focused," which is essential for `Input` elements to receive typing events. It also provides navigation between focusable elements (by default, using arrow keys when Shift is held).
|
||||
* The `demos::app(screen.clone()).draw(&screen);` line is the entry point for rendering the entire UI structure.
|
||||
|
||||
### 2. Global Event Handling (`@Handler` and `Esc` Key)
|
||||
|
||||
* The very first element in `rsx!` (outside the `Paginator`) is a `Handler` component attached to an implicit root widget.
|
||||
* `@Handler::new(...)` creates an event handler that listens for `crossterm::event::Event`s.
|
||||
* The closure checks if the event is a `KeyCode::Esc` key press. If so, it calls `screen.close()`, which gracefully exits the OSUI application, restoring terminal state.
|
||||
* `@AlwaysFocused` ensures this root handler always receives events, regardless of which sub-widget might have specific input focus.
|
||||
|
||||
### 3. Page Navigation with `Paginator`
|
||||
|
||||
* The top-level element is a `Paginator`. This element acts as a simple tab-like interface.
|
||||
* Each direct child of `Paginator` becomes a "page." The `Paginator` displays only one child at a time.
|
||||
* Pressing `Tab` (or `Shift+Tab`) cycles through the `Paginator`'s children, switching the active page. This behavior is built into the `Paginator` element's `event` method.
|
||||
|
||||
### 4. Layout with `FlexRow` and `FlexCol`
|
||||
|
||||
* Each "page" within the `Paginator` uses either `FlexRow` or `FlexCol` to organize its content.
|
||||
* `FlexRow { ... }`: Arranges children horizontally. Used for the welcome message and the counter/input fields.
|
||||
* `FlexCol, gap: 3, { ... }`: Arranges children vertically. Used for the Div examples, with a `gap` of 3 cells between each child.
|
||||
* These flex containers automatically calculate their own size based on their children and distribute space. They are "ghost" elements, meaning they don't draw anything themselves but define layout for their children.
|
||||
|
||||
### 5. Styling and Layout Components (`@Transform`, `@Style`)
|
||||
|
||||
* **`@Transform::new().padding(2, 2)`**: Sets internal spacing of 2 cells on all sides for the `Div` elements.
|
||||
* **`@Style { background: Background::RoundedOutline(0x00ff00) }`**: Applies a green rounded outline background to the first `Div`. `0x00ff00` is a 24-bit RGB hexadecimal color code.
|
||||
* **`@Style { background: Background::Outline(0x00ff00) }`**: Applies a green square outline background to the second `Div`.
|
||||
* **`@Transform::new().dimensions(40, 1)`**: Sets the `Input` fields to a fixed width of 40 cells and a height of 1 cell.
|
||||
* **`@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xff0000) }`**: Styles the username input with white foreground text and a red rounded outline.
|
||||
|
||||
### 6. Reactive State and Text Interpolation (`%count`)
|
||||
|
||||
* `let count = use_state(0);` initializes a reactive state variable.
|
||||
* The `std::thread::spawn` block modifies `count` every second (`**count.get() += 1;`). This modification automatically marks `count` as "changed."
|
||||
* `%count` (on the `FlexRow` containing the counter text) declares a dependency. When `count` changes, this `FlexRow` (and its children, including the text) is rebuilt and re-rendered, displaying the updated value.
|
||||
* The text `"This will increment every second: {count}"` directly interpolates the `count` state's value. This works because `State<T>` implements `Display`.
|
||||
|
||||
### 7. Interactive Input (`Input` Element)
|
||||
|
||||
* `Input { }` creates a text input field.
|
||||
* `@Focused` on the "Username" `Input` ensures it receives keyboard focus when that page is active, allowing the user to type immediately. The `RelativeFocusExtension` helps manage this focus.
|
||||
* When an `Input` is focused, typing characters modifies its internal `State<String>`, and it redraws to show the updated text and cursor.
|
||||
|
||||
This demo illustrates a comprehensive use of OSUI's features, demonstrating how declarative UI, reactive state, event handling, and layout work together to build a functional TUI application.
|
||||
@@ -0,0 +1,165 @@
|
||||
# Common Built-in Elements
|
||||
|
||||
OSUI provides a set of essential UI elements that serve as building blocks for your terminal interfaces. These elements range from simple text to complex layout containers and interactive input fields.
|
||||
|
||||
## 1. Text (`String` Element)
|
||||
|
||||
The simplest element is a `String`. Any string literal or `format!` macro output directly within `rsx!` will be treated as a renderable text element.
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
"Hello, World!" // Renders "Hello, World!"
|
||||
format!("The current count is: {}", 123) // Dynamic text
|
||||
}
|
||||
```
|
||||
**Behavior**: Draws the string at its calculated position. It's a "ghost" element (`is_ghost` returns `true`), meaning it primarily contributes content for layout but doesn't have its own background or border (unless a `Style` component is explicitly attached to its parent that applies to its area). Its size contributes to `Dimension::Content` calculations of its parent.
|
||||
|
||||
## 2. `Div` (Generic Container)
|
||||
|
||||
The `Div` element is a versatile, transparent container. It doesn't draw anything itself but is crucial for grouping other elements and applying layout and style components to a collection of children.
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
@Transform::new().padding(2, 2);
|
||||
@Style { background: Background::Solid(0x222222) };
|
||||
Div {
|
||||
"This text is inside a gray Div with padding."
|
||||
FlexRow { "Another", "element", "can", "be", "nested" }
|
||||
}
|
||||
}
|
||||
```
|
||||
**Behavior**: `Div` is a "ghost" element. Its `after_render` method iterates through its children and renders them. It automatically calculates its own `width` and `height` to encompass all its children, plus any `padding` specified in its `Transform`.
|
||||
|
||||
## 3. Flex Containers (`FlexRow` and `FlexCol`)
|
||||
|
||||
Flex containers are powerful layout elements that automatically arrange their children either horizontally (`FlexRow`) or vertically (`FlexCol`), with optional gaps between them.
|
||||
|
||||
### `FlexRow`
|
||||
|
||||
Arranges children in a row (horizontally).
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
@Transform::new().padding(1, 1).dimensions(Full, Content);
|
||||
@Style { background: Background::Outline(0x00FF00) };
|
||||
FlexRow, gap: 2, { // `gap` property sets spacing between children
|
||||
"Item 1"
|
||||
"Item 2"
|
||||
@Style { foreground: Some(0xFFFF00) };
|
||||
"Item 3 (Yellow)"
|
||||
Div { "Nested div as item 4" }
|
||||
}
|
||||
}
|
||||
```
|
||||
**Behavior**: `FlexRow` is a "ghost" element. Its `after_render` dynamically positions each child next to the previous one, accounting for the `gap` property. The `FlexRow`'s `width` will be the sum of its children's widths (plus gaps and padding), and its `height` will be the height of its tallest child.
|
||||
|
||||
### `FlexCol`
|
||||
|
||||
Arranges children in a column (vertically).
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
@Transform::new().padding(1, 1).dimensions(Content, Full);
|
||||
@Style { background: Background::Outline(0xFF00FF) };
|
||||
FlexCol, gap: 1, {
|
||||
"First Line"
|
||||
"Second Line"
|
||||
Input { } // An input field on a new line
|
||||
Div { "A div below the input" }
|
||||
}
|
||||
}
|
||||
```
|
||||
**Behavior**: `FlexCol` is a "ghost" element. Its `after_render` dynamically positions each child below the previous one, accounting for the `gap` property. The `FlexCol`'s `height` will be the sum of its children's heights (plus gaps and padding), and its `width` will be the width of its widest child.
|
||||
|
||||
## 4. `Heading` (ASCII Art Text)
|
||||
|
||||
The `Heading` element uses the `figlet-rs` library to render large, ASCII art text. It's great for titles and banners.
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
// Heading with default (standard) font
|
||||
Heading { "OSUI" }
|
||||
|
||||
// Heading with smooth characters (replaces hyphens and pipes)
|
||||
Heading, smooth: true, { "Awesome" }
|
||||
}
|
||||
```
|
||||
**Properties**:
|
||||
* `font: FIGfont`: The font to use (defaults to `standard`). You can load other FIGlet fonts.
|
||||
* `smooth: bool`: If `true`, replaces common characters like `-` and `|` with Unicode line drawing characters for a smoother appearance.
|
||||
|
||||
**Behavior**: `Heading` is a "ghost" element. It takes a single `String` child (or any element that can be downcast to `String`) and converts it to ASCII art before drawing it. Its size contributes to its parent's `Dimension::Content` calculation.
|
||||
|
||||
## 5. `Input` (Text Input Field)
|
||||
|
||||
The `Input` element provides a basic interactive text input field.
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
FlexCol, gap: 1, {
|
||||
"Enter Username:"
|
||||
@Transform::new().dimensions(30, 1).padding(0,0);
|
||||
@Style { background: Background::Outline(0xAAAAAA) };
|
||||
@Focused; // Mark this input as initially focused for keyboard interaction
|
||||
Input { }
|
||||
|
||||
"Enter Password:"
|
||||
@Transform::new().dimensions(30, 1).padding(0,0);
|
||||
@Style { background: Background::Outline(0xAAAAAA) };
|
||||
Input { } // This input will not be focused initially
|
||||
}
|
||||
}
|
||||
```
|
||||
**Properties**:
|
||||
* `state: State<String>`: The internal `State` that holds the input string. You can access this `State` to get or set the input value.
|
||||
* `cursor: usize`: Internal cursor position within the input string.
|
||||
|
||||
**Behavior**:
|
||||
* When focused (via the `@Focused` component or navigation), it captures keyboard input.
|
||||
* Supports typing characters, `Backspace`, `Delete`, `Left` arrow, `Right` arrow.
|
||||
* The currently typed text is drawn, and a "inverted" character at the cursor position indicates focus.
|
||||
* The `Input` element automatically creates its own internal `State<String>`. To access this state, you would need to store a reference to the `Input` element (e.g., if you were creating it programmatically without `rsx!`) or manage your own state and inject it.
|
||||
|
||||
## 6. `Paginator` (Page Navigation)
|
||||
|
||||
The `Paginator` element manages a collection of child widgets, displaying only one at a time and allowing navigation between them (typically with Tab/Shift+Tab).
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
@Transform::new().center().dimensions(50, 20); // Paginator needs fixed size to contain pages
|
||||
@Style { background: Background::Solid(0x333333) };
|
||||
Paginator {
|
||||
// First page content
|
||||
FlexCol { "This is Page 1" "Press TAB for next page, Shift+TAB for previous" }
|
||||
|
||||
// Second page content
|
||||
Div { "This is Page 2, with some input:" Input { } }
|
||||
|
||||
// Third page content
|
||||
Heading { "Last Page!" }
|
||||
}
|
||||
}
|
||||
```
|
||||
**Properties**:
|
||||
* `index: usize`: The current page index being displayed (defaults to 0).
|
||||
|
||||
**Behavior**:
|
||||
* `Paginator` is a "ghost" element. It draws only its child at the current `index`.
|
||||
* It listens for `KeyCode::Tab` and `KeyCode::BackTab` events to cycle through its children.
|
||||
* Its `width` and `height` are determined by its currently displayed child.
|
||||
|
||||
These built-in elements provide a solid foundation for building diverse and interactive terminal user interfaces with OSUI.
|
||||
@@ -0,0 +1,215 @@
|
||||
# Creating Custom Widgets
|
||||
|
||||
While OSUI provides a rich set of built-in elements, you'll often need to create your own custom widgets to encapsulate specific UI logic or appearance. This guide walks you through implementing the `Element` trait and integrating your custom widget into the OSUI ecosystem.
|
||||
|
||||
## The `Element` Trait Revisited
|
||||
|
||||
As discussed in [Concepts: Widget Model](../concepts/widget-model.md), the `Element` trait is the contract for anything that can be rendered.
|
||||
|
||||
```rust
|
||||
pub trait Element: Send + Sync {
|
||||
// Draw commands for the element itself
|
||||
fn render(&mut self, scope: &mut RenderScope, render_context: &RenderContext);
|
||||
|
||||
// Logic after self-rendering, typically for rendering children
|
||||
fn after_render(&mut self, scope: &mut RenderScope, render_context: &RenderContext);
|
||||
|
||||
// Used by rsx! to register children
|
||||
fn draw_child(&mut self, element: &Arc<Widget>);
|
||||
|
||||
// Handle incoming events
|
||||
fn event(&mut self, event: &dyn Event);
|
||||
|
||||
// Is this element purely a logical/layout container (e.g., Div, FlexRow)?
|
||||
fn is_ghost(&mut self) -> bool;
|
||||
|
||||
// Required for downcasting, usually implemented boilerplate
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
```
|
||||
|
||||
## Example: A Simple `ClickableBox`
|
||||
|
||||
Let's create a box that displays a message and changes its background color when clicked.
|
||||
|
||||
### 1. Define the Element Struct
|
||||
|
||||
Our `ClickableBox` will hold its message, current color, and a list of children.
|
||||
|
||||
```rust
|
||||
// src/elements/clickable_box.rs (or anywhere you organize your custom elements)
|
||||
|
||||
use std::sync::Arc;
|
||||
use crossterm::event::{Event as CrosstermEvent, MouseEvent, MouseEventKind};
|
||||
use crate::{prelude::*, NoRenderRoot}; // Import necessary OSUI items
|
||||
|
||||
pub struct ClickableBox {
|
||||
message: String,
|
||||
current_color: u32,
|
||||
default_color: u32,
|
||||
clicked_color: u32,
|
||||
children: Vec<Arc<Widget>>, // To hold potential nested elements
|
||||
// We'll track the size it renders to, important for parents
|
||||
size: (u16, u16),
|
||||
}
|
||||
|
||||
impl ClickableBox {
|
||||
pub fn new(message: &str) -> Self {
|
||||
Self {
|
||||
message: message.to_string(),
|
||||
current_color: 0x0055AA, // Default blue
|
||||
default_color: 0x0055AA,
|
||||
clicked_color: 0xAA5500, // Orange when clicked
|
||||
children: Vec::new(),
|
||||
size: (0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// A method to change color, useful for event handlers
|
||||
pub fn set_clicked(&mut self, clicked: bool) {
|
||||
self.current_color = if clicked { self.clicked_color } else { self.default_color };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Implement the `Element` Trait
|
||||
|
||||
Now, let's implement the core `Element` trait methods.
|
||||
|
||||
```rust
|
||||
// Continue in src/elements/clickable_box.rs
|
||||
|
||||
impl Element for ClickableBox {
|
||||
fn render(
|
||||
&mut self,
|
||||
scope: &mut RenderScope,
|
||||
render_context: &RenderContext,
|
||||
) {
|
||||
// Draw the background rectangle based on current_color
|
||||
scope.draw_rect(0, 0, scope.get_transform().width, scope.get_transform().height, self.current_color);
|
||||
|
||||
// Draw the message text, centered
|
||||
let (msg_w, msg_h) = utils::str_size(&self.message);
|
||||
let text_x = (scope.get_transform().width.saturating_sub(msg_w)) / 2;
|
||||
let text_y = (scope.get_transform().height.saturating_sub(msg_h)) / 2;
|
||||
scope.draw_text_colored(text_x, text_y, &self.message, 0xFFFFFF); // White text
|
||||
|
||||
// Use the area based on the message size if dimensions are `Content`
|
||||
scope.use_area(msg_w, msg_h);
|
||||
|
||||
// Update the size for the after_render pass and parent's layout
|
||||
self.size = (scope.get_transform().width, scope.get_transform().height);
|
||||
}
|
||||
|
||||
fn after_render(
|
||||
&mut self,
|
||||
scope: &mut RenderScope,
|
||||
render_context: &RenderContext,
|
||||
) {
|
||||
// This element is a container, so we need to render its children.
|
||||
// We'll pass them a new RenderScope nested within this box's area.
|
||||
let mut transform = scope.get_transform().clone();
|
||||
let mut child_renderer = DivRenderer(&mut transform); // Use DivRenderer helper for children
|
||||
|
||||
let (parent_w, parent_h) = scope.get_parent_size(); // Store parent size
|
||||
scope.set_parent_size(self.size.0, self.size.1); // Set current element's size as parent for children
|
||||
|
||||
for widget in &self.children {
|
||||
// Render each child widget using the context and the specialized renderer
|
||||
scope.render_widget(&mut child_renderer, render_context.get_context(), widget);
|
||||
}
|
||||
|
||||
// Restore parent size for subsequent sibling elements
|
||||
scope.set_parent_size(parent_w, parent_h);
|
||||
// The `DivRenderer` updates `transform.width/height` to encompass children.
|
||||
// We need to propagate this up to our element's own calculated size.
|
||||
self.size = (transform.width, transform.height);
|
||||
}
|
||||
|
||||
fn event(&mut self, event: &dyn Event) {
|
||||
// Listen for Crossterm Mouse Events
|
||||
if let Some(crossterm_event) = event.get::<CrosstermEvent>() {
|
||||
if let CrosstermEvent::Mouse(MouseEvent { kind: MouseEventKind::Down(_), column, row, .. }) = crossterm_event {
|
||||
// Check if the click occurred within our widget's bounds
|
||||
// This would require knowing the widget's absolute position on screen.
|
||||
// For simplicity here, we'll just toggle color on any click to demonstrate.
|
||||
// In a real app, you'd get the widget's RawTransform from an extension
|
||||
// or a global layout manager to check bounds.
|
||||
self.set_clicked(!self.current_color == self.clicked_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_child(&mut self, element: &Arc<Widget>) {
|
||||
// When a child is added (e.g., via rsx! nesting), store it.
|
||||
// Inject NoRenderRoot so OSUI's main loop doesn't try to render it directly.
|
||||
element.inject(|w| w.component(NoRenderRoot));
|
||||
self.children.push(element.clone());
|
||||
}
|
||||
|
||||
fn is_ghost(&mut self) -> bool {
|
||||
// This element draws its own background, so it's not a ghost.
|
||||
false
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Integrate with `mod.rs` (Optional but Recommended)
|
||||
|
||||
Add your new element to `src/elements/mod.rs` so it's easily accessible via `osui::prelude::*`.
|
||||
|
||||
```rust
|
||||
// src/elements/mod.rs (partial)
|
||||
pub mod div;
|
||||
// ... other elements
|
||||
pub mod clickable_box; // Your new module!
|
||||
|
||||
pub use div::*;
|
||||
// ... other elements
|
||||
pub use clickable_box::*; // Export it for prelude
|
||||
// ... String impl (already there)
|
||||
```
|
||||
|
||||
### 4. Use Your Custom Widget in `rsx!`
|
||||
|
||||
Now you can use `ClickableBox` just like any other built-in element:
|
||||
|
||||
```rust
|
||||
// src/main.rs (or your demo app)
|
||||
use osui::prelude::*;
|
||||
use osui::elements::ClickableBox; // Explicitly import if not using prelude
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
screen.extension(InputExtension); // For mouse events
|
||||
screen.extension(RelativeFocusExtension::new()); // Optional, but good practice
|
||||
// You'd also need a MouseExtension if you want raw mouse events on widgets.
|
||||
// For this basic example, `crossterm::event::Event` captures all.
|
||||
|
||||
rsx! {
|
||||
// A ClickableBox with fixed dimensions and some nested text
|
||||
@Transform::new().dimensions(30, 5).center();
|
||||
ClickableBox::new("Click Me!") {
|
||||
// Nested content will be drawn by ClickableBox's after_render
|
||||
@Transform::new().y(3); // Position child text within the box
|
||||
"Nested content inside the box"
|
||||
}
|
||||
}
|
||||
.draw(&screen);
|
||||
|
||||
screen.run()?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
**Note on mouse events**: For a true click-detection, you'd need to compare `MouseEvent.column` and `MouseEvent.row` against the widget's actual rendered `RawTransform` coordinates. This typically involves an extension collecting widget positions or a global event dispatcher that routes events to widgets based on their bounds. The `Input` element handles focus and key events because it's built to capture those when focused.
|
||||
|
||||
By following this pattern, you can extend OSUI with a wide variety of custom UI components tailored to your application's specific needs.
|
||||
@@ -0,0 +1,116 @@
|
||||
# Extending OSUI
|
||||
|
||||
OSUI's architecture is designed for extensibility, allowing you to add custom behaviors, event listeners, and rendering logic through the `Extension` trait. Extensions are global listeners and modifiers that hook into the OSUI application lifecycle.
|
||||
|
||||
## The `Extension` Trait
|
||||
|
||||
The core of OSUI's extensibility is the `Extension` trait:
|
||||
|
||||
```rust
|
||||
pub trait Extension {
|
||||
fn init(&mut self, _ctx: &Context) {}
|
||||
fn event(&mut self, _ctx: &Context, _event: &dyn Event) {}
|
||||
fn on_close(&mut self) {}
|
||||
fn render(&mut self, _ctx: &Context, _scope: &mut RenderScope) {}
|
||||
fn render_widget(&mut self, _ctx: &Context, _scope: &mut RenderScope, _widget: &Arc<Widget>) {}
|
||||
fn after_render_widget(
|
||||
&mut self,
|
||||
_ctx: &Context,
|
||||
_scope: &mut RenderScope,
|
||||
_widget: &Arc<Widget>,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
Each method provides a hook into a specific part of the OSUI application lifecycle:
|
||||
|
||||
* **`init(&mut self, ctx: &Context)`**: Called once when the extension is registered with the `Screen` and before the main rendering loop starts. Use this for setup tasks, like spawning background threads or initializing global resources.
|
||||
* **`event(&mut self, ctx: &Context, event: &dyn Event)`**: Called whenever any `Event` is dispatched across the OSUI system. This is where extensions can listen for and react to keyboard input, custom events, or events from other extensions.
|
||||
* **`on_close(&mut self)`**: Called when `screen.close()` is invoked. Use this for cleanup, like disabling raw terminal mode or saving state.
|
||||
* **`render(&mut self, ctx: &Context, scope: &mut RenderScope)`**: Called *before* any individual widgets are rendered for a given frame. This `RenderScope` represents the *entire screen*. Useful for drawing global elements or backgrounds that span the whole terminal.
|
||||
* **`render_widget(&mut self, ctx: &Context, scope: &mut RenderScope, widget: &Arc<Widget>)`**: Called *before* a specific `widget`'s `Element::render` method is called. Extensions can inject drawing commands into the `scope` or modify the `widget` at this stage.
|
||||
* **`after_render_widget(&mut self, ctx: &Context, scope: &mut RenderScope, widget: &Arc<Widget>)`**: Called *after* a specific `widget`'s `Element::after_render` method and its drawing commands have been processed. Useful for post-processing, debugging, or recording widget positions (e.g., `RelativeFocusExtension`).
|
||||
|
||||
## The `Context` Object
|
||||
|
||||
The `Context` struct (`src/extensions/mod.rs`) is passed to most `Extension` hooks and provides access to core OSUI functionalities:
|
||||
|
||||
* **`Context::new(screen: Arc<Screen>)`**: Creates a new `Context` linked to the main `Screen`.
|
||||
* **`context.event<E: Event + Clone + 'static>(&self, e: &E)`**: Dispatches an event `e` to all widgets (via their `event` method and `Handler<E>` components) and all registered extensions. This is how extensions can inject new events into the system.
|
||||
* **`context.get_widgets()`**: Provides a `MutexGuard` to the `Vec<Arc<Widget>>` currently managed by the `Screen`. This allows extensions to iterate over and potentially modify widgets.
|
||||
* **`context.iter_components<C, F>()` / `context.get_components<C>()`**: Helper methods to easily iterate over or collect specific components attached to widgets.
|
||||
|
||||
## Creating a Custom Extension
|
||||
|
||||
Let's create a simple extension that logs every keyboard key pressed to standard output.
|
||||
|
||||
```rust
|
||||
// src/my_extension.rs
|
||||
use std::io::{self, Write};
|
||||
use osui::prelude::*; // Import OSUI prelude
|
||||
|
||||
// Define our custom extension struct
|
||||
pub struct KeyLoggerExtension;
|
||||
|
||||
// Implement the Extension trait for our struct
|
||||
impl Extension for KeyLoggerExtension {
|
||||
// Implement the event hook to listen for events
|
||||
fn event(&mut self, _ctx: &Context, event: &dyn Event) {
|
||||
// Attempt to downcast the generic `event` to a `crossterm::event::Event`.
|
||||
// The `InputExtension` dispatches these events.
|
||||
if let Some(crossterm_event) = event.get::<crossterm::event::Event>() {
|
||||
match crossterm_event {
|
||||
crossterm::event::Event::Key(key_event) => {
|
||||
// Log the key event to stdout.
|
||||
let _ = writeln!(io::stdout(), "Key Pressed: {:?}", key_event);
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
_ => {} // Ignore other crossterm events (mouse, resize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Registering Your Extension
|
||||
|
||||
To make your extension active, you must register it with the `Screen` instance in your `main.rs` or application setup:
|
||||
|
||||
```rust
|
||||
// src/main.rs
|
||||
mod my_extension; // Declare your new module
|
||||
mod demos; // Assuming you have a demos module as in guides
|
||||
|
||||
use osui::prelude::*;
|
||||
use my_extension::KeyLoggerExtension; // Import your extension
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
|
||||
// Register OSUI's built-in InputExtension (essential for keyboard input)
|
||||
screen.extension(InputExtension);
|
||||
// Register OSUI's built-in RelativeFocusExtension (for Tab/Shift+Tab navigation)
|
||||
screen.extension(RelativeFocusExtension::new());
|
||||
|
||||
// Register your custom KeyLoggerExtension
|
||||
screen.extension(KeyLoggerExtension);
|
||||
|
||||
demos::app(screen.clone()).draw(&screen);
|
||||
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
|
||||
Now, when you run your application and press keys, you will see `Key Pressed: ...` messages in your terminal's output stream (though they might be mixed with the TUI output due to how `crossterm` operates).
|
||||
|
||||
## Built-in Extensions
|
||||
|
||||
OSUI includes several powerful built-in extensions that you've already encountered in examples:
|
||||
|
||||
* **`InputExtension`**: Handles raw terminal input from `crossterm`.
|
||||
* **`RelativeFocusExtension`**: Manages focus between widgets based on their rendered positions, enabling navigation via keyboard (e.g., arrow keys). It introduces `Focused` and `AlwaysFocused` components.
|
||||
* **`IdExtension`**: Provides a way to assign unique IDs to widgets and retrieve them later by ID. It introduces the `Id` component.
|
||||
* **`TickExtension`**: Dispatches `TickEvent`s at a configurable interval, useful for animations or periodic updates.
|
||||
* **`VelocityExtension`**: Provides simple animation by automatically moving widgets with a `Velocity` component.
|
||||
|
||||
By implementing custom extensions, you can integrate external libraries, create complex behaviors not covered by simple components, or debug your UI in powerful ways.
|
||||
@@ -0,0 +1,157 @@
|
||||
# Getting Started with OSUI Example
|
||||
|
||||
This guide expands on the basic "Hello, OSUI!" example to demonstrate a slightly more interactive application with keyboard input and reactive state.
|
||||
|
||||
## 1. Project Setup (Review)
|
||||
|
||||
Ensure you have a new Rust project set up and `osui` added to your `Cargo.toml`. Refer to the [Getting Started](../intro/getting-started.md) guide if you haven't done this already.
|
||||
|
||||
## 2. The `main.rs` File
|
||||
|
||||
We'll use a `main.rs` file that directly calls into a `demos::app` function, similar to how the OSUI examples are structured. This separates the application logic from the basic setup.
|
||||
|
||||
```rust
|
||||
// src/main.rs
|
||||
mod demos; // Declare the demos module
|
||||
|
||||
use osui::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
|
||||
// Register extensions for input handling and relative focus navigation.
|
||||
// InputExtension is crucial for keyboard events.
|
||||
screen.extension(InputExtension);
|
||||
// RelativeFocusExtension enables navigation between widgets using arrow keys.
|
||||
screen.extension(RelativeFocusExtension::new());
|
||||
|
||||
// Call the `app` function from the `demos` module,
|
||||
// which defines our UI and draws it to the screen.
|
||||
demos::app(screen.clone()).draw(&screen);
|
||||
|
||||
// Run the main event loop. This will block until the application closes.
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
|
||||
## 3. The `demos` Module (`src/demos/mod.rs`)
|
||||
|
||||
This is where the main application logic and UI definition reside. We'll create a simple counter that increments every second and allows the user to exit using the `Esc` key.
|
||||
|
||||
```rust
|
||||
// src/demos/mod.rs
|
||||
use std::sync::Arc;
|
||||
use osui::prelude::*;
|
||||
|
||||
// This function takes an Arc<Screen> to interact with the main TUI context.
|
||||
pub fn app(screen: Arc<Screen>) -> Rsx {
|
||||
// 1. Create a reactive state variable for our counter.
|
||||
let count = use_state(0);
|
||||
|
||||
// 2. Spawn a background thread to update the counter every second.
|
||||
// We clone `count` (which is an Arc internally) to move it into the thread.
|
||||
std::thread::spawn({
|
||||
let count = count.clone();
|
||||
move || loop {
|
||||
// Acquire a mutable lock on the state and increment the value.
|
||||
// DerefMut implementation on Inner<T> automatically marks the state as "changed".
|
||||
**count.get() += 1;
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Define the UI using the rsx! macro.
|
||||
rsx! {
|
||||
// @Handler::new: Attaches an event handler component to the root widget.
|
||||
// This handler listens for `crossterm::event::Event`s.
|
||||
@Handler::new({
|
||||
let screen = screen.clone(); // Clone screen to move into the closure
|
||||
move |_, e: &crossterm::event::Event| {
|
||||
// Check if the event is a Key event and if the key is 'Esc'.
|
||||
if let crossterm::event::Event::Key(crossterm::event::KeyEvent { code, .. }) = e {
|
||||
if *code == crossterm::event::KeyCode::Esc {
|
||||
// If Esc is pressed, close the screen, terminating the application.
|
||||
screen.close();
|
||||
}
|
||||
}
|
||||
}});
|
||||
// @AlwaysFocused: A component from RelativeFocusExtension that keeps this widget focused
|
||||
// even when other widgets are navigated to. Useful for root containers or global handlers.
|
||||
@AlwaysFocused;
|
||||
Paginator { // Paginator is a simple page management element
|
||||
// FlexRow organizes children horizontally
|
||||
FlexRow {
|
||||
// Heading element for large text (uses figlet-rs)
|
||||
Heading, smooth: false, { "OSUI" } // Sets `smooth` property on Heading
|
||||
"Welcome to the OSUI demo!"
|
||||
"Press tab to switch to the next page or shift+tab to the previous page"
|
||||
}
|
||||
|
||||
// FlexCol organizes children vertically
|
||||
FlexCol, gap: 3, { // Sets `gap` property on FlexCol
|
||||
// @Transform: Component to define layout (position, dimensions, padding)
|
||||
@Transform::new().padding(2, 2);
|
||||
// @Style: Component to define visual style (background, foreground)
|
||||
@Style { foreground: None, background: Background::RoundedOutline(0x00ff00) };
|
||||
Div { // Div is a basic container element
|
||||
"This is text inside a div"
|
||||
}
|
||||
|
||||
@Transform::new().padding(2, 2);
|
||||
@Style { foreground: None, background: Background::Outline(0x00ff00) };
|
||||
Div {
|
||||
"This is text inside a div with square outlines"
|
||||
}
|
||||
}
|
||||
|
||||
FlexRow, gap: 1, {
|
||||
// %count: Declares this widget depends on the `count` state variable.
|
||||
// When `count` changes, this widget (and its children) will re-render.
|
||||
%count
|
||||
// String interpolation directly from state.
|
||||
"This will increment every second: {count}"
|
||||
|
||||
FlexRow { // Nested FlexRow for username input
|
||||
"Username"
|
||||
@Transform::new().padding(1, 1).dimensions(40, 1);
|
||||
@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xff0000) };
|
||||
@Focused; // This component marks the Input as initially focused.
|
||||
Input { } // Input element allows text input
|
||||
}
|
||||
|
||||
@Transform::new().margin(0, 1);
|
||||
FlexRow { // Nested FlexRow for password input
|
||||
"Password"
|
||||
@Transform::new().padding(1, 1).dimensions(40, 1);
|
||||
@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xffff00) };
|
||||
Input { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **`Screen` Initialization**: The `Screen` is the central orchestrator. It holds your UI widgets and extensions.
|
||||
2. **Extensions**:
|
||||
* `InputExtension`: Crucial for any interactive app. It enables raw mode for terminal input and continuously reads keyboard events, dispatching them to the `Screen`'s event bus.
|
||||
* `RelativeFocusExtension`: Manages which widget is "focused," allowing elements like `Input` to respond to typing. It also provides navigation (e.g., Tab key or arrow keys with Shift).
|
||||
3. **State Management (`use_state` and `%count`)**:
|
||||
* `use_state(0)` creates a `State<i32>` variable initialized to `0`. `State` is an `Arc<Mutex<T>>` internally, making it safe to share across threads.
|
||||
* The `std::thread::spawn` block continuously increments this `count` every second. `**count.get() += 1;` uses `DerefMut` on the `MutexGuard` obtained from `get()`. This `DerefMut` implementation *automatically marks the state as changed*.
|
||||
* In the `rsx!` macro, `%count` tells OSUI that the enclosing `Div` (and its children) depends on `count`.
|
||||
* Whenever `count` is marked as changed, the `DynWidget` associated with that `Div` automatically rebuilds its content and triggers a re-render in the next frame. This is why the number updates in the UI.
|
||||
4. **`rsx!` Macro**:
|
||||
* The `rsx!` macro provides a declarative way to define your UI tree. It creates a hierarchy of `Widget`s, each potentially containing an `Element` and various `Component`s.
|
||||
* `Paginator`, `FlexRow`, `FlexCol`, `Div`, `Heading`, `Input` are built-in [elements](../reference/elements/index.md) that define structure and appearance.
|
||||
* `@Transform` and `@Style` are [components](../reference/style.md) that attach layout and visual properties to elements.
|
||||
* `@Handler` is a [component](../reference/extensions.md) that allows a widget to listen for specific events (here, `crossterm::event::Event`).
|
||||
* `@Focused` and `@AlwaysFocused` are [components](../reference/extensions/focus.md) from `RelativeFocusExtension` that manage input focus.
|
||||
5. **`screen.run()`**: This method starts the main application loop. It continuously:
|
||||
* Renders all widgets.
|
||||
* Processes events from extensions.
|
||||
* Calls `auto_refresh` on dynamic widgets to check dependencies and re-render if needed.
|
||||
|
||||
This example showcases how OSUI integrates reactive state, event handling, and declarative UI definition to create interactive terminal applications.
|
||||
@@ -0,0 +1,147 @@
|
||||
# Layout and Styling with OSUI
|
||||
|
||||
OSUI provides a robust system for controlling the position, size, and visual appearance of your UI elements using `Transform` and `Style` components.
|
||||
|
||||
## 1. Controlling Layout with `Transform`
|
||||
|
||||
The `Transform` component defines how a widget is positioned and sized relative to its parent. It's designed for flexibility, allowing both fixed values and dynamic rules.
|
||||
|
||||
```rust
|
||||
// Defined using the component! macro in src/style.rs
|
||||
component!(Transform {
|
||||
pub x: Position,
|
||||
pub y: Position,
|
||||
pub mx: i32,
|
||||
pub my: i32,
|
||||
pub px: u16,
|
||||
pub py: u16,
|
||||
pub width: Dimension,
|
||||
pub height: Dimension,
|
||||
});
|
||||
```
|
||||
|
||||
* **`x`, `y`**: Horizontal and vertical position, defined by the `Position` enum.
|
||||
* **`mx`, `my`**: Margins (offsets) from the calculated position. Can be positive or negative.
|
||||
* **`px`, `py`**: Padding (internal spacing) around the content within the widget's bounds.
|
||||
* **`width`, `height`**: Sizing rules for dimensions, defined by the `Dimension` enum.
|
||||
|
||||
### `Position` Enum
|
||||
|
||||
Controls horizontal or vertical alignment:
|
||||
|
||||
* **`Const(u16)`**: Fixed position in cells from the origin (top-left).
|
||||
* `x: Position::Const(10)` or simply `x: 10` (due to `From<u16> for Position` impl).
|
||||
* **`Center`**: Centers the widget in the parent's available space.
|
||||
* **`End`**: Aligns the widget to the end (right or bottom) of the parent.
|
||||
|
||||
### `Dimension` Enum
|
||||
|
||||
Controls sizing:
|
||||
|
||||
* **`Full`**: Fills the available space from the parent.
|
||||
* **`Content`**: Automatically sized to fit content. This is the default. The element's `render` method will typically update the `RenderScope`'s size to match its content.
|
||||
* **`Const(u16)`**: Fixed size in cells.
|
||||
* `width: Dimension::Const(50)` or simply `width: 50`.
|
||||
|
||||
### Fluent API for `Transform`
|
||||
|
||||
`Transform` provides a fluent builder pattern for easy configuration:
|
||||
|
||||
* **`Transform::new()`**: Creates a default transform: top-left aligned (`x: 0`, `y: 0`), content sizing (`width: Content`, `height: Content`), no margins or padding.
|
||||
* **`Transform::center()`**: Shortcut for `x: Center`, `y: Center`.
|
||||
* **`transform.bottom()`**: Sets `y: End`.
|
||||
* **`transform.right()`**: Sets `x: End`.
|
||||
* **`transform.margin(x, y)`**: Sets `mx` and `my`.
|
||||
* **`transform.padding(x, y)`**: Sets `px` and `py`.
|
||||
* **`transform.dimensions(width, height)`**: Sets `width: Const(width)` and `height: Const(height)`.
|
||||
|
||||
### Examples of `Transform` Usage
|
||||
|
||||
Attach `Transform` as a component to any element using the `@` syntax in `rsx!`:
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
// Center a Div on the screen
|
||||
@Transform::center();
|
||||
Div { "I am centered." }
|
||||
|
||||
// Place a Div at (10, 5) with fixed dimensions and padding
|
||||
@Transform::new().x(10).y(5).dimensions(30, 7).padding(2, 1);
|
||||
Div { "Fixed size box with padding." }
|
||||
|
||||
// Align a Div to the bottom-right with margin
|
||||
@Transform::new().bottom().right().margin(-5, -3); // Negative margin pulls it closer to edge
|
||||
Div { "Bottom right with margin." }
|
||||
|
||||
// A FlexRow that takes full width and automatically sizes height
|
||||
@Transform::new().width(Full);
|
||||
FlexRow { "Full width content." }
|
||||
}
|
||||
```
|
||||
|
||||
The `transform!` macro provides a more concise way to create and set properties on a `Transform`:
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
// Equivalent to Transform::new().x(10).y(5).dimensions(30, 7).padding(2, 1);
|
||||
@transform!(x: 10, y: 5, width: 30, height: 7, px: 2, py: 1);
|
||||
Div { "Shorter syntax for transform." }
|
||||
|
||||
// Using Position and Dimension enums directly
|
||||
@transform!(x: Center, y: End, width: Full);
|
||||
Div { "Centered horizontally, at bottom, full width." }
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Defining Style with `Style`
|
||||
|
||||
The `Style` component defines the visual appearance, specifically background and foreground colors.
|
||||
|
||||
```rust
|
||||
// Defined using the component! macro in src/style.rs
|
||||
component!(Style {
|
||||
pub background: Background,
|
||||
pub foreground: Option<u32>,
|
||||
});
|
||||
|
||||
pub enum Background {
|
||||
NoBackground, // No background drawn
|
||||
Outline(u32), // Draws a basic rectangular outline
|
||||
RoundedOutline(u32),// Draws a rounded rectangular outline
|
||||
Solid(u32), // Fills the background with a solid color
|
||||
}
|
||||
```
|
||||
|
||||
* **`background`**: Determines how the background of the widget's calculated area is rendered.
|
||||
* **`foreground`**: An `Option<u32>` where `u32` represents a 24-bit RGB color (e.g., `0xFF0000` for red, `0x00FF00` for green). If `None`, the default terminal foreground color is used.
|
||||
|
||||
### Examples of `Style` Usage
|
||||
|
||||
Attach `Style` as a component:
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
// A div with a solid blue background and white text
|
||||
@Transform::new().dimensions(25, 3);
|
||||
@Style { background: Background::Solid(0x0000FF), foreground: Some(0xFFFFFF) };
|
||||
Div { "Hello, Blue World!" }
|
||||
|
||||
// A div with a green rounded outline
|
||||
@Transform::new().dimensions(25, 3).margin(0, 4);
|
||||
@Style { background: Background::RoundedOutline(0x00FF00), foreground: Some(0xFFFFFF) };
|
||||
Div { "Rounded green box." }
|
||||
|
||||
// A div with a red square outline
|
||||
@Transform::new().dimensions(25, 3).margin(0, 8);
|
||||
@Style { background: Background::Outline(0xFF0000), foreground: Some(0xFFFFFF) };
|
||||
Div { "Square red box." }
|
||||
}
|
||||
```
|
||||
|
||||
By combining `Transform` and `Style`, you can precisely control both the layout and visual aesthetics of your OSUI applications.
|
||||
@@ -0,0 +1,135 @@
|
||||
# State and Reactivity in OSUI
|
||||
|
||||
OSUI's reactivity model enables your UI to automatically update when underlying data changes, eliminating the need for manual DOM manipulation. This is achieved through the `State<T>` struct and its integration with `DynWidget`s via the `DependencyHandler` trait.
|
||||
|
||||
## The Problem: Dynamic UIs
|
||||
|
||||
Imagine you have a counter that increments over time, and you want to display its current value in your TUI. Without a reactive system, you would manually:
|
||||
1. Get the new count.
|
||||
2. Clear the old count from the screen.
|
||||
3. Draw the new count at the correct position.
|
||||
4. Manage redraws if other elements shift.
|
||||
|
||||
This becomes complex quickly, especially with multiple, interconnected pieces of dynamic data.
|
||||
|
||||
## The OSUI Solution: `State<T>`
|
||||
|
||||
`State<T>` is a generic type that wraps your data `T` and provides mechanisms to signal when `T` has changed. This signal then triggers a re-render of any UI widget that depends on that `State`.
|
||||
|
||||
### 1. Creating Reactive State: `use_state()`
|
||||
|
||||
The `use_state` function is the primary way to create a new `State<T>` instance:
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
screen.extension(InputExtension); // Needed for event loop
|
||||
|
||||
// Create a new State<i32> initialized with 0
|
||||
let counter = use_state(0);
|
||||
|
||||
// ... UI definition and screen.run()
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
`State<T>` internally uses `Arc<Mutex<Inner<T>>>`, making it `Send + Sync` and safely shareable across threads and widgets.
|
||||
|
||||
### 2. Modifying State and Triggering Updates
|
||||
|
||||
The most common way to interact with `State<T>` is via its `get()` method, which returns a `MutexGuard<'_, Inner<T>>`. The `Inner<T>` struct implements `Deref` and `DerefMut` for `T`, allowing you to treat `state.get()` much like a direct mutable reference to your data.
|
||||
|
||||
**Key Point**: When you use `DerefMut` (e.g., `*state.get() = ...` or `*state.get() += 1`), the `State` automatically registers that it has changed. This is critical for reactivity.
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
screen.extension(InputExtension); // Required for terminal setup
|
||||
|
||||
let counter = use_state(0);
|
||||
|
||||
// Spawn a new thread to increment the counter every second.
|
||||
// We clone `counter` (which is cheap due to Arc) to move it into the thread.
|
||||
thread::spawn({
|
||||
let counter_clone = counter.clone(); // Clone the Arc for the new thread
|
||||
move || {
|
||||
loop {
|
||||
// Get a mutable lock on the counter state
|
||||
// The DerefMut implementation on `Inner<i32>` will mark the state as changed
|
||||
// (setting `inner.changed = inner.dependencies`)
|
||||
*counter_clone.get() += 1;
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
// Declare that this Div widget depends on the `counter` state.
|
||||
// The `%counter` syntax is a critical part of connecting state to UI.
|
||||
%counter
|
||||
Div {
|
||||
// Access the value of the state using `.get()`.
|
||||
// `format!` macro will automatically call `Display` for `State<T>`.
|
||||
format!("Current Count: {}", counter.get())
|
||||
}
|
||||
}
|
||||
.draw(&screen);
|
||||
|
||||
screen.run()?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
In this example:
|
||||
* `*counter_clone.get() += 1;` modifies the `i32` value and simultaneously tells `State<i32>` that it has been updated.
|
||||
* Because the `Div` element was declared with `%counter` in `rsx!`, it becomes a `DynWidget` and registers `counter` as one of its dependencies.
|
||||
* In the main rendering loop (inside `screen.run()`), `DynWidget`s repeatedly call `dependency.check()`. When `counter.check()` returns `true` (because it was marked as changed), the `DynWidget` rebuilds its internal `Element` and components, refreshing its content with the new `counter` value.
|
||||
|
||||
### 3. Declaring Dependencies in `rsx!`
|
||||
|
||||
The `%variable_name` syntax in the `rsx!` macro is how you declare that a widget depends on a `State<T>` variable (or any type implementing `DependencyHandler`).
|
||||
|
||||
```rust
|
||||
let my_data = use_state("Initial".to_string());
|
||||
// ...
|
||||
rsx! {
|
||||
%my_data // This Div will re-render when `my_data` changes
|
||||
Div {
|
||||
format!("Data: {}", my_data.get())
|
||||
}
|
||||
}
|
||||
```
|
||||
You can declare multiple dependencies: `%state1 %state2 Div { ... }`.
|
||||
|
||||
### When to use `State::set()` or `State::update()`
|
||||
|
||||
* **`State::set(new_value)`**: Use this when you want to entirely replace the `T` value within the `State` and mark it as changed.
|
||||
```rust
|
||||
// Instead of: *my_state.get() = "New".to_string();
|
||||
my_state.set("New".to_string());
|
||||
```
|
||||
* **`State::update()`**: Use this if you modify the inner `T` value through a path that doesn't trigger `DerefMut` (e.g., if `T` is a complex struct and you modify one of its fields after getting a `&mut T` but without re-assigning the whole `T`). This explicitly tells `State` that it has changed.
|
||||
```rust
|
||||
// If MyComplexData has an internal field modified, and you only got `&mut MyComplexData`
|
||||
// but didn't reassign the whole struct.
|
||||
let mut data_lock = my_complex_data.get();
|
||||
data_lock.some_internal_field.modify();
|
||||
// After modifying, you might need to manually update if DerefMut didn't catch it
|
||||
// (though in most simple cases, DerefMut handles it automatically).
|
||||
my_complex_data.update();
|
||||
```
|
||||
|
||||
### Cloning `State<T>`
|
||||
|
||||
Since `State<T>` uses `Arc`, cloning a `State<T>` instance is very cheap. It only increments the reference count of the shared `Arc`. This is how you pass `State` into closures or other threads without moving ownership.
|
||||
|
||||
```rust
|
||||
let original_state = use_state(0);
|
||||
let cloned_state = original_state.clone(); // This is just an Arc clone
|
||||
```
|
||||
|
||||
By leveraging `State<T>` and dependency tracking, OSUI enables you to build dynamic, responsive terminal UIs with a clear separation of concerns between data and presentation.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Advanced Topics: Performance and Customization
|
||||
|
||||
This section covers more advanced aspects of OSUI, including performance considerations and how to further customize the library beyond basic usage.
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
OSUI is designed to be efficient for terminal UIs, but there are always considerations to keep in mind:
|
||||
|
||||
1. **`StaticWidget` vs. `DynWidget`**:
|
||||
* **Prefer `static` elements for unchanging content**: If a part of your UI never changes (e.g., a fixed label, a decorative border), use the `static` keyword in `rsx!` or `Screen::draw` to create `StaticWidget`s. They have zero overhead for dependency tracking and re-evaluation.
|
||||
* **Use `DynWidget` (implicit without `static`) only where reactivity is needed**: `DynWidget`s carry the overhead of checking dependencies and potentially rebuilding their `Element` and components every frame. While efficient, unnecessary `DynWidget`s can add up.
|
||||
|
||||
2. **State Management (`State<T>`)**:
|
||||
* **Granularity of State**: Try to keep state changes as granular as possible. If only a small part of your UI needs to react to a specific state, make only that minimal `DynWidget` depend on it, rather than a large parent widget. This limits the scope of re-evaluation.
|
||||
* **Avoid Holding Locks**: When interacting with `State<T>`, acquire the `MutexGuard` via `state.get()` for the shortest possible duration. If you only need to read the value, consider `state.get_dl()` which clones the value and doesn't hold the lock, reducing potential for contention.
|
||||
|
||||
3. **Extensions and Threads**:
|
||||
* **Offload Blocking Operations**: As seen with `InputExtension` and `TickExtension`, blocking I/O operations (like reading input or `thread::sleep`) should ideally be performed in separate threads. This prevents the main rendering loop from stalling, ensuring a smooth and responsive UI.
|
||||
* **Minimize Work in Hooks**: The `Extension` hooks (especially `render_widget` and `after_render_widget`) are called frequently, often for every widget every frame. Keep the logic within these hooks as lightweight as possible. For heavy computation, consider delegating to a separate thread and communicating results back via a `State` variable or an `Event`.
|
||||
|
||||
4. **Terminal Redraws**:
|
||||
* OSUI internally manages clearing and redrawing the necessary parts of the screen. However, complex UIs with many elements or frequent full-screen redraws can still be perceived as flickering or slow on some terminals.
|
||||
* While not directly exposed as a user configurable option, OSUI's design aims to minimize redraws by only updating changed cells where possible, though the current implementation does a full clear and redraw each frame. This is a common pattern for many TUIs.
|
||||
|
||||
## Customization
|
||||
|
||||
### 1. Custom Elements
|
||||
|
||||
As detailed in [Creating Custom Widgets](../guides/creating-custom-widgets.md), the primary way to customize OSUI is by implementing the `Element` trait. This allows you to define completely new visual components or logical containers tailored to your application.
|
||||
|
||||
### 2. Custom Components
|
||||
|
||||
Beyond the built-in `Transform` and `Style`, you can define any arbitrary data or behavior as a `Component` using the `component!` macro. This is invaluable for:
|
||||
|
||||
* **Marker Traits**: Like `Focused` or `AlwaysFocused`, indicating a property without holding data.
|
||||
* **Behavioral Data**: Storing data related to a specific behavior, e.g., `Velocity(x, y)` for animation.
|
||||
* **Event Handling**: The `Handler<E>` component allows attaching event-specific logic directly to widgets.
|
||||
* **Custom Logic**: Storing state specific to a widget's custom behavior that isn't part of its core `Element` data.
|
||||
|
||||
### 3. Custom Extensions
|
||||
|
||||
The `Extension` trait provides the deepest level of customization by allowing you to inject global logic into the OSUI application lifecycle. Use extensions for:
|
||||
|
||||
* **Global Event Handling**: Listening to all events across the UI.
|
||||
* **System Integrations**: Interacting with external systems (e.g., network, files, other libraries).
|
||||
* **Custom Layout Passes**: Implementing alternative or additional layout algorithms.
|
||||
* **Debugging Tools**: Injecting logging or visualization aids.
|
||||
* **Theming Systems**: Implementing a global theming system that dynamically adjusts widget styles.
|
||||
|
||||
### 4. `Cargo.toml` Profiles
|
||||
|
||||
The `Cargo.toml` provides specific build profiles that can be customized for performance and debugging.
|
||||
|
||||
```toml
|
||||
[profile.dev]
|
||||
opt-level = 1 # Optimization level for development builds. `1` is a good balance.
|
||||
debug = true # Include debug info.
|
||||
debug-assertions = true # Enable runtime checks for debugging.
|
||||
overflow-checks = true # Enable integer overflow checks.
|
||||
lto = false # Link-time optimizations are off for faster compile times.
|
||||
panic = 'unwind' # Panic unwinds the stack.
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z" # Optimize for size. Can also use `3` for max speed.
|
||||
debug = false # No debug info.
|
||||
debug-assertions = false # No runtime checks.
|
||||
overflow-checks = false # No integer overflow checks.
|
||||
lto = true # Enable link-time optimizations for max performance.
|
||||
panic = 'abort' # Panic aborts the process immediately (can be faster).
|
||||
codegen-units = 1 # Single codegen unit for max optimization (longer compile).
|
||||
```
|
||||
|
||||
* **`opt-level = "z"` in release**: This prioritizes binary size over raw speed. For TUI applications, small binary size can be desirable. You might change this to `opt-level = 3` for maximum performance, though the visual difference in most TUI apps might be negligible.
|
||||
* **`lto = true` in release**: Link-time optimizations can significantly improve runtime performance by allowing the compiler to optimize across crate boundaries. This comes at the cost of longer release build times.
|
||||
* **`panic = 'abort'` in release**: When a panic occurs in a release build, the program immediately terminates without unwinding the stack. This can sometimes lead to smaller binaries and slightly faster panic paths, but makes debugging panics harder. `panic = 'unwind'` (default for `dev`) is generally safer for debugging.
|
||||
|
||||
By understanding and leveraging these profiles, you can fine-tune the performance characteristics of your OSUI application.
|
||||
|
||||
### 5. `no_rsx` and `no_elem` Features
|
||||
|
||||
OSUI offers features that allow you to strip out parts of the library you don't use, primarily for reducing binary size if not for performance in all cases.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[features]
|
||||
no_rsx = [] # Disables the rsx! macro and its related frontend components.
|
||||
no_elem = [] # Disables all built-in elements (Div, Flex, Input, etc.).
|
||||
```
|
||||
|
||||
* **`no_rsx`**: If you prefer to build your UI programmatically (using `Widget::new_static`, `Widget::new_dyn`, `WidgetLoad`, etc.) instead of the declarative `rsx!` macro, enabling this feature removes the `rsx!` macro and the `frontend` module. This can be useful for very small, custom-built applications or for library consumers who want to define their own UI construction layer.
|
||||
* **`no_elem`**: If you intend to implement all your UI elements from scratch (e.g., you only need `Element`, `Component`, `Screen`, and `Extension` traits), enabling this feature removes all the built-in elements like `Div`, `FlexRow`, `Input`, `Heading`, and `Paginator`. This can drastically reduce the binary size if your application is entirely custom.
|
||||
|
||||
**How to use features:**
|
||||
|
||||
In your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
osui = { version = "0.1.1", features = ["no_rsx"] } # Or ["no_elem"], or both
|
||||
```
|
||||
|
||||
Or on the command line:
|
||||
|
||||
```bash
|
||||
cargo build --features no_rsx
|
||||
cargo run --release --features "no_rsx no_elem"
|
||||
```
|
||||
|
||||
These features offer a fine-grained control over the final binary, allowing you to tailor OSUI to your exact needs and potentially reduce its footprint in constrained environments.
|
||||
@@ -0,0 +1,168 @@
|
||||
# Declarative UI and the `rsx!` Macro
|
||||
|
||||
OSUI embraces a declarative approach to UI development, heavily inspired by modern web frameworks. This is primarily facilitated by the `rsx!` macro, which allows you to describe your UI's structure and behavior in a clear, nested, and expressive way.
|
||||
|
||||
## Why Declarative UI?
|
||||
|
||||
Traditional imperative UI development involves manually creating, positioning, and updating UI elements based on state changes. This can lead to complex, hard-to-maintain code, especially for dynamic UIs.
|
||||
|
||||
Declarative UI, in contrast:
|
||||
|
||||
* **Focuses on "What"**: You describe the desired UI state for a given data state, rather than the steps to get there.
|
||||
* **Simplicity**: The code is often more readable and easier to reason about, as it mirrors the visual structure of the UI.
|
||||
* **Reactivity**: When the underlying data changes, the framework (OSUI, in this case) efficiently updates the UI to reflect the new state, minimizing manual DOM manipulation.
|
||||
* **Composition**: Encourages breaking down complex UIs into smaller, reusable components.
|
||||
|
||||
## The `rsx!` Macro
|
||||
|
||||
The `rsx!` macro is the cornerstone of OSUI's declarative syntax. It transforms a nested, JSX-like structure into a tree of `RsxElement`s, which are then used to build `Widget`s on the `Screen`.
|
||||
|
||||
### Basic Syntax
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
screen.extension(InputExtension); // Needed for basic interaction
|
||||
|
||||
rsx! {
|
||||
// Simple text string as an Element
|
||||
"Hello, World!"
|
||||
|
||||
// Static element: No dynamic dependencies or state
|
||||
static Div {
|
||||
// Nested content
|
||||
"This is a static div."
|
||||
}
|
||||
|
||||
// Dynamic element: Reacts to state changes
|
||||
%my_state // This widget depends on `my_state`
|
||||
Div {
|
||||
// Content can be interpolated from state
|
||||
"Current value: {my_state}"
|
||||
}
|
||||
}.draw(&screen);
|
||||
|
||||
screen.run()?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Key Features of `rsx!` Syntax
|
||||
|
||||
1. **Direct Text**:
|
||||
Any string literal within `rsx!` is treated as a `String` element.
|
||||
```rust
|
||||
rsx! {
|
||||
"Just some text."
|
||||
format!("Dynamic text: {}", 123) // You can also use format!
|
||||
}
|
||||
```
|
||||
|
||||
2. **Element Declaration**:
|
||||
Elements are typically declared by their struct name (e.g., `Div`, `FlexRow`, `Input`).
|
||||
```rust
|
||||
rsx! {
|
||||
Div {} // A simple Div element
|
||||
}
|
||||
```
|
||||
|
||||
3. **Properties (Fields)**:
|
||||
You can set public fields on the element struct directly using comma-separated key-value pairs, similar to struct instantiation.
|
||||
```rust
|
||||
rsx! {
|
||||
Heading, smooth: true, { "Important Title" } // Sets the `smooth` field on Heading
|
||||
}
|
||||
```
|
||||
This expands to:
|
||||
```rust
|
||||
let mut elem = Heading::new();
|
||||
elem.smooth = true;
|
||||
// ... then `elem` is wrapped in a Widget
|
||||
```
|
||||
|
||||
4. **Children**:
|
||||
Content nested within curly braces `{}` after an element forms its children. These children are then drawn by the parent element's `after_render` method.
|
||||
```rust
|
||||
rsx! {
|
||||
Div {
|
||||
"First child"
|
||||
"Second child"
|
||||
FlexRow { "Nested FlexRow" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. **Static vs. Dynamic Widgets**:
|
||||
* **`static` keyword**: Prefix an element with `static` to explicitly declare it as a `StaticWidget`. This means its content and components won't change unless manually modified elsewhere. It incurs no dependency tracking overhead.
|
||||
```rust
|
||||
rsx! {
|
||||
static Div { "Always the same" }
|
||||
}
|
||||
```
|
||||
* **Dynamic (Reactive)**: By default, if an element has dependencies (see below) or is just a string literal without `static`, it becomes a `DynWidget`. This allows it to refresh when its dependencies change.
|
||||
|
||||
6. **Dependencies (`%dep_name`)**:
|
||||
Prefixing an element with `%variable_name` registers `variable_name` (which must implement `DependencyHandler`, like `State<T>`) as a dependency for that widget. If `variable_name` signals a change, the widget will automatically rebuild and re-render.
|
||||
```rust
|
||||
let counter = use_state(0);
|
||||
rsx! {
|
||||
%counter // This Div depends on `counter`
|
||||
Div {
|
||||
// `counter` can be used directly in its content
|
||||
"Count: {counter}"
|
||||
}
|
||||
}
|
||||
```
|
||||
Multiple dependencies can be listed: `%dep1 %dep2 Element {}`.
|
||||
|
||||
7. **Components (`@ComponentType`)**:
|
||||
Attach components to an element using the `@` symbol followed by the component's type and its constructor or a value.
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
// Attach a Transform component with specific dimensions
|
||||
@Transform::new().dimensions(10, 5);
|
||||
// Attach a Style component with a solid red background
|
||||
@Style { background: Background::Solid(0xFF0000) };
|
||||
Div {
|
||||
"A red box"
|
||||
}
|
||||
|
||||
// Attach a custom Handler component for events
|
||||
@Handler::new(|_, e: &MyEvent| { /* ... */ });
|
||||
Div { "Click me!" }
|
||||
}
|
||||
```
|
||||
You can attach multiple components to the same element, each on its own `@` line.
|
||||
|
||||
8. **Macro Expansion (`$expand => ($args)`)**:
|
||||
You can include the output of another `Rsx`-generating function or macro using `macro_name => (args)`. This is useful for creating reusable UI fragments.
|
||||
```rust
|
||||
fn my_button(text: &str) -> Rsx {
|
||||
rsx! {
|
||||
Div { format!("Button: {}", text) }
|
||||
}
|
||||
}
|
||||
|
||||
rsx! {
|
||||
my_button => ("Click Me")
|
||||
my_button => ("Another Button")
|
||||
}
|
||||
```
|
||||
|
||||
### How `rsx!` Works Internally
|
||||
|
||||
The `rsx!` macro recursively expands into a series of `Rsx::create_element` or `Rsx::create_element_static` calls. Each `RsxElement` stores either a `StaticWidget` directly or a closure that produces a `WidgetLoad` (for dynamic widgets), along with its dependencies and child `Rsx` tree.
|
||||
|
||||
When `Rsx::draw` or `Rsx::draw_parent` is called on the root `Rsx` object:
|
||||
|
||||
1. It iterates through its `RsxElement`s.
|
||||
2. For `RsxElement::DynElement`, it calls the stored closure to generate a `WidgetLoad`, then creates a `DynWidget` via `screen.draw_box_dyn`. It registers all specified dependencies with this new `DynWidget`.
|
||||
3. For `RsxElement::Element`, it directly creates a `StaticWidget` via `screen.draw_widget`.
|
||||
4. If a parent widget is provided (for nested elements), it calls `parent.get_elem().draw_child(&new_widget)`. This informs the parent `Element` about its new child.
|
||||
5. It then recursively calls `draw_parent` on the child `Rsx` tree, passing the newly created widget as the `parent`.
|
||||
|
||||
This process builds the complete `Arc<Widget>` tree managed by the `Screen`, setting up the initial hierarchy and reactive dependencies. The declarative `rsx!` syntax simplifies this complex creation process into an intuitive structure.
|
||||
@@ -0,0 +1,107 @@
|
||||
# Layout and Styling System
|
||||
|
||||
OSUI employs a two-phase approach to layout and rendering, driven by `Transform` and `RawTransform` structures, and provides expressive tools for styling elements using `Style`.
|
||||
|
||||
## The Two-Phase Transform Model
|
||||
|
||||
OSUI's layout system resolves abstract positioning and sizing rules into concrete pixel coordinates and dimensions. This is managed by two primary structures:
|
||||
|
||||
1. **`Transform` (Configured Layout)**:
|
||||
* This is the high-level struct that developers interact with.
|
||||
* It defines abstract rules for position (`Position` enum) and dimension (`Dimension` enum), along with explicit margins (`mx`, `my`) and padding (`px`, `py`).
|
||||
* `Transform` instances are typically attached to widgets as components using the `Transform` component or via the `transform!` macro.
|
||||
* Examples: `Position::Center`, `Dimension::Full`, `Position::Const(10)`.
|
||||
|
||||
```rust
|
||||
// Example Transform definition
|
||||
component!(Transform {
|
||||
pub x: Position,
|
||||
pub y: Position,
|
||||
pub mx: i32,
|
||||
pub my: i32,
|
||||
pub px: u16,
|
||||
pub py: u16,
|
||||
pub width: Dimension,
|
||||
pub height: Dimension,
|
||||
});
|
||||
```
|
||||
* Methods like `center()`, `bottom()`, `right()`, `margin()`, `padding()`, and `dimensions()` provide a fluent API for configuration.
|
||||
|
||||
2. **`RawTransform` (Resolved Layout)**:
|
||||
* This struct holds the *concrete, absolute* `u16` values for `x`, `y`, `width`, `height`, `px`, and `py` after layout calculations have occurred.
|
||||
* It represents the final calculated bounds and offsets for a widget on the virtual screen.
|
||||
* Developers generally don't manipulate `RawTransform` directly; it's used internally by the `RenderScope` during the rendering phase.
|
||||
|
||||
```rust
|
||||
// Example RawTransform definition
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RawTransform {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub px: u16,
|
||||
pub py: u16,
|
||||
}
|
||||
```
|
||||
|
||||
### How Resolution Works (`use_dimensions`, `use_position`)
|
||||
|
||||
When a widget is rendered, its `Transform` component is used by the `RenderScope` to resolve its abstract rules into a concrete `RawTransform`.
|
||||
|
||||
* **`Transform::use_dimensions(parent_width, parent_height, raw_transform)`**:
|
||||
* This method takes the `parent_width` and `parent_height` (the available space from the parent container) and updates the `raw_transform.width` and `raw_transform.height` based on the `Dimension` rules.
|
||||
* `Dimension::Full` will set the raw dimension to the parent's available size.
|
||||
* `Dimension::Const(n)` will set it to `n`.
|
||||
* `Dimension::Content` means the dimension will be determined by the content drawn by the element itself (e.g., text length or explicit `use_area` calls).
|
||||
|
||||
* **`Transform::use_position(parent_width, parent_height, raw_transform)`**:
|
||||
* This method uses the widget's *resolved* `raw_transform.width` and `raw_transform.height` (after `use_dimensions`) along with `parent_width` and `parent_height` to determine the absolute `raw_transform.x` and `raw_transform.y`.
|
||||
* `Position::Const(n)` sets the coordinate to `n`.
|
||||
* `Position::Center` calculates the coordinate to center the widget within the parent.
|
||||
* `Position::End` calculates the coordinate to align the widget to the end (right/bottom) of the parent.
|
||||
* Margins (`mx`, `my`) are applied as offsets after the base position is calculated.
|
||||
|
||||
This separation allows for a clear definition of layout rules at design time (`Transform`) and their efficient resolution into precise screen coordinates at runtime (`RawTransform`).
|
||||
|
||||
## Styling with `Style`
|
||||
|
||||
The `Style` component defines the visual appearance of a widget. It primarily controls background and foreground colors.
|
||||
|
||||
```rust
|
||||
component!(Style {
|
||||
pub background: Background,
|
||||
pub foreground: Option<u32>,
|
||||
});
|
||||
|
||||
pub enum Background {
|
||||
NoBackground,
|
||||
Outline(u32),
|
||||
RoundedOutline(u32),
|
||||
Solid(u32),
|
||||
}
|
||||
```
|
||||
|
||||
* **`background`**: Defines how the widget's background is rendered.
|
||||
* `NoBackground`: The widget's area is transparent.
|
||||
* `Outline(color)`: Draws a simple rectangular outline using the specified 24-bit RGB color.
|
||||
* `RoundedOutline(color)`: Draws a rounded rectangular outline.
|
||||
* `Solid(color)`: Fills the entire widget area with the specified 24-bit RGB color.
|
||||
* **`foreground`**: An `Option<u32>` representing the 24-bit RGB color for any text drawn within the widget. If `None`, default terminal foreground color is used.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
// Example: A div with blue background and white text
|
||||
rsx! {
|
||||
@Transform::new().dimensions(20, 5).padding(1,1);
|
||||
@Style { background: Background::Solid(0x0000FF), foreground: Some(0xFFFFFF) };
|
||||
Div {
|
||||
"This is a blue box with white text."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By combining `Transform` and `Style` components, developers have precise control over the visual presentation and spatial arrangement of UI elements.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Rendering Pipeline and `RenderScope`
|
||||
|
||||
OSUI's rendering process is a structured sequence of operations managed by the `RenderScope`. This module centralizes the accumulation of drawing commands and their eventual flush to the terminal.
|
||||
|
||||
## The Role of `RenderScope`
|
||||
|
||||
`RenderScope` is a mutable context passed through the rendering process for each widget. It serves several key purposes:
|
||||
|
||||
* **Layout Context**: It holds the current `RawTransform` (resolved position and dimensions) for the widget being rendered, derived from its `Transform` component and its parent's layout.
|
||||
* **Drawing Command Accumulation**: It acts as a temporary buffer for `RenderMethod` instructions (text, rectangles). Widgets and extensions add commands to this stack.
|
||||
* **Parent Size Tracking**: It keeps track of the available dimensions from the current widget's parent, crucial for resolving `Dimension::Full` and `Position::Center`/`End` rules.
|
||||
* **Style Application**: It carries the `Style` component for the current widget, influencing how drawing commands are eventually rendered (e.g., background fill, foreground color).
|
||||
|
||||
## The Rendering Sequence
|
||||
|
||||
When `Screen::render_widget` is called for a given `Arc<Widget>`, the following general pipeline is executed:
|
||||
|
||||
1. **Clear Scope**: The `RenderScope` is cleared of any previous draw instructions, ensuring a clean slate for the current widget.
|
||||
2. **Apply Style (if present)**: If the widget has a `Style` component, it's applied to the `RenderScope`.
|
||||
3. **Apply Transform (Dimensions First)**: If the widget has a `Transform` component, its `use_dimensions` method is called. This resolves `Dimension` rules (`Full`, `Const`, `Content`) into the `RawTransform` based on the parent's size.
|
||||
4. **Element `render` Call**: The widget's `Element::render` method is invoked.
|
||||
* Here, the `Element` issues its direct drawing commands (e.g., `scope.draw_text(...)`, `scope.draw_rect(...)`).
|
||||
* Crucially, if the `Dimension` was `Content`, the `Element` is responsible for updating the `RenderScope`'s `RawTransform` width/height to enclose its drawn content (e.g., `scope.use_area`).
|
||||
5. **Extension `render_widget` Calls**: Any registered extensions have their `render_widget` hook called. Extensions can observe or modify the `RenderScope` or widget state at this point.
|
||||
6. **Re-Apply Transform (Positions Second)**: After the `Element` and extensions have had a chance to determine the *content size* (for `Dimension::Content`), the widget's `Transform::use_position` is called. This resolves `Position` rules (`Const`, `Center`, `End`) into the `RawTransform` using the now-finalized dimensions. Margins are also applied here.
|
||||
7. **`ElementRenderer::before_draw` Call**: An `ElementRenderer` trait hook is called. This is specifically used by "ghost" elements (like `Div` or `FlexRow`) to adjust the `RenderScope`'s transform *for their children*. For example, a `FlexRow` would update the `x`/`y` of the `RenderScope`'s internal `RawTransform` so that the next child starts at the correct position within the row.
|
||||
8. **`RenderScope::draw`**: The accumulated `RenderMethod` instructions within the `RenderScope`'s `render_stack` are flushed to the terminal. This involves:
|
||||
* Drawing the background (if `Style::background` is `Solid`, `Outline`, or `RoundedOutline`).
|
||||
* Iterating through `render_stack` and printing text or drawing rectangles at their resolved coordinates, applying foreground colors from `Style` or specific `TextColored` commands.
|
||||
9. **Element `after_render` Call**: The widget's `Element::after_render` method is invoked.
|
||||
* This is typically where container elements (`Div`, `FlexRow`, `Paginator`) recursively trigger `scope.render_widget` for their children. They pass a *new* or modified `RenderScope` to their children, setting the `parent_width` and `parent_height` appropriately (e.g., the parent's own *content area*).
|
||||
10. **Extension `after_render_widget` Calls**: Any registered extensions have their `after_render_widget` hook called. This allows extensions to perform post-rendering logic, such as updating internal state based on the final rendered transform (e.g., `RelativeFocusExtension` records widget positions).
|
||||
11. **`widget.auto_refresh()`**: For `DynWidget`s, this checks if any registered dependencies have changed and, if so, triggers a `refresh()` to rebuild the widget for the next frame.
|
||||
|
||||
## Key Concepts in `RenderScope`
|
||||
|
||||
* **`RenderMethod`**: An internal enum representing a single primitive drawing operation (Text, TextInverted, TextColored, Rectangle).
|
||||
* **`draw_text`, `draw_rect`, etc.**: Methods on `RenderScope` to add `RenderMethod`s to the `render_stack`. These methods also update the `RenderScope`'s internal `RawTransform` to account for the content's size, enabling `Dimension::Content` to work.
|
||||
* **`use_area(width, height)`**: Allows an `Element` to explicitly declare a minimum required width and height, which is useful when the content itself doesn't automatically dictate a size (e.g., a `Div` that just reserves space).
|
||||
* **Coordinate System**: All coordinates `(x, y)` are relative to the *current* `RenderScope`'s top-left corner. When `RenderScope::draw` is called, these relative coordinates are offset by the `RenderScope`'s absolute `transform.x` and `transform.y`. Padding (`px`, `py`) is also applied as an offset for content rendering.
|
||||
|
||||
This pipeline ensures that layout calculations are performed efficiently, drawing commands are batched, and elements are rendered within their correctly determined bounds, respecting both parent constraints and self-determined content sizes.
|
||||
@@ -0,0 +1,109 @@
|
||||
# State Management and Reactivity
|
||||
|
||||
OSUI provides a built-in, lightweight state management system that enables reactive updates to your UI. This system is centered around the `State<T>` struct and the `DependencyHandler` trait, allowing `DynWidget`s to automatically re-render when their associated data changes.
|
||||
|
||||
## `State<T>`: Your Reactive Data Container
|
||||
|
||||
The `State<T>` struct is a wrapper around your data `T` that facilitates dependency tracking.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State<T> {
|
||||
inner: Arc<Mutex<Inner<T>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Inner<T> {
|
||||
value: T,
|
||||
dependencies: usize, // Number of widgets depending on this state
|
||||
changed: usize, // Counter for changes waiting to be processed by dependents
|
||||
}
|
||||
```
|
||||
|
||||
* **`Arc<Mutex<Inner<T>>>`**: The core of `State<T>` is its use of `Arc` and `Mutex`.
|
||||
* `Arc` allows `State<T>` instances to be shared across multiple widgets and threads without needing to clone the underlying data `T` itself, which is crucial for `DynWidget`s that track multiple dependencies.
|
||||
* `Mutex` ensures safe concurrent access to the `value` and metadata (`dependencies`, `changed`), preventing data races.
|
||||
|
||||
### Creating State (`use_state`)
|
||||
|
||||
You create a new `State<T>` instance using the `use_state` helper function:
|
||||
|
||||
```rust
|
||||
pub fn use_state<T>(v: T) -> State<T> { /* ... */ }
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
screen.extension(InputExtension);
|
||||
screen.extension(RelativeFocusExtension::new());
|
||||
|
||||
// Create a new state variable for a counter
|
||||
let count = use_state(0);
|
||||
|
||||
// Spawn a thread to increment the counter every second
|
||||
std::thread::spawn({
|
||||
let count = count.clone(); // Clone the Arc<State<T>> for the new thread
|
||||
move || loop {
|
||||
// Get a mutable lock on the Inner<T> to modify the value
|
||||
// DerefMut implementation on Inner<T> automatically marks it as changed
|
||||
*count.get() += 1;
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
// Declare the widget as dependent on `count`
|
||||
%count
|
||||
Div {
|
||||
// Access the value using Deref on Inner<T>
|
||||
format!("This number increments: {}", count.get())
|
||||
}
|
||||
}.draw(&screen);
|
||||
|
||||
screen.run()?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Accessing and Modifying State
|
||||
|
||||
* **`State::get()`**: Returns a `MutexGuard<'_, Inner<T>>`. This provides mutable access to the underlying `value` within `Inner<T>`.
|
||||
* `Inner<T>` implements `Deref` and `DerefMut` for `T`. This means you can treat `count.get()` like a direct reference to `T`.
|
||||
* Crucially, when you use `DerefMut` (e.g., `*count.get() += 1`), the `changed` counter within `Inner<T>` is automatically incremented. This is how OSUI knows the state has been modified and needs to trigger a re-render.
|
||||
* **`State::get_dl()`**: (Short for "get, don't lock") Returns a cloned copy of the value. This is useful when you only need to read the value and want to avoid holding the `MutexGuard` for longer than necessary, which can prevent deadlocks in complex scenarios. However, it doesn't mark the state as changed.
|
||||
* **`State::set(v: T)`**: Replaces the entire value and marks the state as changed.
|
||||
* **`State::update()`**: Explicitly marks the state as changed *without* modifying its value. Useful if internal parts of `T` are modified outside of direct `DerefMut` access.
|
||||
|
||||
## `DependencyHandler` Trait
|
||||
|
||||
The `DependencyHandler` trait is the interface through which `DynWidget`s observe changes in their dependencies. `State<T>` implements this trait.
|
||||
|
||||
```rust
|
||||
pub trait DependencyHandler: std::fmt::Debug + Send + Sync {
|
||||
fn add(&self);
|
||||
fn check(&self) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
* **`add()`**: Called when a `DynWidget` registers itself as a dependent of this `State<T>`. It increments the `dependencies` counter within `Inner<T>`.
|
||||
* **`check()`**: Called by `DynWidget`s (specifically by `DynWidget::auto_refresh()`) to determine if the state has changed since the last check.
|
||||
* It decrements the `changed` counter if it's greater than zero, signifying that a change has been "consumed" by a dependent.
|
||||
* It returns `true` if `changed` was greater than zero, indicating a fresh update.
|
||||
|
||||
### How Reactivity Works
|
||||
|
||||
1. **Widget Creation**: When `rsx!` creates a `DynWidget` with a `%state_var` dependency, `state_var.add()` is called, incrementing `state_var.inner.dependencies`.
|
||||
2. **State Modification**: When `*state_var.get() = new_value` or `state_var.set(new_value)` is called, the `state_var.inner.changed` counter is set to `state_var.inner.dependencies`. This means *all* widgets currently depending on this state are marked for a refresh.
|
||||
3. **Automatic Refresh**: In each rendering frame, `DynWidget::auto_refresh()` is called.
|
||||
* It iterates through its registered `DependencyHandler`s.
|
||||
* For each dependency, it calls `dependency.check()`.
|
||||
* If `check()` returns `true` (meaning the state has changed and hasn't been consumed yet by this widget), the `DynWidget`'s internal `load` closure is re-executed (`self.refresh()`). This rebuilds the widget's `Element` and `Component`s, picking up the new state value.
|
||||
* The `check()` method decrements the `changed` counter, ensuring that a single modification to the state triggers exactly one rebuild for each dependent widget.
|
||||
4. **Re-render**: The rebuilt widget is then rendered on the next frame, reflecting the updated state.
|
||||
|
||||
This system provides a robust and efficient way to manage dynamic UI elements, abstracting away the complexities of manual DOM updates and allowing developers to focus on defining their UI's desired state.
|
||||
@@ -0,0 +1,100 @@
|
||||
# Core Widget Model
|
||||
|
||||
OSUI's user interface is built upon a flexible and extensible widget model. At its heart, this model separates rendering logic from data and behavior using `Element`s and `Component`s, all encapsulated within `Widget`s.
|
||||
|
||||
## Elements: The Renderable Unit
|
||||
|
||||
The `Element` trait is the fundamental building block for anything that can be rendered on the screen.
|
||||
|
||||
```rust
|
||||
pub trait Element: Send + Sync {
|
||||
fn render(&mut self, scope: &mut RenderScope, render_context: &RenderContext);
|
||||
fn after_render(&mut self, scope: &mut RenderScope, render_context: &RenderContext);
|
||||
fn draw_child(&mut self, element: &Arc<Widget>);
|
||||
fn event(&mut self, event: &dyn Event);
|
||||
fn is_ghost(&mut self) -> bool;
|
||||
// ... Any and AnyMut methods for downcasting
|
||||
}
|
||||
```
|
||||
|
||||
* **`render`**: This is where the element defines *what* it draws. It uses the provided `RenderScope` to issue drawing commands (e.g., `draw_text`, `draw_rect`). It *does not* handle child rendering; that's done by the system or a parent element's `after_render`.
|
||||
* **`after_render`**: Called after the element's `render` method and any extensions have processed it. This is typically where container elements (like `Div` or `FlexRow`) would recursively trigger the rendering of their children, using the `RenderScope` for layout calculations.
|
||||
* **`draw_child`**: Used by the `rsx!` macro and `Rsx` structure to register a child widget with a parent `Element`.
|
||||
* **`event`**: Allows the element to react to various system events (e.g., keyboard input, custom events).
|
||||
* **`is_ghost`**: A "ghost" element is one that primarily serves as a layout or logical container and does not draw itself, but manages the rendering of its children. Examples include `Div` and `FlexRow`. They receive a `RenderScope` but might not add anything to its `render_stack` directly.
|
||||
|
||||
By default, simple text (`String`) is also an `Element`, allowing you to embed strings directly in `rsx!`.
|
||||
|
||||
## Components: Attaching Behavior and Data
|
||||
|
||||
The `Component` trait is an optional marker trait used for attaching arbitrary data or behavior to a `Widget`.
|
||||
|
||||
```rust
|
||||
pub trait Component: Send + Sync {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
```
|
||||
|
||||
Components are stored in a `HashMap` within a `Widget`, keyed by `TypeId`. This allows a widget to dynamically acquire and retrieve different functionalities or data.
|
||||
|
||||
**Why Components?**
|
||||
|
||||
* **Separation of Concerns**: Keep common behaviors (like styling, focus, velocity) separate from the core rendering logic of an `Element`.
|
||||
* **Flexibility**: Widgets can gain new capabilities at runtime by adding or removing components without modifying their fundamental `Element` implementation.
|
||||
* **Extensibility**: Extensions often operate by attaching or querying specific components (e.g., `Transform` for layout, `Style` for appearance, `Focused` for focus management).
|
||||
|
||||
Examples of built-in components include `Transform`, `Style`, `Velocity`, `Focused`, `Handler<E>`, etc. You can easily define your own using the `component!` macro.
|
||||
|
||||
## Widgets: The Container for Elements and Components
|
||||
|
||||
The `Widget` enum wraps an `Element` and its associated `Component`s. It's the primary way to interact with UI nodes in the OSUI tree.
|
||||
|
||||
```rust
|
||||
pub enum Widget {
|
||||
Static(StaticWidget),
|
||||
Dynamic(DynWidget),
|
||||
}
|
||||
```
|
||||
|
||||
`Widget` provides a unified interface to access its underlying `Element` and `Component`s, regardless of whether it's static or dynamic. Most interactions with the UI tree, such as drawing children or querying properties, are done via an `Arc<Widget>`.
|
||||
|
||||
### `WidgetLoad`: Building Widgets
|
||||
|
||||
`WidgetLoad` is a temporary struct used during the initial construction of a widget. It encapsulates the root `BoxedElement` and a `HashMap` of `BoxedComponent`s.
|
||||
|
||||
```rust
|
||||
pub struct WidgetLoad(BoxedElement, HashMap<TypeId, BoxedComponent>);
|
||||
|
||||
impl WidgetLoad {
|
||||
pub fn new<E: Element + 'static>(e: E) -> Self { /* ... */ }
|
||||
pub fn component<C: Component + 'static>(mut self, c: C) -> Self { /* ... */ }
|
||||
pub fn set_component<C: Component + 'static>(mut self, c: C) -> Self { /* ... */ }
|
||||
pub fn get<C: Component + 'static + Clone>(&self) -> Option<C> { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
It acts as a builder pattern for setting up a widget's initial state and attaching components, often used by the `rsx!` macro.
|
||||
|
||||
### `StaticWidget` vs. `DynWidget`
|
||||
|
||||
OSUI differentiates between two types of widgets to optimize for different use cases:
|
||||
|
||||
* **`StaticWidget`**:
|
||||
* **Purpose**: Represents UI elements whose content and components do not change after initial creation.
|
||||
* **When to Use**: Ideal for static text, immutable labels, or simple decorative elements that don't react to state changes.
|
||||
* **Performance**: More efficient as they don't carry the overhead of dependency tracking or re-evaluation logic.
|
||||
* **Creation**: Typically created directly via `Widget::new_static` or `Screen::draw` for direct `Element`s, or by `rsx!` when no dependencies are specified.
|
||||
|
||||
* **`DynWidget`**:
|
||||
* **Purpose**: Represents UI elements whose content can change reactively based on external state.
|
||||
* **When to Use**: Essential for displaying dynamic data, user input fields, or any widget that needs to update its appearance or children when underlying data changes (e.g., a counter, a list that filters based on input).
|
||||
* **Mechanism**: Stores a closure (`FnMut() -> WidgetLoad`) that rebuilds its `Element` and `Component`s. It tracks dependencies (via `DependencyHandler`) and automatically `refresh`es itself when those dependencies signal a change.
|
||||
* **Performance**: Carries a small overhead for dependency checking and re-evaluation.
|
||||
* **Creation**: Created via `Widget::new_dyn` or `Screen::draw_dyn`, or by `rsx!` when state dependencies (`%state_var`) are provided.
|
||||
|
||||
The distinction allows OSUI to efficiently render static parts of the UI while providing powerful reactivity for dynamic sections. When you use the `rsx!` macro, OSUI automatically determines whether to create a `StaticWidget` or `DynWidget` based on the presence of dependencies.
|
||||
|
||||
### Dependency Tracking
|
||||
|
||||
`DynWidget`s are at the core of OSUI's reactivity. They listen for changes in their registered dependencies. When a dependency changes, the widget's internal `load` closure is re-executed, effectively rebuilding its `Element` and `Component`s, leading to a re-render. This mechanism is explained in detail in [State Management](../concepts/state-management.md).
|
||||
@@ -0,0 +1,109 @@
|
||||
# Advanced Topics: Performance and Customization
|
||||
|
||||
This section covers more advanced aspects of OSUI, including performance considerations and how to further customize the library beyond basic usage.
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
OSUI is designed to be efficient for terminal UIs, but there are always considerations to keep in mind:
|
||||
|
||||
1. **`StaticWidget` vs. `DynWidget`**:
|
||||
* **Prefer `static` elements for unchanging content**: If a part of your UI never changes (e.g., a fixed label, a decorative border), use the `static` keyword in `rsx!` or `Screen::draw` to create `StaticWidget`s. They have zero overhead for dependency tracking and re-evaluation.
|
||||
* **Use `DynWidget` (implicit without `static`) only where reactivity is needed**: `DynWidget`s carry the overhead of checking dependencies and potentially rebuilding their `Element` and components every frame. While efficient, unnecessary `DynWidget`s can add up.
|
||||
|
||||
2. **State Management (`State<T>`)**:
|
||||
* **Granularity of State**: Try to keep state changes as granular as possible. If only a small part of your UI needs to react to a specific state, make only that minimal `DynWidget` depend on it, rather than a large parent widget. This limits the scope of re-evaluation.
|
||||
* **Avoid Holding Locks**: When interacting with `State<T>`, acquire the `MutexGuard` via `state.get()` for the shortest possible duration. If you only need to read the value, consider `state.get_dl()` which clones the value and doesn't hold the lock, reducing potential for contention.
|
||||
|
||||
3. **Extensions and Threads**:
|
||||
* **Offload Blocking Operations**: As seen with `InputExtension` and `TickExtension`, blocking I/O operations (like reading input or `thread::sleep`) should ideally be performed in separate threads. This prevents the main rendering loop from stalling, ensuring a smooth and responsive UI.
|
||||
* **Minimize Work in Hooks**: The `Extension` hooks (especially `render_widget` and `after_render_widget`) are called frequently, often for every widget every frame. Keep the logic within these hooks as lightweight as possible. For heavy computation, consider delegating to a separate thread and communicating results back via a `State` variable or an `Event`.
|
||||
|
||||
4. **Terminal Redraws**:
|
||||
* OSUI internally manages clearing and redrawing the necessary parts of the screen. However, complex UIs with many elements or frequent full-screen redraws can still be perceived as flickering or slow on some terminals.
|
||||
* While not directly exposed as a user configurable option, OSUI's design aims to minimize redraws by only updating changed cells where possible, though the current implementation does a full clear and redraw each frame. This is a common pattern for many TUIs.
|
||||
|
||||
## Customization
|
||||
|
||||
### 1. Custom Elements
|
||||
|
||||
As detailed in [Creating Custom Widgets](../guides/creating-custom-widgets.md), the primary way to customize OSUI is by implementing the `Element` trait. This allows you to define completely new visual components or logical containers tailored to your application.
|
||||
|
||||
### 2. Custom Components
|
||||
|
||||
Beyond the built-in `Transform` and `Style`, you can define any arbitrary data or behavior as a `Component` using the `component!` macro. This is invaluable for:
|
||||
|
||||
* **Marker Traits**: Like `Focused` or `AlwaysFocused`, indicating a property without holding data.
|
||||
* **Behavioral Data**: Storing data related to a specific behavior, e.g., `Velocity(x, y)` for animation.
|
||||
* **Event Handling**: The `Handler<E>` component allows attaching event-specific logic directly to widgets.
|
||||
* **Custom Logic**: Storing state specific to a widget's custom behavior that isn't part of its core `Element` data.
|
||||
|
||||
### 3. Custom Extensions
|
||||
|
||||
The `Extension` trait provides the deepest level of customization by allowing you to inject global logic into the OSUI application lifecycle. Use extensions for:
|
||||
|
||||
* **Global Event Handling**: Listening to all events across the UI.
|
||||
* **System Integrations**: Interacting with external systems (e.g., network, files, other libraries).
|
||||
* **Custom Layout Passes**: Implementing alternative or additional layout algorithms.
|
||||
* **Debugging Tools**: Injecting logging or visualization aids.
|
||||
* **Theming Systems**: Implementing a global theming system that dynamically adjusts widget styles.
|
||||
|
||||
### 4. `Cargo.toml` Profiles
|
||||
|
||||
The `Cargo.toml` provides specific build profiles that can be customized for performance and debugging.
|
||||
|
||||
```toml
|
||||
[profile.dev]
|
||||
opt-level = 1 # Optimization level for development builds. `1` is a good balance.
|
||||
debug = true # Include debug info.
|
||||
debug-assertions = true # Enable runtime checks for debugging.
|
||||
overflow-checks = true # Enable integer overflow checks.
|
||||
lto = false # Link-time optimizations are off for faster compile times.
|
||||
panic = 'unwind' # Panic unwinds the stack.
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z" # Optimize for size. Can also use `3` for max speed.
|
||||
debug = false # No debug info.
|
||||
debug-assertions = false # No runtime checks.
|
||||
overflow-checks = false # No integer overflow checks.
|
||||
lto = true # Enable link-time optimizations for max performance.
|
||||
panic = 'abort' # Panic aborts the process immediately (can be faster).
|
||||
codegen-units = 1 # Single codegen unit for max optimization (longer compile).
|
||||
```
|
||||
|
||||
* **`opt-level = "z"` in release**: This prioritizes binary size over raw speed. For TUI applications, small binary size can be desirable. You might change this to `opt-level = 3` for maximum performance, though the visual difference in most TUI apps might be negligible.
|
||||
* **`lto = true` in release**: Link-time optimizations can significantly improve runtime performance by allowing the compiler to optimize across crate boundaries. This comes at the cost of longer release build times.
|
||||
* **`panic = 'abort'` in release**: When a panic occurs in a release build, the program immediately terminates without unwinding the stack. This can sometimes lead to smaller binaries and slightly faster panic paths, but makes debugging panics harder. `panic = 'unwind'` (default for `dev`) is generally safer for debugging.
|
||||
|
||||
By understanding and leveraging these profiles, you can fine-tune the performance characteristics of your OSUI application.
|
||||
|
||||
### 5. `no_rsx` and `no_elem` Features
|
||||
|
||||
OSUI offers features that allow you to strip out parts of the library you don't use, primarily for reducing binary size if not for performance in all cases.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[features]
|
||||
no_rsx = [] # Disables the rsx! macro and its related frontend components.
|
||||
no_elem = [] # Disables all built-in elements (Div, Flex, Input, etc.).
|
||||
```
|
||||
|
||||
* **`no_rsx`**: If you prefer to build your UI programmatically (using `Widget::new_static`, `Widget::new_dyn`, `WidgetLoad`, etc.) instead of the declarative `rsx!` macro, enabling this feature removes the `rsx!` macro and the `frontend` module. This can be useful for very small, custom-built applications or for library consumers who want to define their own UI construction layer.
|
||||
* **`no_elem`**: If you intend to implement all your UI elements from scratch (e.g., you only need `Element`, `Component`, `Screen`, and `Extension` traits), enabling this feature removes all the built-in elements like `Div`, `FlexRow`, `Input`, `Heading`, and `Paginator`. This can drastically reduce the binary size if your application is entirely custom.
|
||||
|
||||
**How to use features:**
|
||||
|
||||
In your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
osui = { version = "0.1.1", features = ["no_rsx"] } # Or ["no_elem"], or both
|
||||
```
|
||||
|
||||
Or on the command line:
|
||||
|
||||
```bash
|
||||
cargo build --features no_rsx
|
||||
cargo run --release --features "no_rsx no_elem"
|
||||
```
|
||||
|
||||
These features offer a fine-grained control over the final binary, allowing you to tailor OSUI to your exact needs and potentially reduce its footprint in constrained environments.
|
||||
@@ -0,0 +1,56 @@
|
||||
# `osui::elements`
|
||||
|
||||
The `elements` module contains OSUI's built-in UI components, which serve as the fundamental building blocks for constructing your terminal user interfaces. These elements range from simple text to complex layout containers and interactive input fields.
|
||||
|
||||
## Core Element: `String`
|
||||
|
||||
In OSUI, a `String` (or anything that can be formatted into a `String`) implicitly acts as an `Element`. This allows you to directly embed text literals and interpolated strings within your `rsx!` markup.
|
||||
|
||||
### `Element` Trait Implementation for `String`
|
||||
|
||||
```rust
|
||||
impl Element for String {
|
||||
fn render(
|
||||
&mut self,
|
||||
scope: &mut crate::render_scope::RenderScope,
|
||||
_: &crate::render_scope::RenderContext,
|
||||
) {
|
||||
scope.draw_text(0, 0, self); // Draws the string at (0,0) relative to element's transform
|
||||
}
|
||||
|
||||
fn is_ghost(&mut self) -> bool {
|
||||
true // String elements are "ghosts" – they don't draw their own background/border.
|
||||
}
|
||||
|
||||
// as_any and as_any_mut implementations are boilerplate
|
||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
* When a `String` is used as an element, its `render` method simply calls `scope.draw_text` to print itself.
|
||||
* It's marked as `is_ghost() -> true`, meaning it doesn't handle its own layout or background drawing. Its size contributes to its parent's `Dimension::Content` calculation.
|
||||
|
||||
**Usage:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
"Hello, World!" // Simple string literal
|
||||
format!("The answer is: {}", 42) // Formatted string
|
||||
}
|
||||
```
|
||||
|
||||
## Other Built-in Elements
|
||||
|
||||
The `elements` module exports several other essential UI elements, each with its own dedicated documentation:
|
||||
|
||||
* **[`Div`](../reference/elements/div.md)**: A generic, transparent container element used for grouping children and applying layout and style.
|
||||
* **[`FlexRow`](../reference/elements/flex.md)**: A layout container that arranges its children horizontally in a row.
|
||||
* **[`FlexCol`](../reference/elements/flex.md)**: A layout container that arranges its children vertically in a column.
|
||||
* **[`Heading`](../reference/elements/heading.md)**: Renders large, stylized ASCII art text using `figlet-rs`.
|
||||
* **[`Input`](../reference/elements/input.md)**: An interactive element for user text input.
|
||||
* **[`Paginator`](../reference/elements/paginator.md)**: A container that manages multiple "pages" (children) and allows navigation between them.
|
||||
|
||||
These elements, combined with the `Transform` and `Style` components, provide a powerful foundation for building diverse and complex terminal user interfaces in OSUI.
|
||||
@@ -0,0 +1,87 @@
|
||||
# `osui::elements::div`
|
||||
|
||||
The `Div` element is a fundamental building block in OSUI, serving as a generic, transparent container. It doesn't have its own visual representation by default but is primarily used for grouping other elements and applying layout (`Transform`) and styling (`Style`) to a collection of children.
|
||||
|
||||
## `Div` Struct
|
||||
|
||||
```rust
|
||||
pub struct Div {
|
||||
children: Vec<Arc<Widget>>, // Children widgets contained within this Div
|
||||
size: (u16, u16), // Internal tracking of the Div's calculated size
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Div::new() -> Self`
|
||||
Creates a new `Div` instance with no children and default size.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let my_div = Div::new(); // Create a Div programmatically
|
||||
```
|
||||
|
||||
### `Element` Trait Implementation
|
||||
|
||||
#### `render(&mut self, scope: &mut RenderScope, _: &RenderContext)`
|
||||
This method, for `Div`, primarily focuses on setting the `RenderScope`'s area based on its calculated size or default values. It does *not* issue any direct drawing commands (e.g., `draw_text`, `draw_rect`) for itself, as `Div` is transparent by default.
|
||||
|
||||
#### `after_render(&mut self, scope: &mut RenderScope, render_context: &RenderContext)`
|
||||
This is the crucial method for container elements like `Div`. It's called after the `Div` itself has been processed by the rendering pipeline.
|
||||
1. It clones the `RenderScope`'s `RawTransform` to use as a basis for its children's layout.
|
||||
2. It creates a `DivRenderer` (an `ElementRenderer` helper) which will adjust child positions relative to the `Div`.
|
||||
3. It temporarily sets the `RenderScope`'s `parent_size` to its own calculated content area. This ensures that children using `Dimension::Full` or `Position::Center`/`End` resolve correctly within the `Div`'s bounds.
|
||||
4. It iterates through its `children` and calls `scope.render_widget` for each, effectively triggering the rendering pipeline for its nested elements.
|
||||
5. After children are rendered, it restores the original `parent_size` to the `RenderScope`.
|
||||
6. It updates its internal `self.size` based on the accumulated size of its children (as reported by `DivRenderer`).
|
||||
|
||||
#### `draw_child(&mut self, element: &Arc<Widget>)`
|
||||
This method is called by the `rsx!` macro or `Rsx::draw_parent` when a widget is declared as a child of this `Div`.
|
||||
1. It adds the `element` to its internal `children` `Vec`.
|
||||
2. It injects a `NoRenderRoot` component into the child widget. This is critical: it tells the main `Screen` rendering loop *not* to render this child directly, as the `Div` itself will handle its rendering in `after_render`. This prevents double-rendering and ensures correct layout.
|
||||
|
||||
#### `is_ghost(&mut self) -> bool`
|
||||
Returns `true`. A `Div` is a "ghost" element because it primarily serves as a logical grouping and layout container and does not draw any visual representation (like a background or border) itself. Any visual properties are applied via external `Style` components associated with the `Div`'s widget.
|
||||
|
||||
#### `as_any(&self) -> &dyn std::any::Any` / `as_any_mut(&mut self) -> &mut dyn std::any::Any`
|
||||
Standard implementations for downcasting.
|
||||
|
||||
## `DivRenderer`
|
||||
|
||||
A helper struct that implements `ElementRenderer` specifically for `Div` to adjust the `RenderScope` for its children.
|
||||
|
||||
```rust
|
||||
pub struct DivRenderer<'a>(pub &'a mut RawTransform);
|
||||
```
|
||||
|
||||
### `ElementRenderer` Trait Implementation for `DivRenderer`
|
||||
|
||||
#### `before_draw(&mut self, scope: &mut RenderScope, _widget: &Arc<Widget>)`
|
||||
This method is called for each child widget of the `Div` just before that child is drawn.
|
||||
1. It updates the `Div`'s own `RawTransform` (`self.0`) to expand its `width` and `height` to encompass the child's area plus its padding.
|
||||
2. It translates the child's `RawTransform` (`t`) by the `Div`'s absolute position and padding. This ensures children are positioned correctly *inside* the `Div`.
|
||||
3. It updates the child's `RawTransform` padding by adding the `Div`'s padding.
|
||||
|
||||
## Usage in `rsx!`
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
// A Div with a solid background and padding, containing text and a FlexRow
|
||||
@Transform::new().dimensions(40, 10).center().padding(2, 1);
|
||||
@Style { background: Background::Solid(0x333333), foreground: Some(0xFFFFFF) };
|
||||
Div {
|
||||
"This is content inside the Div."
|
||||
"It will respect the Div's padding and dimensions."
|
||||
|
||||
FlexRow, gap: 1, {
|
||||
"Nested"
|
||||
"FlexRow"
|
||||
"Elements"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
`Div` is an essential tool for structuring your UI, applying common styles, and managing the layout of groups of widgets.
|
||||
@@ -0,0 +1,161 @@
|
||||
# `osui::elements::flex`
|
||||
|
||||
The `flex` module provides `FlexRow` and `FlexCol` elements, which are powerful layout containers for automatically arranging their children either horizontally or vertically, with optional spacing. They are "ghost" elements, meaning they control the layout of their children but don't draw any visual elements themselves by default.
|
||||
|
||||
## `FlexRow` Struct
|
||||
|
||||
Arranges children in a row (horizontally).
|
||||
|
||||
```rust
|
||||
pub struct FlexRow {
|
||||
pub gap: u16, // Spacing in cells between adjacent children horizontally
|
||||
children: Vec<Arc<Widget>>,
|
||||
size: (u16, u16), // Internal tracking of the FlexRow's calculated size
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `FlexRow::new() -> Self`
|
||||
Creates a new `FlexRow` instance with no children, no gap, and default size.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let my_row = FlexRow::new();
|
||||
```
|
||||
|
||||
### `Element` Trait Implementation for `FlexRow`
|
||||
|
||||
#### `render(&mut self, scope: &mut RenderScope, _: &RenderContext)`
|
||||
Similar to `Div`, this method primarily ensures the `RenderScope`'s area reflects its calculated size. It doesn't draw anything visually for the `FlexRow` itself.
|
||||
|
||||
#### `after_render(&mut self, scope: &mut RenderScope, render_context: &RenderContext)`
|
||||
This method calculates and applies the layout for its children:
|
||||
1. Clones the `RenderScope`'s `RawTransform` to use as a basis.
|
||||
2. Stores the original parent size from the `RenderScope`.
|
||||
3. Sets the `RenderScope`'s `parent_size` to its own (FlexRow's) determined `width` and `height`.
|
||||
4. Initializes `v = 0`; this variable tracks the current horizontal offset for placing children.
|
||||
5. Creates a `RowRenderer` helper which will modify `RenderScope`'s transforms for each child.
|
||||
6. Iterates through `self.children`, calling `scope.render_widget` for each. The `RowRenderer` updates the `x` position for each child and increments `v` to prepare for the next child.
|
||||
7. Restores the original `parent_size` to the `RenderScope`.
|
||||
8. Updates `self.size` based on the final accumulated width and maximum height of its children (as calculated by `RowRenderer`).
|
||||
|
||||
#### `draw_child(&mut self, element: &Arc<Widget>)`
|
||||
Adds a child `Widget` to the `FlexRow`'s internal `children` list and injects `NoRenderRoot` into the child to prevent direct rendering by the main `Screen` loop.
|
||||
|
||||
#### `is_ghost(&mut self) -> bool`
|
||||
Returns `true`, as `FlexRow` is a layout-only container.
|
||||
|
||||
#### `as_any(&self) -> &dyn std::any::Any` / `as_any_mut(&mut self) -> &mut dyn std::any::Any`
|
||||
Standard implementations for downcasting.
|
||||
|
||||
## `FlexCol` Struct
|
||||
|
||||
Arranges children in a column (vertically).
|
||||
|
||||
```rust
|
||||
pub struct FlexCol {
|
||||
pub gap: u16, // Spacing in cells between adjacent children vertically
|
||||
children: Vec<Arc<Widget>>,
|
||||
size: (u16, u16), // Internal tracking of the FlexCol's calculated size
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `FlexCol::new() -> Self`
|
||||
Creates a new `FlexCol` instance with no children, no gap, and default size.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let my_col = FlexCol::new();
|
||||
```
|
||||
|
||||
### `Element` Trait Implementation for `FlexCol`
|
||||
|
||||
The implementation mirrors `FlexRow`, but for vertical arrangement:
|
||||
|
||||
#### `render(&mut self, scope: &mut RenderScope, _: &RenderContext)`
|
||||
Ensures `RenderScope` area is set. No direct drawing.
|
||||
|
||||
#### `after_render(&mut self, scope: &mut RenderScope, render_context: &RenderContext)`
|
||||
1. Clones `RawTransform`.
|
||||
2. Sets `RenderScope` `parent_size` to its own dimensions.
|
||||
3. Initializes `v = 0` (this variable tracks the current vertical offset).
|
||||
4. Creates a `ColumnRenderer` helper.
|
||||
5. Iterates `children`, calling `scope.render_widget`. The `ColumnRenderer` updates the `y` position for each child and increments `v` for the next child.
|
||||
6. Restores original `parent_size`.
|
||||
7. Updates `self.size` based on the accumulated height and maximum width of its children.
|
||||
|
||||
#### `draw_child(&mut self, element: &Arc<Widget>)`
|
||||
Adds child and injects `NoRenderRoot`.
|
||||
|
||||
#### `is_ghost(&mut self) -> bool`
|
||||
Returns `true`.
|
||||
|
||||
#### `as_any(&self) -> &dyn std::any::Any` / `as_any_mut(&mut self) -> &mut dyn std::any::Any`
|
||||
Standard implementations.
|
||||
|
||||
## `RowRenderer` (Helper)
|
||||
|
||||
An `ElementRenderer` implementation specifically for `FlexRow` to adjust the `RenderScope` for its children's positions.
|
||||
|
||||
```rust
|
||||
pub struct RowRenderer<'a>(&'a mut RawTransform, u16, &'a mut u16);
|
||||
```
|
||||
|
||||
### `ElementRenderer` Trait Implementation for `RowRenderer`
|
||||
|
||||
#### `before_draw(&mut self, scope: &mut RenderScope, _widget: &Arc<Widget>)`
|
||||
Called for each child of a `FlexRow` just before the child is drawn.
|
||||
1. Updates the `FlexRow`'s `RawTransform` (`self.0`) to encompass the child's area and the running horizontal offset.
|
||||
2. Adjusts the child's `RawTransform` (`t`) by the parent `FlexRow`'s absolute position and adds the current horizontal offset (`*self.2`).
|
||||
3. Increments `*self.2` (the running horizontal offset) by the child's width, its horizontal padding, and the `gap` to prepare for the next child.
|
||||
4. Adds the parent `FlexRow`'s padding to the child's `RawTransform` padding.
|
||||
|
||||
## `ColumnRenderer` (Helper)
|
||||
|
||||
An `ElementRenderer` implementation specifically for `FlexCol` to adjust the `RenderScope` for its children's positions.
|
||||
|
||||
```rust
|
||||
pub struct ColumnRenderer<'a>(&'a mut RawTransform, u16, &'a mut u16);
|
||||
```
|
||||
|
||||
### `ElementRenderer` Trait Implementation for `ColumnRenderer`
|
||||
|
||||
#### `before_draw(&mut self, scope: &mut RenderScope, _widget: &Arc<Widget>)`
|
||||
Called for each child of a `FlexCol` just before the child is drawn.
|
||||
1. Updates the `FlexCol`'s `RawTransform` (`self.0`) to encompass the child's area and the running vertical offset.
|
||||
2. Adjusts the child's `RawTransform` (`t`) by the parent `FlexCol`'s absolute position and adds the current vertical offset (`*self.2`).
|
||||
3. Increments `*self.2` (the running vertical offset) by the child's height, its vertical padding, and the `gap` to prepare for the next child.
|
||||
4. Adds the parent `FlexCol`'s padding to the child's `RawTransform` padding.
|
||||
|
||||
## Usage in `rsx!`
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
// A FlexRow with 2 cells gap between items
|
||||
@Transform::new().padding(1,1);
|
||||
@Style { background: Background::Outline(0x00FF00) };
|
||||
FlexRow, gap: 2, {
|
||||
"First Item"
|
||||
Div { "Second Item (a Div)" }
|
||||
@Style { foreground: Some(0xFF0000) };
|
||||
"Third Item (Red Text)"
|
||||
}
|
||||
|
||||
// A FlexCol with 1 cell gap, centered horizontally
|
||||
@Transform::new().x(Center).margin(0, 5); // Margin to separate from the row above
|
||||
@Style { background: Background::Outline(0x0000FF) };
|
||||
FlexCol, gap: 1, {
|
||||
"Column Item 1"
|
||||
Input { } // An input field
|
||||
"Column Item 3"
|
||||
}
|
||||
}
|
||||
```
|
||||
Flex containers are powerful for building responsive and neatly aligned layouts without manual coordinate calculations.
|
||||
@@ -0,0 +1,67 @@
|
||||
# `osui::elements::heading`
|
||||
|
||||
The `Heading` element provides a way to render large, stylized text using FIGlet fonts (ASCII art). It's suitable for titles, banners, and decorative text in your terminal UI.
|
||||
|
||||
## `Heading` Struct
|
||||
|
||||
```rust
|
||||
pub struct Heading {
|
||||
pub font: FIGfont, // The FIGfont instance to use for rendering
|
||||
pub smooth: bool, // If true, attempts to replace ASCII art characters with Unicode line drawing characters
|
||||
children: Vec<Arc<Widget>>, // Holds children, usually a single String element
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Heading::new() -> Heading`
|
||||
Creates a new `Heading` instance. By default, it uses the `FIGfont::standard()` font and `smooth` is `false`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let my_heading = Heading::new();
|
||||
```
|
||||
|
||||
### `Element` Trait Implementation
|
||||
|
||||
#### `render(&mut self, scope: &mut RenderScope, _: &RenderContext)`
|
||||
This method performs the core rendering for the `Heading`.
|
||||
1. It iterates through its `children` (typically expects a single `String` child).
|
||||
2. It attempts to downcast the child's `Element` to a `String` and concatenates the text.
|
||||
3. It then uses the `FIGfont::convert` method to transform the accumulated text into ASCII art.
|
||||
4. If `self.smooth` is `true`, it replaces common ASCII art characters (`-` and `|`) with their Unicode line-drawing equivalents (`─` and `│`) for a cleaner look.
|
||||
5. Finally, it calls `scope.draw_text(0, 0, ...)` to render the generated ASCII art. The `RenderScope`'s width and height will automatically be updated by `draw_text` to encompass the size of the rendered ASCII art.
|
||||
|
||||
#### `draw_child(&mut self, element: &Arc<Widget>)`
|
||||
This method is called when an element is declared as a child of the `Heading` (e.g., the text within its `rsx!` block).
|
||||
1. It injects a `NoRenderRoot` component into the child. This is important to ensure the child `String` element is not rendered independently by the main `Screen` loop, but rather its content is *read* by the `Heading` and then the `Heading` renders the ASCII art.
|
||||
2. It adds the `element` to its internal `children` `Vec`.
|
||||
|
||||
#### `is_ghost(&mut self) -> bool`
|
||||
Returns `true`. `Heading` is a "ghost" element because it primarily processes and renders the content of its children into a new visual form (ASCII art) rather than directly displaying its own structural properties. Any background or other styling would be applied via a `Style` component on the `Heading`'s widget itself.
|
||||
|
||||
#### `as_any(&self) -> &dyn std::any::Any` / `as_any_mut(&mut self) -> &mut dyn std::any::Any`
|
||||
Standard implementations for downcasting.
|
||||
|
||||
## Usage in `rsx!`
|
||||
|
||||
You typically pass the text for the `Heading` as a child. You can also set its `smooth` property.
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
// A standard FIGlet heading
|
||||
Heading { "OSUI" }
|
||||
|
||||
// A smooth FIGlet heading, separated by a margin
|
||||
@Transform::new().margin(0, 5); // Add some vertical space
|
||||
Heading, smooth: true, { "Awesome!" }
|
||||
|
||||
// Using Heading with dynamic text from state
|
||||
%my_dynamic_text_state
|
||||
Heading { format!("Hello {}", my_dynamic_text_state.get()) }
|
||||
}
|
||||
```
|
||||
`Heading` is an easy way to add visual flair and prominence to titles in your TUI.
|
||||
@@ -0,0 +1,83 @@
|
||||
# `osui::elements::input`
|
||||
|
||||
The `Input` element provides a basic interactive text input field for your OSUI applications. It allows users to type, backspace, delete characters, and move the cursor within the input area.
|
||||
|
||||
## `Input` Struct
|
||||
|
||||
```rust
|
||||
pub struct Input {
|
||||
pub state: State<String>, // Reactive state holding the input string
|
||||
cursor: usize, // Current cursor position within the string
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Input::new() -> Self`
|
||||
Creates a new `Input` instance with an empty `String` for its `state` and the `cursor` at position `0`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let my_input = Input::new();
|
||||
```
|
||||
|
||||
### `Element` Trait Implementation
|
||||
|
||||
#### `render(&mut self, scope: &mut RenderScope, render_context: &RenderContext)`
|
||||
This method is responsible for drawing the input field's current text and the cursor.
|
||||
1. It retrieves the current string from `self.state`.
|
||||
2. It calls `scope.draw_text(0, 0, &s)` to render the entire input string.
|
||||
3. **Cursor Rendering (Focus Indicator)**: If `render_context.is_focused()` is `true` (meaning this `Input` widget has keyboard focus), it draws an "inverted" character at the current `self.cursor` position. If the cursor is at the end of the string, it draws an inverted space. This visually indicates where the user is typing.
|
||||
|
||||
#### `event(&mut self, event: &dyn Event)`
|
||||
This method handles incoming `crossterm::event::Event`s, specifically keyboard input, when the `Input` widget is focused.
|
||||
It checks if the event is a `KeyEvent` and if modifiers (other than `Shift`) are present, it ignores the event to prevent unintended actions (e.g., `Ctrl+C`).
|
||||
It then matches on `KeyCode`:
|
||||
* **`KeyCode::Char(c)`**: Inserts the character `c` at the `cursor` position in the `state` string and increments `cursor`.
|
||||
* **`KeyCode::Backspace`**: If `cursor > 0`, removes the character before the cursor and decrements `cursor`.
|
||||
* **`KeyCode::Delete`**: If `cursor` is not at the end of the string, removes the character at the `cursor` position.
|
||||
* **`KeyCode::Left`**: Moves `cursor` one position to the left (if not already at `0`).
|
||||
* **`KeyCode::Right`**: Moves `cursor` one position to the right (if not already at the end of the string).
|
||||
After any modification, the `Input`'s `state` is automatically marked as changed (due to `DerefMut` on `State::get()`), triggering a re-render.
|
||||
|
||||
#### `as_any(&self) -> &dyn std::any::Any` / `as_any_mut(&mut self) -> &mut dyn std::any::Any`
|
||||
Standard implementations for downcasting.
|
||||
|
||||
## Usage in `rsx!`
|
||||
|
||||
The `Input` element needs to be part of the widget tree. For it to receive keyboard input, it must be the `focused` widget. You typically achieve this using the `Focused` component (provided by `RelativeFocusExtension`).
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
screen.extension(InputExtension); // Essential for keyboard input
|
||||
screen.extension(RelativeFocusExtension::new()); // Manages focus
|
||||
|
||||
rsx! {
|
||||
FlexCol, gap: 1, {
|
||||
"Username:"
|
||||
@Transform::new().dimensions(30, 1).padding(1, 0); // Give it some padding
|
||||
@Style { background: Background::Outline(0xAAAAAA), foreground: Some(0xFFFFFF) };
|
||||
@Focused; // This input will be focused by default
|
||||
Input { }
|
||||
|
||||
"Password:"
|
||||
@Transform::new().dimensions(30, 1).padding(1, 0);
|
||||
@Style { background: Background::RoundedOutline(0xAAAAAA), foreground: Some(0xFFFFFF) };
|
||||
Input { } // This input will only be focused via navigation (e.g., Shift+Down arrow)
|
||||
}
|
||||
}
|
||||
.draw(&screen);
|
||||
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
**Accessing Input Value:**
|
||||
The `Input` element manages its own `State<String>`. If you need to access the typed value from another part of your application (e.g., when a submit button is pressed), you would typically:
|
||||
1. **Inject your own `State<String>`**: Instead of `Input { }`, you could modify `Input::new()` or create a custom `Input` variant that takes an external `State<String>` to bind to.
|
||||
2. **Get component by ID**: If using `IdExtension`, you could assign an `Id` to the `Input` widget, then later retrieve the `Arc<Widget>` by ID and call `widget.get::<Input>()` to access its `state` field.
|
||||
|
||||
The `Input` element provides a crucial interactive component for building forms and dynamic data entry in your TUI.
|
||||
@@ -0,0 +1,96 @@
|
||||
# `osui::elements::paginator`
|
||||
|
||||
The `Paginator` element is a container that manages a collection of child widgets, displaying only one child at a time. It provides built-in logic to navigate between these "pages" using keyboard events (specifically, `Tab` and `Shift+Tab`).
|
||||
|
||||
## `Paginator` Struct
|
||||
|
||||
```rust
|
||||
pub struct Paginator {
|
||||
children: Vec<Arc<Widget>>, // The list of pages/children
|
||||
size: (u16, u16), // Internal tracking of the Paginator's calculated size
|
||||
index: usize, // The index of the currently displayed child
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Paginator::new() -> Self`
|
||||
Creates a new `Paginator` instance with no children and an initial `index` of `0`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let my_paginator = Paginator::new();
|
||||
```
|
||||
|
||||
### `Element` Trait Implementation
|
||||
|
||||
#### `render(&mut self, scope: &mut RenderScope, _: &RenderContext)`
|
||||
This method primarily focuses on setting the `RenderScope`'s area based on its calculated size. It does not draw any visual elements for the `Paginator` itself, acting as a "ghost" element.
|
||||
|
||||
#### `after_render(&mut self, scope: &mut RenderScope, render_context: &RenderContext)`
|
||||
This is the core rendering logic for `Paginator`:
|
||||
1. It checks if `self.index` points to a valid child in `self.children`.
|
||||
2. If a child exists at the current `index`, it prepares a `RenderScope` (cloning its own `RawTransform`).
|
||||
3. It sets the `RenderScope`'s `parent_size` to its own (Paginator's) determined `width` and `height`, so the child lays out correctly within the Paginator's bounds.
|
||||
4. It creates a `DivRenderer` helper to manage the child's positioning.
|
||||
5. It calls `scope.render_widget` for *only the currently active child widget*.
|
||||
6. After the child is rendered, it restores the original `parent_size` to the `RenderScope`.
|
||||
7. It updates its internal `self.size` to reflect the size of the currently displayed child (plus any accumulated size from `DivRenderer`).
|
||||
|
||||
#### `event(&mut self, event: &dyn Event)`
|
||||
This method handles keyboard events for navigation:
|
||||
1. It listens for `crossterm::event::Event::Key` events.
|
||||
2. If `KeyCode::Tab` is pressed:
|
||||
* It increments `self.index`. If `self.index` goes beyond the last child, it wraps around to `0`.
|
||||
3. If `KeyCode::BackTab` (Shift+Tab) is pressed:
|
||||
* It decrements `self.index`. If `self.index` goes below `0`, it wraps around to the last child.
|
||||
These index changes will trigger a re-render in the next frame, displaying the new page.
|
||||
|
||||
#### `is_ghost(&mut self) -> bool`
|
||||
Returns `true`. `Paginator` is a "ghost" element because it is a logical container that controls which of its children is visible, but it does not draw itself. Any styling applied to the `Paginator` widget will apply to the area it manages.
|
||||
|
||||
#### `draw_child(&mut self, element: &Arc<Widget>)`
|
||||
This method is called when a widget is declared as a child of this `Paginator` in `rsx!`.
|
||||
1. It adds the `element` to its internal `children` `Vec`.
|
||||
2. It injects a `NoRenderRoot` component into the child to ensure the main `Screen` rendering loop doesn't render it directly (the `Paginator` handles rendering the active child).
|
||||
|
||||
#### `as_any(&self) -> &dyn std::any::Any` / `as_any_mut(&mut self) -> &mut dyn std::any::Any`
|
||||
Standard implementations for downcasting.
|
||||
|
||||
## Usage in `rsx!`
|
||||
|
||||
The direct children of a `Paginator` element become its pages. You can use any other element (e.g., `Div`, `FlexCol`) as a page container.
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
// Paginator often needs explicit dimensions to define the space for its pages
|
||||
@Transform::new().dimensions(60, 20).center();
|
||||
@Style { background: Background::Solid(0x222222) }; // Optional background for the paginator's area
|
||||
Paginator {
|
||||
// Page 1: Simple text and instructions
|
||||
FlexCol {
|
||||
"Welcome to the first page!"
|
||||
"Press TAB to go to the next page."
|
||||
}
|
||||
|
||||
// Page 2: Contains an input field
|
||||
FlexCol {
|
||||
"This is the second page."
|
||||
"Type something here:"
|
||||
@Transform::new().dimensions(30, 1);
|
||||
@Style { background: Background::Outline(0x555555) };
|
||||
Input { }
|
||||
}
|
||||
|
||||
// Page 3: A heading
|
||||
FlexCol {
|
||||
Heading { "The End" }
|
||||
"This is the last page. Shift+TAB to go back."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
`Paginator` is useful for organizing complex UIs into logical sections, preventing clutter, and improving user experience by allowing easy navigation between distinct views.
|
||||
@@ -0,0 +1,186 @@
|
||||
# `osui::extensions`
|
||||
|
||||
The `extensions` module defines the core traits and types for extending OSUI's functionality. It provides a global event bus and lifecycle hooks that allow custom logic to interact with the entire UI application.
|
||||
|
||||
## `Extension` Trait
|
||||
|
||||
The central trait for adding global behaviors to an OSUI application. Any struct implementing this trait can be registered with the `Screen` to receive lifecycle and event callbacks.
|
||||
|
||||
```rust
|
||||
pub trait Extension {
|
||||
/// Called once when the extension is registered with the `Screen`.
|
||||
#[allow(unused)]
|
||||
fn init(&mut self, _ctx: &Context) {}
|
||||
|
||||
/// Called when any `Event` is dispatched across the system.
|
||||
#[allow(unused)]
|
||||
fn event(&mut self, _ctx: &Context, _event: &dyn Event) {}
|
||||
|
||||
/// Called when `Screen::close()` is invoked, before the application terminates.
|
||||
#[allow(unused)]
|
||||
fn on_close(&mut self) {}
|
||||
|
||||
/// Called before any widgets are rendered in a frame, with a scope for the entire screen.
|
||||
#[allow(unused)]
|
||||
fn render(&mut self, _ctx: &Context, _scope: &mut RenderScope) {}
|
||||
|
||||
/// Called before a specific widget's `Element::render` method is invoked.
|
||||
#[allow(unused)]
|
||||
fn render_widget(&mut self, _ctx: &Context, _scope: &mut RenderScope, _widget: &Arc<Widget>) {}
|
||||
|
||||
/// Called after a specific widget's `Element::after_render` method is invoked.
|
||||
#[allow(unused)]
|
||||
fn after_render_widget(
|
||||
&mut self,
|
||||
_ctx: &Context,
|
||||
_scope: &mut RenderScope,
|
||||
_widget: &Arc<Widget>,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
## `Event` Trait
|
||||
|
||||
A marker trait for types that can be dispatched as events within the OSUI system. Events are type-erased (`dyn Event`) when dispatched, requiring downcasting to retrieve their specific type.
|
||||
|
||||
```rust
|
||||
pub trait Event: Send + Sync {
|
||||
/// Returns a type-erased reference to this object, enabling downcasting.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
impl<'a> dyn Event + 'a {
|
||||
/// Attempts to downcast a dynamic `Event` trait object to a concrete type `T`.
|
||||
///
|
||||
/// # Type Parameters
|
||||
/// * `T`: The concrete event type to downcast to. Must also implement `Event`.
|
||||
///
|
||||
/// # Returns
|
||||
/// `Some(&T)` if the downcast is successful, `None` otherwise.
|
||||
pub fn get<T: Event + 'static>(&self) -> Option<&T> {
|
||||
self.as_any().downcast_ref()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `Context`
|
||||
|
||||
The `Context` struct provides a way for extensions and widgets to interact with the global OSUI `Screen` instance. It holds an `Arc<Screen>` and offers convenience methods for dispatching events, querying widgets, and accessing components.
|
||||
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
pub struct Context {
|
||||
screen: Arc<Screen>,
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Context::new(screen: Arc<Screen>) -> Self`
|
||||
Creates a new `Context` instance.
|
||||
|
||||
**Arguments:**
|
||||
* `screen`: An `Arc` to the `Screen` instance that this context will operate on.
|
||||
|
||||
#### `Context::event<E: Event + Clone + 'static>(&self, e: &E)`
|
||||
Dispatches an event `e` throughout the OSUI system.
|
||||
This will:
|
||||
1. Call the `event` method on all widgets (specifically, on any `Handler<E>` components attached to them, and on the `Element::event` method of focused widgets).
|
||||
2. Call the `event` method on all registered `Extension`s.
|
||||
|
||||
**Arguments:**
|
||||
* `e`: A reference to the event to dispatch.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
// Inside an extension or a component:
|
||||
// self.ctx.event(&MyCustomEvent { /* ... */ });
|
||||
```
|
||||
|
||||
#### `Context::get_widgets(&self) -> MutexGuard<Vec<Arc<Widget>>>`
|
||||
Returns a `MutexGuard` to the `Vec` of `Arc<Widget>` that the `Screen` is currently managing. This allows extensions to iterate over and manipulate the global list of widgets.
|
||||
|
||||
**Returns:**
|
||||
A `MutexGuard` providing mutable access to the list of root widgets.
|
||||
|
||||
#### `Context::iter_components<C: Component + 'static + Clone, F: FnMut(&Arc<Widget>, Option<C>)>(&self, mut iterator: F)`
|
||||
Iterates over all widgets managed by the `Screen` and applies a provided closure to each widget, along with an `Option` of a cloned component of type `C` if the widget has one.
|
||||
|
||||
**Type Parameters:**
|
||||
* `C`: The `Component` type to look for.
|
||||
* `F`: The closure to execute for each widget.
|
||||
|
||||
**Arguments:**
|
||||
* `iterator`: A closure that takes an `Arc<Widget>` and an `Option<C>`.
|
||||
|
||||
#### `Context::get_components<C: Component + 'static + Clone>(&self) -> Vec<C>`
|
||||
Collects all instances of a specific `Component` type from all widgets managed by the `Screen` into a `Vec`.
|
||||
|
||||
**Type Parameters:**
|
||||
* `C`: The `Component` type to collect.
|
||||
|
||||
**Returns:**
|
||||
A `Vec` containing cloned instances of component `C`.
|
||||
|
||||
#### `Context::render_root(&self, scope: &mut RenderScope)`
|
||||
Calls the `render` hook for all registered `Extension`s, allowing them to draw global elements that span the entire screen. This is typically called once per frame before individual widgets are rendered.
|
||||
|
||||
**Arguments:**
|
||||
* `scope`: The `RenderScope` representing the entire screen.
|
||||
|
||||
#### `Context::render(&self, w: &Arc<Widget>, scope: &mut RenderScope)`
|
||||
Calls the `render_widget` hook for all registered `Extension`s for a specific `widget`. This is invoked during the rendering of each individual widget.
|
||||
|
||||
**Arguments:**
|
||||
* `w`: The `Arc<Widget>` currently being rendered.
|
||||
* `scope`: The `RenderScope` for `w`.
|
||||
|
||||
#### `Context::after_render(&self, w: &Arc<Widget>, scope: &mut RenderScope)`
|
||||
Calls the `after_render_widget` hook for all registered `Extension`s for a specific `widget`. This is invoked after a widget's `Element::after_render` method has completed.
|
||||
|
||||
**Arguments:**
|
||||
* `w`: The `Arc<Widget>` that has just finished its `after_render` phase.
|
||||
* `scope`: The `RenderScope` for `w`.
|
||||
|
||||
## `Handler<E>` (Component)
|
||||
|
||||
A component that allows a widget to listen for and react to specific `Event` types `E`.
|
||||
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
pub struct Handler<E: Event>(Arc<Mutex<dyn FnMut(&Arc<Widget>, &E) + Send + Sync>>);
|
||||
```
|
||||
|
||||
### Implementations
|
||||
|
||||
#### `impl<E: Event + 'static> Component for Handler<E>`
|
||||
`Handler<E>` implements the `Component` trait, allowing it to be attached to any `Widget`.
|
||||
|
||||
#### `impl<E: Event + 'static> Handler<E>`
|
||||
#### `Handler::new<F: FnMut(&Arc<Widget>, &E) + Send + Sync + 'static>(f: F) -> Handler<E>`
|
||||
Creates a new `Handler<E>` with the given mutable closure `f`. This closure will be called when an event of type `E` is dispatched.
|
||||
|
||||
**Arguments:**
|
||||
* `f`: The closure to execute when the event occurs. It receives the `Arc<Widget>` it's attached to and a reference to the event.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
event!(ButtonClicked);
|
||||
|
||||
rsx! {
|
||||
@Handler::new(|widget_arc, event: &ButtonClicked| {
|
||||
println!("Button clicked on widget: {:?}", widget_arc);
|
||||
});
|
||||
Div { "My Button" }
|
||||
}
|
||||
```
|
||||
|
||||
#### `Handler::call(&self, w: &Arc<Widget>, e: &E)`
|
||||
Invokes the internal closure with the provided widget and event. This method is called internally by `Widget::event`.
|
||||
|
||||
**Arguments:**
|
||||
* `w`: The `Arc<Widget>` that owns this handler.
|
||||
* `e`: The event to process.
|
||||
@@ -0,0 +1,86 @@
|
||||
# `osui::extensions::focus`
|
||||
|
||||
The `focus` module provides extensions and components for managing keyboard focus within your OSUI application. It enables navigation between widgets using keyboard input, which is crucial for interactive elements like `Input` fields.
|
||||
|
||||
## Components
|
||||
|
||||
### `AlwaysFocused` (Component)
|
||||
```rust
|
||||
component!(AlwaysFocused);
|
||||
```
|
||||
A marker component that, when attached to a widget, ensures that the widget remains focused regardless of user navigation. This is useful for global event handlers or root containers that should always receive input.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@AlwaysFocused;
|
||||
@Handler::new(|_, e: &crossterm::event::Event| {
|
||||
// This handler will always receive events.
|
||||
});
|
||||
Div { "Global Handler" }
|
||||
}
|
||||
```
|
||||
|
||||
### `Focused` (Component)
|
||||
```rust
|
||||
component!(Focused);
|
||||
```
|
||||
A marker component that, when attached to a widget, indicates that this widget should be initially focused when the application starts or when focus is determined. The `RelativeFocusExtension` uses this to set initial focus.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
FlexCol {
|
||||
"Username:"
|
||||
@Focused; // This Input will be focused by default
|
||||
Input { }
|
||||
"Password:"
|
||||
Input { }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `RelativeFocusExtension`
|
||||
|
||||
This extension manages focus between eligible widgets based on their relative positions on the screen. It allows users to navigate the UI using arrow keys (often combined with Shift).
|
||||
|
||||
```rust
|
||||
pub struct RelativeFocusExtension {
|
||||
cursor: usize, // Internal index of the currently focused widget
|
||||
rendered: Arc<Mutex<Vec<(usize, u16, u16)>>>, // Stores widget indices and their (x,y) positions
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `RelativeFocusExtension::new() -> Self`
|
||||
Creates a new instance of the `RelativeFocusExtension`.
|
||||
|
||||
### `Extension` Trait Implementation
|
||||
|
||||
#### `init(&mut self, _ctx: &Context)`
|
||||
When initialized, it checks existing widgets for the `Focused` component and sets the first one found as focused.
|
||||
|
||||
#### `event(&mut self, ctx: &Context, event: &dyn Event)`
|
||||
Listens for `crossterm::event::Event`s, specifically `KeyCode::Right`, `Left`, `Up`, `Down` when combined with `KeyModifiers::SHIFT`.
|
||||
When such a key is pressed, it calculates the closest eligible widget in the specified direction based on the `rendered` positions and updates `self.cursor` to that widget's index. It then sets the focus state (`set_focused`) for all widgets accordingly.
|
||||
Widgets with the `AlwaysFocused` component remain focused.
|
||||
|
||||
#### `render(&mut self, _ctx: &Context, _scope: &mut RenderScope)`
|
||||
Clears the internal `rendered` list at the beginning of each frame. This ensures that widget positions are recalculated based on the latest render.
|
||||
|
||||
#### `after_render_widget(&mut self, ctx: &Context, scope: &mut RenderScope, widget: &Arc<Widget>)`
|
||||
After each widget is rendered, this hook captures its absolute position (`RawTransform.x`, `RawTransform.y`) and its index in the `Screen`'s widget list, storing it in the `rendered` list. This data is then used by the `event` method for focus navigation calculations. It runs in a separate thread for performance.
|
||||
|
||||
### How Focus Navigation Works
|
||||
|
||||
1. **Position Tracking**: During the `after_render_widget` phase, the `RelativeFocusExtension` records the `(x, y)` coordinates of every non-ghost widget that is rendered.
|
||||
2. **Event Listening**: When the user presses `Shift + Arrow Key`, the `event` method is triggered.
|
||||
3. **Closest Widget Calculation**: The `find_closest_in_direction` helper function (internal to the module) determines the next best widget to focus. It prioritizes:
|
||||
* Widgets directly in the line of the arrow key (same row for left/right, same column for up/down).
|
||||
* If no direct match, it finds the geographically closest widget in the general direction.
|
||||
4. **Focus Update**: The extension then updates the `focused` state on widgets using `widget.set_focused(true/false)`. Widgets with the `Focused` component (usually `Input` fields) will then react to subsequent key events.
|
||||
|
||||
**Note on `Tab` / `BackTab`**: While `RelativeFocusExtension` handles arrow keys, `Paginator` elements handle `Tab` and `Shift+Tab` internally to cycle through their pages. For general `Tab` navigation across *all* focusable elements in the UI, you would implement a custom `Handler` on a root widget that iterates through widgets and sets focus, or extend `RelativeFocusExtension` to handle `Tab` more generically.
|
||||
@@ -0,0 +1,78 @@
|
||||
# `osui::extensions::id`
|
||||
|
||||
The `id` module provides a simple way to assign unique identifiers to OSUI widgets and retrieve them later by that ID. This is useful for direct access to specific UI elements for manipulation or querying.
|
||||
|
||||
## `Id` (Component)
|
||||
|
||||
```rust
|
||||
component!(Id(pub usize));
|
||||
```
|
||||
A tuple struct component that holds a `usize` value, representing a unique identifier for the widget it's attached to.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@Id(101); // Assign ID 101 to this Div
|
||||
Div { "My uniquely identified div" }
|
||||
}
|
||||
```
|
||||
|
||||
## `IdExtension`
|
||||
|
||||
The `IdExtension` provides functionality to look up widgets by their `Id` component.
|
||||
|
||||
```rust
|
||||
pub struct IdExtension(pub Arc<Screen>);
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `IdExtension::new(screen: Arc<Screen>) -> Arc<Self>`
|
||||
Creates a new `IdExtension` instance. It requires an `Arc<Screen>` because it needs to access the screen's list of widgets to perform ID lookups.
|
||||
|
||||
**Arguments:**
|
||||
* `screen`: An `Arc` to the main `Screen` instance.
|
||||
|
||||
**Returns:**
|
||||
An `Arc<IdExtension>`. It's recommended to store extensions that are queried by other parts of your app in an `Arc` so they can be easily shared.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
let id_ext = IdExtension::new(screen.clone());
|
||||
screen.extension(id_ext.clone()); // Register the extension
|
||||
```
|
||||
|
||||
#### `IdExtension::get_element(self: &Arc<IdExtension>, id: usize) -> Option<Arc<Widget>>`
|
||||
Retrieves an `Arc<Widget>` from the `Screen`'s widget list that has an `Id` component matching the provided `id`.
|
||||
|
||||
**Arguments:**
|
||||
* `id`: The `usize` identifier to search for.
|
||||
|
||||
**Returns:**
|
||||
`Some(Arc<Widget>)` if a widget with the matching ID is found, `None` otherwise.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
let id_ext = IdExtension::new(screen.clone());
|
||||
screen.extension(id_ext.clone());
|
||||
|
||||
rsx! {
|
||||
@Id(100);
|
||||
Div { "Hello, ID 100!" }
|
||||
}.draw(&screen);
|
||||
|
||||
// Later, perhaps in an event handler or another part of your app:
|
||||
if let Some(widget_100) = id_ext.get_element(100) {
|
||||
// You can now interact with widget_100 directly, e.g., get its transform, set components etc.
|
||||
// let transform: Transform = widget_100.get().unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
### `Extension` Trait Implementation
|
||||
|
||||
`IdExtension` currently has an empty implementation for the `Extension` trait (`impl Extension for Arc<IdExtension> {}`). This means it doesn't hook into any lifecycle events or event dispatching by default. Its primary purpose is to provide the `get_element` lookup method, which is invoked manually when needed.
|
||||
@@ -0,0 +1,75 @@
|
||||
# `osui::extensions::input_handling`
|
||||
|
||||
The `input_handling` module provides the `InputExtension`, which integrates `crossterm` for low-level terminal input and dispatches these events throughout the OSUI system. This is a fundamental extension required for any interactive OSUI application.
|
||||
|
||||
## `InputExtension`
|
||||
|
||||
Manages raw terminal input and dispatches `crossterm` events.
|
||||
|
||||
```rust
|
||||
pub struct InputExtension;
|
||||
```
|
||||
|
||||
### `Extension` Trait Implementation
|
||||
|
||||
#### `init(&mut self, ctx: &Context)`
|
||||
Called when the extension is initialized.
|
||||
1. Enables `crossterm`'s raw mode (`crossterm::terminal::enable_raw_mode().unwrap()`). Raw mode allows direct, unbuffered input capture, essential for TUI applications.
|
||||
2. Spawns a new thread. This thread continuously listens for `crossterm::event::read()` events.
|
||||
3. Whenever an event is read successfully, it dispatches the event using `ctx.event(&e)`. This makes the `crossterm::event::Event` available to all widgets (via `Element::event` or `Handler<crossterm::event::Event>` components) and other extensions.
|
||||
|
||||
**Why a separate thread?**
|
||||
Reading input from the terminal (`crossterm::event::read()`) is a blocking operation. Spawning it in a separate thread prevents the main rendering loop from freezing while waiting for user input, ensuring the UI remains responsive.
|
||||
|
||||
#### `on_close(&mut self)`
|
||||
Called when `Screen::close()` is invoked.
|
||||
1. Disables `crossterm`'s raw mode (`crossterm::terminal::disable_raw_mode().unwrap()`). This restores the terminal to its normal, buffered input state, which is crucial for a clean exit.
|
||||
|
||||
### `Event` Trait Implementation
|
||||
|
||||
The `crossterm::event::Event` enum itself implements OSUI's `Event` trait, allowing `InputExtension` to dispatch it directly.
|
||||
|
||||
```rust
|
||||
impl crate::extensions::Event for crossterm::event::Event {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
This means you can easily listen for `crossterm` events in your widgets or other extensions using `Handler<crossterm::event::Event>` or by downcasting the `dyn Event` in an `Extension::event` method.
|
||||
|
||||
## Usage
|
||||
|
||||
You must register the `InputExtension` with your `Screen` for keyboard and mouse input to work in your OSUI application.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
|
||||
// Register the InputExtension
|
||||
screen.extension(InputExtension);
|
||||
|
||||
// Other extensions and UI setup
|
||||
screen.extension(RelativeFocusExtension::new()); // Often used with InputExtension for navigation
|
||||
|
||||
rsx! {
|
||||
// A global event handler to exit on Escape key, relying on InputExtension
|
||||
@Handler::new({
|
||||
let screen = screen.clone();
|
||||
move |_, e: &crossterm::event::Event| {
|
||||
if let crossterm::event::Event::Key(crossterm::event::KeyEvent { code: crossterm::event::KeyCode::Esc, .. }) = e {
|
||||
screen.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
// An Input widget that will receive key events from InputExtension
|
||||
Input { }
|
||||
}.draw(&screen);
|
||||
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
`InputExtension` is a foundational component for building interactive terminal user interfaces with OSUI.
|
||||
@@ -0,0 +1,93 @@
|
||||
# `osui::extensions::tick`
|
||||
|
||||
The `tick` module provides the `TickExtension` and `TickEvent`, enabling applications to receive periodic updates at a defined rate. This is useful for animations, game loops, or any time-based logic.
|
||||
|
||||
## `TickEvent` (Event)
|
||||
|
||||
```rust
|
||||
event!(TickEvent(pub u32));
|
||||
```
|
||||
A custom event type dispatched by the `TickExtension`. It contains a `u32` value representing the current tick count since the extension started.
|
||||
|
||||
**Fields:**
|
||||
* `0`: `u32` - The current tick count.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
// Listen for TickEvent
|
||||
rsx! {
|
||||
@Handler::new(|_, e: &TickEvent| {
|
||||
println!("Tick: {}", e.0);
|
||||
});
|
||||
Div { "Listening for ticks" }
|
||||
}
|
||||
```
|
||||
|
||||
## `TickExtension`
|
||||
|
||||
Dispatches `TickEvent`s at a configurable interval.
|
||||
|
||||
```rust
|
||||
pub struct TickExtension(pub u16);
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
* `0`: `u16` - The desired ticks per second (Hz). For example, `TickExtension(30)` would dispatch a `TickEvent` approximately every 33 milliseconds (1000ms / 30Hz).
|
||||
|
||||
### `Extension` Trait Implementation
|
||||
|
||||
#### `init(&mut self, ctx: &Context)`
|
||||
Called when the `TickExtension` is registered with the `Screen`.
|
||||
1. Calculates the `rate_dur` (duration per tick) in milliseconds: `1000 / self.0 as u64`.
|
||||
2. Spawns a new thread.
|
||||
3. In this new thread, it enters an infinite loop:
|
||||
* It dispatches a `TickEvent` with the current tick count using `ctx.event(&TickEvent(tick))`.
|
||||
* The `tick` counter is incremented.
|
||||
* The thread sleeps for the calculated `rate_dur`.
|
||||
|
||||
**Why a separate thread?**
|
||||
Similar to `InputExtension`, `TickExtension` spawns a separate thread because `std::thread::sleep` is a blocking operation. This ensures that the main rendering loop continues to run smoothly, independent of the tick rate.
|
||||
|
||||
## Usage
|
||||
|
||||
To use `TickExtension`, you simply need to create an instance with your desired tick rate and register it with the `Screen`. Then, your widgets or other extensions can listen for `TickEvent`s using `Handler<TickEvent>`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
|
||||
// Register essential extensions
|
||||
screen.extension(InputExtension);
|
||||
screen.extension(RelativeFocusExtension::new());
|
||||
|
||||
// Register TickExtension for 30 ticks per second
|
||||
screen.extension(TickExtension(30));
|
||||
|
||||
// Create a state to display the tick count
|
||||
let tick_count_state = use_state(0u32);
|
||||
|
||||
rsx! {
|
||||
// Attach a Handler to update the state on each TickEvent
|
||||
@Handler::new({
|
||||
let state_clone = tick_count_state.clone();
|
||||
move |_, e: &TickEvent| {
|
||||
state_clone.set(e.0); // Update the state with the current tick count
|
||||
}
|
||||
});
|
||||
// Display the reactive tick count
|
||||
%tick_count_state
|
||||
Div {
|
||||
format!("Current Tick: {}", tick_count_state.get())
|
||||
}
|
||||
}
|
||||
.draw(&screen);
|
||||
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
This example demonstrates how `TickExtension` provides a consistent timing mechanism, allowing you to build dynamic and animated UIs.
|
||||
@@ -0,0 +1,92 @@
|
||||
# `osui::extensions::velocity`
|
||||
|
||||
The `velocity` module provides the `VelocityExtension` and `Velocity` component, enabling simple animation by automatically moving widgets with a constant velocity.
|
||||
|
||||
## `Velocity` (Component)
|
||||
|
||||
```rust
|
||||
component!(Velocity(pub i32, pub i32));
|
||||
```
|
||||
A tuple struct component that holds two `i32` values, representing the horizontal (`x`) and vertical (`y`) velocity of a widget. The values represent "cells per second" for movement.
|
||||
|
||||
**Fields:**
|
||||
* `0`: `i32` - Horizontal velocity (cells per second). Positive moves right, negative moves left.
|
||||
* `1`: `i32` - Vertical velocity (cells per second). Positive moves down, negative moves up.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@Velocity(10, 0); // Move 10 cells right per second
|
||||
@Transform::new().x(0).y(0);
|
||||
Div { "Moving Right" }
|
||||
}
|
||||
```
|
||||
|
||||
## `VelocityExtension`
|
||||
|
||||
This extension is responsible for applying the specified `Velocity` to widgets that also have a `Transform` component, causing them to move across the screen.
|
||||
|
||||
```rust
|
||||
pub struct VelocityExtension;
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `VelocityExtension::apply_velocity(ticks: u16, velocity: i32, x: &mut u16)`
|
||||
An internal helper function that updates a single coordinate (`x` or `y`) based on the elapsed `ticks` and `velocity`. It ensures that movement occurs at the specified `velocity` (cells per second) by checking if enough time has passed since the last movement.
|
||||
|
||||
#### `VelocityExtension::apply_velocity_xy(ticks: u16, widget: &Arc<Widget>)`
|
||||
An internal helper that applies `Velocity` to both `x` and `y` coordinates of a widget's `Transform` component. It retrieves the `Velocity` and `Transform` components, calculates new positions using `apply_velocity`, and then updates the `Transform` component on the widget.
|
||||
|
||||
### `Extension` Trait Implementation
|
||||
|
||||
#### `init(&mut self, ctx: &super::Context)`
|
||||
Called when the extension is initialized.
|
||||
1. Clones the `Context`.
|
||||
2. Spawns a new thread.
|
||||
3. In this thread, it enters an infinite loop:
|
||||
* Initializes a `tick` counter (0 to 1000, then resets). This acts as a granular time counter.
|
||||
* Iterates over all widgets managed by the `Screen` (obtained via `ctx.get_widgets()`).
|
||||
* For each widget, it calls `Self::apply_velocity_xy(tick, widget)` to update its position if it has a `Velocity` and `Transform` component.
|
||||
* The thread sleeps for 1 millisecond. This ensures very frequent checks and smooth potential movement, as `velocity` is defined in "cells per second".
|
||||
|
||||
**Why a separate thread?**
|
||||
Similar to `InputExtension` and `TickExtension`, a separate thread is used because time-based operations like `thread::sleep` are blocking. This prevents the velocity calculations from blocking the main rendering loop and ensures smooth animations. The 1ms sleep provides a high refresh rate for velocity updates.
|
||||
|
||||
## Usage
|
||||
|
||||
To use `VelocityExtension`, you need to:
|
||||
1. Register it with your `Screen`.
|
||||
2. Attach both a `Transform` and a `Velocity` component to the widget you want to animate. The `Transform` should have `Position::Const` for the coordinates you want to animate.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
|
||||
screen.extension(InputExtension); // Required for general terminal operations
|
||||
screen.extension(RelativeFocusExtension::new()); // Optional, but good practice
|
||||
screen.extension(VelocityExtension); // Register the VelocityExtension
|
||||
|
||||
rsx! {
|
||||
// A Div that moves right at 10 cells/second
|
||||
@Velocity(10, 0);
|
||||
@Transform::new().x(0).y(0).dimensions(10, 1); // Must have Const position to be moved
|
||||
@Style { background: Background::Solid(0xFF0000) };
|
||||
Div { "Moving Text" }
|
||||
|
||||
// A Div that moves down at 5 cells/second, starting below the first
|
||||
@Velocity(0, 5);
|
||||
@Transform::new().x(0).y(2).dimensions(10, 1);
|
||||
@Style { background: Background::Solid(0x0000FF) };
|
||||
Div { "Moving Down" }
|
||||
}
|
||||
.draw(&screen);
|
||||
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
`VelocityExtension` provides a simple, component-based way to add continuous motion to your OSUI elements. For more complex animations, you might combine it with `TickExtension` and custom logic within a widget's `event` method or a more advanced animation extension.
|
||||
@@ -0,0 +1,86 @@
|
||||
# `osui::frontend`
|
||||
|
||||
The `frontend` module defines the internal structures that represent the declarative UI tree parsed by the `rsx!` macro. This tree is then used to construct the actual `Widget` hierarchy managed by the `Screen`.
|
||||
|
||||
## `RsxElement` Enum
|
||||
|
||||
Represents a single node in the RSX tree before it's converted into a `Widget`.
|
||||
|
||||
```rust
|
||||
pub enum RsxElement {
|
||||
/// A static widget with its children.
|
||||
/// Used when the `rsx!` macro detects a `static` element or a string literal without dependencies.
|
||||
Element(StaticWidget, Rsx),
|
||||
|
||||
/// A dynamically generated widget with its dependencies and children.
|
||||
/// Used when the `rsx!` macro detects a `%dependency` or a non-static string literal.
|
||||
DynElement(
|
||||
Box<dyn FnMut() -> WidgetLoad + Send + Sync>,
|
||||
Vec<Box<dyn DependencyHandler>>,
|
||||
Rsx,
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
* **`Element(StaticWidget, Rsx)`**: Holds a pre-constructed `StaticWidget` and its child `Rsx` tree. This variant is for UI parts that do not change dynamically.
|
||||
* **`DynElement(Box<dyn FnMut() -> WidgetLoad + Send + Sync>, Vec<Box<dyn DependencyHandler>>, Rsx)`**:
|
||||
* The `Box<dyn FnMut() -> WidgetLoad + Send + Sync>` is a closure that, when executed, will create the `WidgetLoad` for this dynamic widget. This allows deferring the widget's construction until it's actually needed or when it needs to be rebuilt.
|
||||
* `Vec<Box<dyn DependencyHandler>>`: A list of reactive dependencies (like `State<T>`) that, when changed, will trigger this dynamic widget to rebuild itself by re-executing its `FnMut() -> WidgetLoad` closure.
|
||||
* `Rsx`: The child RSX tree for this dynamic widget.
|
||||
|
||||
## `Rsx` Struct
|
||||
|
||||
A container representing a collection (a list or a group) of `RsxElement`s. This is the top-level type generated by the `rsx!` macro.
|
||||
|
||||
```rust
|
||||
pub struct Rsx(pub Vec<RsxElement>);
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Rsx::draw(self, screen: &Arc<Screen>)`
|
||||
Draws the `Rsx` tree onto the given `Screen` as root-level widgets.
|
||||
This is the entry point for rendering the UI defined by an `rsx!` block. It effectively calls `draw_parent` with no parent.
|
||||
|
||||
**Arguments:**
|
||||
* `screen`: An `Arc` to the `Screen` instance.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
rsx! { "Hello, OSUI!" }.draw(&screen);
|
||||
```
|
||||
|
||||
#### `Rsx::draw_parent(self, screen: &Arc<Screen>, parent: Option<Arc<Widget>>)`
|
||||
Recursively draws the `Rsx` tree with an optional parent widget.
|
||||
This method iterates through the `RsxElement`s:
|
||||
* For `DynElement`s, it calls the internal `FnMut()` to create a `WidgetLoad`, then creates an `Arc<Widget::Dynamic>` via `screen.draw_box_dyn`. It registers all the element's dependencies with this new dynamic widget.
|
||||
* For `Element`s (static), it creates an `Arc<Widget::Static>` via `screen.draw_widget`.
|
||||
* If a `parent` `Arc<Widget>` is provided, the newly created child widget is registered with the parent's `Element` via `parent.get_elem().draw_child(&new_widget)`. This is how the parent-child relationships are established in the runtime widget tree.
|
||||
* It then recursively calls `draw_parent` for the child `Rsx` tree, passing the newly created widget as the `parent`.
|
||||
|
||||
**Arguments:**
|
||||
* `screen`: An `Arc` to the `Screen` instance.
|
||||
* `parent`: An `Option<Arc<Widget>>` representing the parent widget. `None` for root widgets.
|
||||
|
||||
#### `Rsx::create_element<F: FnMut() -> WidgetLoad + Send + Sync + 'static>(&mut self, load: F, dependencies: Vec<Box<dyn DependencyHandler>>, children: Rsx)`
|
||||
Adds a dynamically constructed `RsxElement::DynElement` to this `Rsx` container. This method is primarily used internally by the `rsx!` macro.
|
||||
|
||||
**Arguments:**
|
||||
* `load`: A closure that generates the `WidgetLoad` for the dynamic element.
|
||||
* `dependencies`: A `Vec` of boxed `DependencyHandler`s that this element depends on.
|
||||
* `children`: The `Rsx` tree representing the children of this element.
|
||||
|
||||
#### `Rsx::create_element_static(&mut self, element: StaticWidget, children: Rsx)`
|
||||
Adds a statically defined `RsxElement::Element` to this `Rsx` container. This method is primarily used internally by the `rsx!` macro.
|
||||
|
||||
**Arguments:**
|
||||
* `element`: A pre-constructed `StaticWidget`.
|
||||
* `children`: The `Rsx` tree representing the children of this element.
|
||||
|
||||
#### `Rsx::expand(&mut self, other: &mut Rsx)`
|
||||
Appends the elements from another `Rsx` tree into this one. This is used by the `rsx!` macro when handling the `$expand => (...args)` syntax.
|
||||
|
||||
**Arguments:**
|
||||
* `other`: A mutable reference to another `Rsx` container whose elements will be moved into `self`.
|
||||
@@ -0,0 +1,31 @@
|
||||
# API Reference
|
||||
|
||||
This section provides detailed documentation for all public modules, structs, enums, traits, and macros within the OSUI library.
|
||||
|
||||
## Core Concepts & Structure
|
||||
|
||||
* [Screen](../reference/screen.md): The main application context for managing widgets and extensions.
|
||||
* [Widget Model](../reference/widget.md): `Element`, `Component`, `Widget` (Static/Dynamic), and `WidgetLoad`.
|
||||
* [Rendering & Scope](../reference/render-scope.md): `RenderScope`, `RenderMethod`, and `ElementRenderer`.
|
||||
* [State Management](../reference/state.md): `State<T>` and `DependencyHandler` for reactivity.
|
||||
* [Frontend & Macros](../reference/frontend.md): `RsxElement`, `Rsx`, and the `event!`, `component!`, `transform!`, `rsx!` macros.
|
||||
* [Utilities](../reference/utils.md): Helper functions for terminal control and string manipulation.
|
||||
|
||||
## Built-in Components & Extensions
|
||||
|
||||
* [Style & Layout](../reference/style.md): `Transform`, `Position`, `Dimension`, `Style`, `Background`.
|
||||
* [Extensions Overview](../reference/extensions.md): The `Extension` trait, `Event` trait, and `Context` for global behaviors.
|
||||
* [Focus Extension](../reference/extensions/focus.md): `AlwaysFocused`, `Focused`, `RelativeFocusExtension` for keyboard navigation.
|
||||
* [ID Extension](../reference/extensions/id.md): `IdExtension`, `Id` for unique widget identification.
|
||||
* [Input Handling Extension](../reference/extensions/input_handling.md): `InputExtension` for keyboard and mouse input.
|
||||
* [Tick Extension](../reference/extensions/tick.md): `TickExtension`, `TickEvent` for timed events.
|
||||
* [Velocity Extension](../reference/extensions/velocity.md): `VelocityExtension`, `Velocity` for simple animations.
|
||||
|
||||
## Built-in UI Elements
|
||||
|
||||
* [Elements Overview](../reference/elements.md): General Element trait and String as an Element.
|
||||
* [Div](../reference/elements/div.md): A generic container element.
|
||||
* [Flex Containers](../reference/elements/flex.md): `FlexRow` and `FlexCol` for automatic horizontal/vertical layout.
|
||||
* [Heading](../reference/elements/heading.md): Renders large ASCII art text.
|
||||
* [Input](../reference/elements/input.md): An interactive text input field.
|
||||
* [Paginator](../reference/elements/paginator.md): Manages and navigates between multiple pages/children.
|
||||
@@ -0,0 +1,198 @@
|
||||
# Macros
|
||||
|
||||
OSUI provides several declarative macros to simplify common tasks like defining custom events, components, `Transform`s, and UI trees.
|
||||
|
||||
## `event!` Macro
|
||||
|
||||
Declares a struct that implements the `Event` trait, simplifying event type creation for OSUI's reactive system.
|
||||
|
||||
### Variants
|
||||
|
||||
* **`event!(Name)`**: Defines a unit struct named `Name`.
|
||||
* **`event!(Name { ... })`**: Defines a named struct with fields.
|
||||
* **`event!(Name (...))`**: Defines a tuple struct.
|
||||
|
||||
### Usage
|
||||
|
||||
```rust
|
||||
use osui::macros::event; // Import the macro
|
||||
|
||||
// Unit struct event
|
||||
event!(Clicked);
|
||||
|
||||
// Named struct event with fields
|
||||
event!(Resized { width: u32, height: u32 });
|
||||
|
||||
// Tuple struct event
|
||||
event!(Moved(u32, u32));
|
||||
|
||||
// Example usage
|
||||
fn main() {
|
||||
let click_event = Clicked;
|
||||
let resize_event = Resized { width: 80, height: 24 };
|
||||
let move_event = Moved(10, 20);
|
||||
|
||||
// Events can be dispatched and handled
|
||||
// (requires an OSUI screen and event context)
|
||||
}
|
||||
```
|
||||
|
||||
## `component!` Macro
|
||||
|
||||
Declares a struct that implements the `Component` trait, reducing boilerplate when defining new data or behavior extensions for widgets.
|
||||
|
||||
### Variants
|
||||
|
||||
* **`component!(Name)`**: Defines a unit struct.
|
||||
* **`component!(Name { ... })`**: Defines a named struct with fields.
|
||||
* **`component!(Name (...))`**: Defines a tuple struct.
|
||||
|
||||
### Usage
|
||||
|
||||
```rust
|
||||
use osui::macros::component; // Import the macro
|
||||
|
||||
// Unit struct component (e.g., a marker for a property)
|
||||
component!(Focusable);
|
||||
|
||||
// Named struct component (e.g., a tooltip message)
|
||||
component!(Tooltip { text: String });
|
||||
|
||||
// Tuple struct component (e.g., a fixed size)
|
||||
component!(Size(u32, u32));
|
||||
|
||||
// Example usage (assuming a widget instance `my_widget`)
|
||||
// my_widget.component(Focusable);
|
||||
// my_widget.component(Tooltip { text: "Hello".to_string() });
|
||||
// my_widget.component(Size(100, 50));
|
||||
```
|
||||
|
||||
## `event_handler!` Macro
|
||||
|
||||
Creates an event handler closure that safely calls a method on a `self` instance. This is particularly useful when you need to register a `'static` event handler that interacts with the current object, often requiring unsafe raw pointers.
|
||||
|
||||
### Arguments
|
||||
|
||||
* `$self_ty`: The type of `self` (e.g., `Self` or a specific struct name).
|
||||
* `$self`: The instance variable (usually `self`) being used.
|
||||
* `$events`: The event source object, which must have an `.on` method (e.g., an `EventHandler` wrapper that allows registering closures). This part is not shown in the source, but it implies such an interface.
|
||||
* `$method`: The method on `$self` to call when an event is received.
|
||||
|
||||
### Safety
|
||||
|
||||
This macro uses `unsafe` code to cast `self` to a raw pointer and dereference it. It is the developer's responsibility to ensure that the `self` reference remains valid for the entire lifetime of the generated closure. Incorrect use can lead to use-after-free or other memory safety issues. Use with extreme caution and only when necessary for `'static` lifetimes.
|
||||
|
||||
### Usage (Conceptual Example)
|
||||
|
||||
```rust
|
||||
use osui::prelude::*; // Assuming event_handler! is in prelude or imported
|
||||
|
||||
struct MyComponent {
|
||||
// ... fields
|
||||
}
|
||||
|
||||
impl MyComponent {
|
||||
// A method to be called by the event handler
|
||||
fn handle_click(&mut self, _widget: &Arc<Widget>, _event: &Clicked) {
|
||||
println!("MyComponent was clicked!");
|
||||
}
|
||||
|
||||
fn setup_event_listener(self: &Arc<Self>, some_event_source: &Arc<Widget>) {
|
||||
// You'd attach a Handler component to `some_event_source`
|
||||
// which then triggers `self.handle_click`
|
||||
some_event_source.set_component(Handler::new({
|
||||
// This is a conceptual expansion of what event_handler! might do
|
||||
let self_ref = Arc::downgrade(self); // Weak reference for safety
|
||||
move |widget_arc, event: &Clicked| {
|
||||
if let Some(strong_self) = self_ref.upgrade() {
|
||||
// Safe dereference if the component still exists
|
||||
let mut_self = Arc::get_mut(&mut strong_self).unwrap(); // Requires Arc to be unique
|
||||
mut_self.handle_click(widget_arc, event);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
```
|
||||
**Note**: The provided `event_handler!` macro in the source code directly converts `self` to a raw pointer. The example above shows a safer, `Arc`-based approach that's more common in modern Rust GUI frameworks for `'static` closures where the lifetime of `self` is not guaranteed. OSUI's `Handler` component itself simplifies common patterns, often making direct use of `event_handler!` macro unnecessary unless you are working with bare `&mut self` on objects whose lifetime is strictly controlled.
|
||||
|
||||
## `transform!` Macro
|
||||
|
||||
A convenient macro for constructing a `Transform` component with specified properties. It simplifies the setup compared to using `Transform::new()` followed by multiple fluent method calls.
|
||||
|
||||
### Arguments
|
||||
|
||||
Takes comma-separated key-value pairs where the key is a public field of `Transform` and the value is an expression that can be converted into the field's type (e.g., `u16` for `Position::Const`, `Dimension::Const`).
|
||||
|
||||
### Usage
|
||||
|
||||
```rust
|
||||
use osui::prelude::*; // Imports Transform, Position, Dimension
|
||||
|
||||
rsx! {
|
||||
// Sets x: Const(10), y: Center, width: Full, height: Const(5), px: 1
|
||||
@transform!(x: 10, y: Center, width: Full, height: 5, px: 1);
|
||||
Div { "Transformed Div" }
|
||||
|
||||
// Minimal transform, only setting width
|
||||
@transform!(width: 20);
|
||||
Div { "Width 20" }
|
||||
}
|
||||
```
|
||||
This macro makes it very concise to apply layout rules directly within your `rsx!` syntax.
|
||||
|
||||
## `rsx!` Macro
|
||||
|
||||
The primary macro for declaratively defining your OSUI user interface. It provides a JSX-like syntax for nesting elements, attaching components, and specifying dependencies.
|
||||
|
||||
### Arguments
|
||||
|
||||
Takes a sequence of UI elements, potentially nested within curly braces `{}`.
|
||||
|
||||
### Features
|
||||
|
||||
* **Text Literals**: Direct strings (e.g., `"Hello"`) become text elements.
|
||||
* **Element Tags**: Element struct names (e.g., `Div`, `Input`) followed by properties and children.
|
||||
* **Properties**: `field: value` pairs set element properties (e.g., `Heading, smooth: true,`).
|
||||
* **Children**: Elements nested within `{}` are children of the parent.
|
||||
* **Dependencies**: `%variable_name` registers `variable_name` (must implement `DependencyHandler`) as a dependency for a dynamic widget.
|
||||
* **Components**: `@ComponentType` or `@ComponentType::new(args)` attaches a component to the element.
|
||||
* **Static Elements**: `static ElementType { ... }` creates a `StaticWidget` (no reactivity overhead).
|
||||
* **Expansion**: `$another_macro => (args)` allows embedding UI generated by other macros/functions.
|
||||
|
||||
### Usage
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
let my_state_var = use_state("Initial".to_string());
|
||||
|
||||
rsx! {
|
||||
// Simple text
|
||||
"Welcome to OSUI!"
|
||||
|
||||
// Static Div with styling
|
||||
@Transform::new().dimensions(20, 5);
|
||||
@Style { background: Background::Solid(0x333333), foreground: Some(0xFFFFFF) };
|
||||
static Div { "This is a static box." }
|
||||
|
||||
// Dynamic Div reacting to `my_state_var`
|
||||
%my_state_var
|
||||
Div {
|
||||
format!("Current state: {}", my_state_var.get())
|
||||
}
|
||||
|
||||
// FlexRow with Heading and Input
|
||||
FlexRow, gap: 2, {
|
||||
Heading { "User Input" }
|
||||
@Transform::new().dimensions(30, 1);
|
||||
@Style { background: Background::Outline(0xAAAAAA) };
|
||||
@Focused;
|
||||
Input { }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `rsx_inner!` Macro
|
||||
|
||||
An internal, recursive macro used by `rsx!` to parse and build the UI tree. **This macro is not intended for direct use by developers.** Its definition shows the complex pattern matching used to process the various `rsx!` syntax forms.
|
||||
@@ -0,0 +1,150 @@
|
||||
# `osui::render_scope`
|
||||
|
||||
The `render_scope` module defines the `RenderScope` struct, which is central to OSUI's drawing and layout process. It acts as a context for rendering operations, handling transformations, parent-child dimensions, and collecting drawing instructions before they are flushed to the terminal.
|
||||
|
||||
## `ElementRenderer` Trait
|
||||
|
||||
A trait that can be implemented by custom renderers, allowing them to hook into the drawing process right before `RenderScope::draw` is called. Used internally by container elements like `Div` and `Flex`.
|
||||
|
||||
```rust
|
||||
pub trait ElementRenderer {
|
||||
/// Called right after the `after_render` function is called for a widget,
|
||||
/// just before the `RenderScope::draw` method is invoked.
|
||||
#[allow(unused)]
|
||||
fn before_draw(&mut self, scope: &mut RenderScope, widget: &Arc<Widget>) {}
|
||||
}
|
||||
```
|
||||
|
||||
## `RenderMethod` Enum
|
||||
|
||||
An internal enum representing a single primitive draw instruction. These methods are accumulated in `RenderScope`'s `render_stack`.
|
||||
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
enum RenderMethod {
|
||||
/// Plain text rendering at current transform. (x, y, text)
|
||||
Text(u16, u16, String),
|
||||
/// Plain text rendering at current transform with foreground/background swapped. (x, y, text)
|
||||
TextInverted(u16, u16, String),
|
||||
/// Text rendered with a specific 24-bit color. (x, y, text, color)
|
||||
TextColored(u16, u16, String, u32),
|
||||
/// A filled rectangle of a given size and background color. (x, y, width, height, color)
|
||||
Rectangle(u16, u16, u16, u16, u32),
|
||||
}
|
||||
```
|
||||
|
||||
## `RenderScope`
|
||||
|
||||
`RenderScope` is the primary context object passed around during the rendering phase. It contains mutable state for the current widget's layout, style, and accumulated draw commands.
|
||||
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
pub struct RenderScope {
|
||||
transform: RawTransform, // Resolved layout (position, dimensions, padding)
|
||||
render_stack: Vec<RenderMethod>, // Stack of drawing instructions
|
||||
parent_width: u16, // Width of the parent container
|
||||
parent_height: u16, // Height of the parent container
|
||||
style: Style, // Current style applied to this scope
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `RenderScope::new() -> RenderScope`
|
||||
Creates a new, empty `RenderScope` with default `RawTransform` and `Style`.
|
||||
|
||||
#### `RenderScope::set_transform_raw(&mut self, transform: RawTransform)`
|
||||
Directly sets the internal `RawTransform` for this scope. This is usually managed internally or by `ElementRenderer`s.
|
||||
|
||||
#### `RenderScope::set_transform(&mut self, transform: &Transform)`
|
||||
Applies a high-level `Transform` configuration to this scope. This method resolves the `Position` and `Dimension` rules into the concrete `RawTransform` based on the `parent_width` and `parent_height` of the scope. It also applies padding (`px`, `py`).
|
||||
|
||||
#### `RenderScope::draw_text(&mut self, x: u16, y: u16, text: &str)`
|
||||
Adds a plain text draw instruction to the `render_stack`.
|
||||
The `(x, y)` coordinates are relative to the current `RenderScope`'s top-left corner (after its `Transform` is applied and considering padding). This method also updates the `RenderScope`'s `RawTransform` width and height if the drawn text exceeds the current content size, which is important for `Dimension::Content`.
|
||||
|
||||
#### `RenderScope::draw_text_inverted(&mut self, x: u16, y: u16, text: &str)`
|
||||
Adds a text draw instruction where the foreground and background colors are swapped (inverted).
|
||||
|
||||
#### `RenderScope::draw_text_colored(&mut self, x: u16, y: u16, text: &str, color: u32)`
|
||||
Adds a text draw instruction with a specific 24-bit RGB foreground `color`.
|
||||
|
||||
#### `RenderScope::draw_rect(&mut self, x: u16, y: u16, width: u16, height: u16, color: u32)`
|
||||
Adds a filled rectangle draw instruction. The `(x, y)` coordinates are relative to the current `RenderScope`'s top-left corner. This method also updates the `RenderScope`'s `RawTransform` width and height if the rectangle exceeds the current content size.
|
||||
|
||||
#### `RenderScope::use_area(&mut self, width: u16, height: u16)`
|
||||
Manually ensures that the `RenderScope`'s `RawTransform` has at least the specified `width` and `height`. This is useful for elements with `Dimension::Content` that might not draw explicit text or rectangles but still need to claim space.
|
||||
|
||||
#### `RenderScope::draw(&self)`
|
||||
Flushes all accumulated `RenderMethod` instructions in the `render_stack` to the actual terminal. This method also draws any background defined by the `Style` component (e.g., solid fill, outline) *before* the text/rectangle commands.
|
||||
|
||||
#### `RenderScope::clear(&mut self)`
|
||||
Clears all accumulated render instructions and resets the internal `RawTransform` and `Style` to defaults. This is called at the beginning of rendering each new widget.
|
||||
|
||||
#### `RenderScope::get_size(&self) -> (u16, u16)`
|
||||
Returns the current width and height of the `RenderScope`'s internal `RawTransform`. This represents the *calculated* size of the widget's content area.
|
||||
|
||||
#### `RenderScope::get_size_or(&self, width: u16, height: u16) -> (u16, u16)`
|
||||
Returns the current size. If the current width or height is `0`, it defaults to the provided `width` or `height` respectively.
|
||||
|
||||
#### `RenderScope::get_size_or_parent(&self) -> (u16, u16)`
|
||||
Returns the current size. If the current width or height is `0`, it defaults to the `parent_width` or `parent_height` respectively.
|
||||
|
||||
#### `RenderScope::get_parent_size(&self) -> (u16, u16)`
|
||||
Returns the width and height of the parent container that this `RenderScope` is operating within. This is crucial for `Dimension::Full` and `Position::Center`/`End` calculations.
|
||||
|
||||
#### `RenderScope::set_parent_size(&mut self, width: u16, height: u16)`
|
||||
Sets the dimensions of the parent container for this `RenderScope`. Container elements (like `Div`, `FlexRow`) use this to define the available space for their children.
|
||||
|
||||
#### `RenderScope::get_transform_mut(&mut self) -> &mut RawTransform`
|
||||
Returns a mutable reference to the internal `RawTransform`. Use with caution.
|
||||
|
||||
#### `RenderScope::get_transform(&self) -> &RawTransform`
|
||||
Returns an immutable reference to the internal `RawTransform`.
|
||||
|
||||
#### `RenderScope::set_style(&mut self, style: Style)`
|
||||
Sets the `Style` for the current render scope. This style will apply to any `draw_text` or background drawing operations within this scope.
|
||||
|
||||
#### `RenderScope::get_style(&mut self) -> &mut Style`
|
||||
Returns a mutable reference to the `Style` currently applied to this scope.
|
||||
|
||||
#### `RenderScope::render_widget(&mut self, renderer: &mut dyn ElementRenderer, ctx: &crate::extensions::Context, widget: &std::sync::Arc<crate::widget::Widget>) -> bool`
|
||||
This is the central function for rendering a single widget and its associated process. It performs:
|
||||
1. Clears the `RenderScope`.
|
||||
2. Applies `Style` and `Transform` components to the `RenderScope`.
|
||||
3. Calls the widget's `Element::render` method.
|
||||
4. Calls `Context::render` (extension hook `render_widget`).
|
||||
5. Re-applies `Transform` (for final position based on calculated size).
|
||||
6. Calls `ElementRenderer::before_draw` (for parent-controlled child positioning).
|
||||
7. Calls `RenderScope::draw` to flush commands to terminal.
|
||||
8. Calls the widget's `Element::after_render` method.
|
||||
9. Calls `Context::after_render` (extension hook `after_render_widget`).
|
||||
10. Calls `widget.auto_refresh()` for dynamic widgets.
|
||||
|
||||
**Returns:**
|
||||
`true` if the widget was rendered, `false` if it had a `NoRender` component.
|
||||
|
||||
## `RenderContext`
|
||||
|
||||
A wrapper around the global `Context` that also indicates whether the currently rendering widget is focused.
|
||||
|
||||
```rust
|
||||
pub struct RenderContext(Context, bool);
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `RenderContext::new(c: &Context, focused: bool) -> Self`
|
||||
Creates a new `RenderContext`.
|
||||
|
||||
#### `RenderContext::is_focused(&self) -> bool`
|
||||
Returns `true` if the widget associated with this `RenderContext` is currently focused. Elements often use this to draw a focus indicator.
|
||||
|
||||
#### `RenderContext::render(&self, w: &Arc<Widget>, scope: &mut RenderScope)`
|
||||
Delegates to `Context::render`, allowing the widget to trigger extension `render_widget` hooks.
|
||||
|
||||
#### `RenderContext::after_render(&self, w: &Arc<Widget>, scope: &mut RenderScope)`
|
||||
Delegates to `Context::after_render`, allowing the widget to trigger extension `after_render_widget` hooks.
|
||||
|
||||
#### `RenderContext::get_context(&self) -> &Context`
|
||||
Returns an immutable reference to the underlying global `Context`.
|
||||
@@ -0,0 +1,182 @@
|
||||
# `osui::Screen`
|
||||
|
||||
The `Screen` struct is the central orchestrator of an OSUI application. It manages the UI tree (widgets), registers extensions, and runs the main rendering and event loop. It's the primary entry point for setting up and running your terminal user interface.
|
||||
|
||||
## Struct Definition
|
||||
|
||||
```rust
|
||||
pub struct Screen {
|
||||
pub widgets: Mutex<Vec<Arc<Widget>>>,
|
||||
extensions: Mutex<Vec<Arc<Mutex<Box<dyn Extension + Send + Sync>>>>>,
|
||||
running: Mutex<bool>,
|
||||
}
|
||||
```
|
||||
|
||||
* **`widgets`**: A `Mutex` protecting a `Vec` of `Arc<Widget>`. This holds the top-level widgets that `Screen` is responsible for rendering.
|
||||
* **`extensions`**: A `Mutex` protecting a `Vec` of registered `Extension` implementations. Extensions provide global behaviors and hooks into the rendering and event pipeline.
|
||||
* **`running`**: A `Mutex<bool>` flag controlling the main event loop's execution.
|
||||
|
||||
## Associated Items
|
||||
|
||||
### Methods
|
||||
|
||||
#### `Screen::new() -> Arc<Self>`
|
||||
Creates a new `Screen` instance wrapped in an `Arc`.
|
||||
It's recommended to always create the `Screen` this way, as its `Arc` can then be easily cloned and passed to extensions or other parts of your application without moving ownership.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
```
|
||||
|
||||
#### `Screen::draw<E: Element + 'static + Send + Sync>(self: &Arc<Self>, element: E) -> Arc<Widget>`
|
||||
Draws a static element to the screen and returns its `Arc<Widget>` handle.
|
||||
This is a convenience method that wraps the provided `Element` into a `StaticWidget`.
|
||||
|
||||
**Arguments:**
|
||||
* `element`: An instance of a type that implements the `Element` trait.
|
||||
|
||||
**Returns:**
|
||||
An `Arc<Widget>` representing the newly drawn static widget.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
let my_text_widget = screen.draw("Hello, Static World!");
|
||||
```
|
||||
|
||||
#### `Screen::draw_box(self: &Arc<Self>, element: BoxedElement) -> Arc<Widget>`
|
||||
Draws a boxed `Element` to the screen and returns its `Arc<Widget>` handle.
|
||||
Similar to `draw`, but takes a `BoxedElement` directly.
|
||||
|
||||
**Arguments:**
|
||||
* `element`: A `Box<dyn Element + Send + Sync>`.
|
||||
|
||||
**Returns:**
|
||||
An `Arc<Widget>` representing the newly drawn static widget.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
let my_div_widget = screen.draw_box(Box::new(Div::new()));
|
||||
```
|
||||
|
||||
#### `Screen::draw_dyn<F: FnMut() -> WidgetLoad + 'static + Send + Sync>(self: &Arc<Self>, element: F) -> Arc<Widget>`
|
||||
Draws a dynamic element (reactive widget) to the screen, built from a closure, and returns its `Arc<Widget>` handle.
|
||||
This method creates a `DynWidget` that can re-evaluate its content based on dependencies.
|
||||
|
||||
**Arguments:**
|
||||
* `element`: A closure that, when called, returns a `WidgetLoad`. This closure defines how the dynamic widget's content is generated.
|
||||
|
||||
**Returns:**
|
||||
An `Arc<Widget>` representing the newly drawn dynamic widget.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
let counter_state = use_state(0);
|
||||
let dynamic_text_widget = screen.draw_dyn({
|
||||
let counter_clone = counter_state.clone();
|
||||
move || {
|
||||
WidgetLoad::new(format!("Count: {}", counter_clone.get()))
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### `Screen::draw_box_dyn(self: &Arc<Self>, element: Box<dyn FnMut() -> WidgetLoad + Send + Sync>) -> Arc<Widget>`
|
||||
Draws a dynamic element from a boxed closure and returns its `Arc<Widget>` handle.
|
||||
Similar to `draw_dyn`, but takes a boxed closure directly.
|
||||
|
||||
**Arguments:**
|
||||
* `element`: A `Box<dyn FnMut() -> WidgetLoad + Send + Sync>`.
|
||||
|
||||
**Returns:**
|
||||
An `Arc<Widget>` representing the newly drawn dynamic widget.
|
||||
|
||||
#### `Screen::draw_widget(self: &Arc<Self>, widget: Arc<Widget>)`
|
||||
Adds an existing `Arc<Widget>` to the screen's managed widget list.
|
||||
This is used internally by `draw` and `draw_dyn` but can be called directly if you're constructing `Arc<Widget>` instances manually.
|
||||
|
||||
**Arguments:**
|
||||
* `widget`: The `Arc<Widget>` to add.
|
||||
|
||||
**Notes:**
|
||||
* The first widget added to the screen via any `draw` method or `draw_widget` will automatically be set as `focused`.
|
||||
|
||||
#### `Screen::extension<E: Extension + Send + Sync + 'static>(self: &Arc<Self>, ext: E)`
|
||||
Registers an extension with the screen.
|
||||
Extensions provide global hooks for lifecycle events, rendering, and event handling. They are initialized once and remain active for the screen's lifetime.
|
||||
|
||||
**Arguments:**
|
||||
* `ext`: An instance of a type that implements the `Extension` trait.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
screen.extension(InputExtension); // Register the input handling extension
|
||||
screen.extension(RelativeFocusExtension::new()); // Register the focus navigation extension
|
||||
```
|
||||
|
||||
#### `Screen::run(self: &Arc<Self>) -> std::io::Result<()>`
|
||||
Starts the main rendering and event loop.
|
||||
This method blocks the current thread and continuously renders the UI, processes events, and updates dynamic widgets until `screen.close()` is called. It also performs initial setup and final cleanup of the terminal.
|
||||
|
||||
**Returns:**
|
||||
A `std::io::Result<()>` indicating success or failure of terminal operations.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let screen = Screen::new();
|
||||
// ... draw widgets, register extensions ...
|
||||
screen.run()?; // Start the loop
|
||||
```
|
||||
|
||||
#### `Screen::render(self: &Arc<Self>, ctx: &Context) -> std::io::Result<()>`
|
||||
Renders all widgets and applies extensions for a single frame.
|
||||
This method is called internally by `Screen::run` and should generally not be called directly by users.
|
||||
|
||||
**Arguments:**
|
||||
* `ctx`: A `Context` object providing access to screen functionalities.
|
||||
|
||||
#### `Screen::close(self: &Arc<Self>)`
|
||||
Closes the main event loop and performs cleanup.
|
||||
This method sets the internal `running` flag to `false`, causing the `Screen::run` loop to terminate. It also calls `on_close` for all registered extensions and restores the terminal cursor and screen state.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
// Inside an event handler or another thread:
|
||||
// screen.close();
|
||||
```
|
||||
|
||||
## Related Components
|
||||
|
||||
### `NoRender` (Component)
|
||||
```rust
|
||||
component!(NoRender);
|
||||
```
|
||||
When attached to a widget, this component indicates that the widget should *not* be directly rendered by the `Screen`'s main loop. This is commonly used for child widgets managed and rendered by their parent "ghost" elements (e.g., `Div`, `FlexRow`, `Paginator`). The parent element takes responsibility for rendering these children within its own `after_render` phase.
|
||||
|
||||
### `NoRenderRoot` (Component)
|
||||
```rust
|
||||
component!(NoRenderRoot);
|
||||
```
|
||||
Similar to `NoRender`, but specifically used to prevent a widget from being rendered by the *root* `Screen` renderer. It is typically injected by parent elements (like `Div`) onto their children, indicating that the child's rendering is handled by the parent's `after_render` and thus shouldn't be processed by the main `Screen` loop. This avoids double-rendering or incorrect layout calculations at the root level.
|
||||
|
||||
## `RenderWrapperEvent` (Event)
|
||||
|
||||
```rust
|
||||
event!(RenderWrapperEvent(*mut RenderScope));
|
||||
```
|
||||
A special internal event type used to pass a mutable reference to a `RenderScope` during rendering.
|
||||
It's primarily used by `Handler<RenderWrapperEvent>` components to allow extensions or custom logic to directly manipulate the `RenderScope` for a widget *before* its `Element::render` method is called.
|
||||
|
||||
**Methods:**
|
||||
* `get_scope(&self) -> &mut RenderScope`: Returns a mutable reference to the underlying `RenderScope`.
|
||||
* **Safety**: The caller must ensure the pointer is valid for the lifetime of the event. This is generally handled internally by OSUI.
|
||||
@@ -0,0 +1,146 @@
|
||||
# `osui::state`
|
||||
|
||||
The `state` module provides OSUI's built-in reactivity system, allowing widgets to automatically update when their associated data changes. This is achieved through the `State<T>` struct and the `DependencyHandler` trait.
|
||||
|
||||
## `DependencyHandler` Trait
|
||||
|
||||
A trait for types that can signal changes, triggering reactive updates in `DynWidget`s. `State<T>` is the primary implementer.
|
||||
|
||||
```rust
|
||||
pub trait DependencyHandler: std::fmt::Debug + Send + Sync {
|
||||
/// Called when a dependent widget registers itself with this dependency.
|
||||
/// This typically increments an internal counter of dependents.
|
||||
fn add(&self);
|
||||
|
||||
/// Returns `true` if the state has changed since the last `check()`.
|
||||
/// If `true`, it typically "consumes" one change notification.
|
||||
fn check(&self) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
## `State<T>`
|
||||
|
||||
`State<T>` is a reactive data container. It wraps a value of type `T` and provides methods to get/set the value and signal changes to dependent widgets.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State<T> {
|
||||
inner: Arc<Mutex<Inner<T>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Inner<T> {
|
||||
value: T,
|
||||
dependencies: usize, // Count of widgets depending on this state
|
||||
changed: usize, // Count of pending changes to be processed by dependents
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Functions
|
||||
|
||||
#### `use_state<T>(v: T) -> State<T>`
|
||||
Creates a new `State<T>` instance, initialized with the provided value `v`.
|
||||
This is the recommended way to create reactive state variables.
|
||||
|
||||
**Arguments:**
|
||||
* `v`: The initial value for the state.
|
||||
|
||||
**Returns:**
|
||||
A new `State<T>` instance.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let my_counter = use_state(0);
|
||||
let my_text = use_state(String::from("Initial Text"));
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `State::get_dl(&self) -> T`
|
||||
Returns a cloned copy of the inner value `T`.
|
||||
This method is recommended for preventing potential deadlocks if you only need to read the value and don't need to hold a lock for extended periods. It does *not* mark the state as changed.
|
||||
|
||||
**Returns:**
|
||||
A `T` (clone of the inner value).
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let my_state = use_state(42);
|
||||
let value = my_state.get_dl(); // value is 42
|
||||
```
|
||||
|
||||
#### `State::get(&self) -> MutexGuard<'_, Inner<T>>`
|
||||
Acquires a `MutexGuard` for the inner `Inner<T>` struct, providing mutable (or immutable) access to the `value` within `Inner<T>`.
|
||||
This method *will* block if another thread or part of the application is currently holding the lock.
|
||||
|
||||
**Returns:**
|
||||
A `MutexGuard` that dereferences to `Inner<T>`. Since `Inner<T>` implements `Deref` and `DerefMut` for `T`, you can often treat `state.get()` as a direct reference to `T`.
|
||||
|
||||
**Example (modifying value and marking as changed):**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let my_state = use_state(0);
|
||||
{
|
||||
let mut inner_guard = my_state.get(); // Acquire lock
|
||||
*inner_guard += 1; // Modify value using DerefMut; automatically marks as changed
|
||||
} // Lock is released here
|
||||
```
|
||||
|
||||
#### `State::set(&self, v: T)`
|
||||
Sets the inner value of the state to `v` and explicitly marks it as changed.
|
||||
This is an alternative to acquiring a `get()` lock and reassigning.
|
||||
|
||||
**Arguments:**
|
||||
* `v`: The new value for the state.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let my_state = use_state("hello".to_string());
|
||||
my_state.set("world".to_string()); // State is updated and marked changed
|
||||
```
|
||||
|
||||
#### `State::update(&self)`
|
||||
Explicitly marks the state as updated without changing its value.
|
||||
This is useful if you've modified the inner `T` through a method that doesn't trigger `DerefMut` on `Inner<T>` (e.g., if `T` is a complex mutable struct and you called a method on it while holding the `MutexGuard` without reassigning the `T` itself).
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
#[derive(Debug, Clone)]
|
||||
struct MyData { count: i32 }
|
||||
let my_data_state = use_state(MyData { count: 0 });
|
||||
{
|
||||
let mut data_guard = my_data_state.get();
|
||||
data_guard.count += 1; // Directly modify field within the guard
|
||||
}
|
||||
my_data_state.update(); // Manually signal that the state has changed
|
||||
```
|
||||
|
||||
### Implementations
|
||||
|
||||
#### `impl<T: Debug + Send + Sync> DependencyHandler for State<T>`
|
||||
`State<T>` implements `DependencyHandler`.
|
||||
* `add()`: Increments `inner.dependencies`.
|
||||
* `check()`: Returns `true` if `inner.changed > 0`, then decrements `inner.changed`. This ensures each dependent consumes one change notification.
|
||||
|
||||
#### `impl<T: Display> Display for State<T>`
|
||||
`State<T>` implements `Display`, allowing it to be formatted directly (e.g., in `format!` strings or debug output), by displaying its inner `value`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let count = use_state(10);
|
||||
println!("Current count: {}", count); // Output: "Current count: 10"
|
||||
```
|
||||
|
||||
#### `impl<T> Deref for Inner<T>`
|
||||
Allows immutable dereferencing of `Inner<T>` to `T`.
|
||||
This means `state.get().value` can be simply `*state.get()`.
|
||||
|
||||
#### `impl<T> DerefMut for Inner<T>`
|
||||
Allows mutable dereferencing of `Inner<T>` to `T`.
|
||||
Crucially, when this is used, the `changed` counter within `Inner<T>` is set to `dependencies`, marking the state as changed for all its dependents.
|
||||
This means `*state.get() = new_value;` will trigger the change notification.
|
||||
@@ -0,0 +1,279 @@
|
||||
# `osui::style`
|
||||
|
||||
The `style` module defines the structures and enums used to control the visual appearance and layout of OSUI widgets. It provides a declarative way to specify positions, dimensions, and backgrounds.
|
||||
|
||||
## `RawTransform`
|
||||
|
||||
`RawTransform` holds the concrete, resolved layout information for a widget. These values are absolute coordinates and sizes in terminal cells, derived after all layout calculations have been performed. Developers typically interact with `Transform` rather than `RawTransform` directly.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RawTransform {
|
||||
pub x: u16, // Absolute X-coordinate (column) of the widget's top-left corner.
|
||||
pub y: u16, // Absolute Y-coordinate (row) of the widget's top-left corner.
|
||||
pub width: u16, // Resolved width of the widget in cells.
|
||||
pub height: u16, // Resolved height of the widget in cells.
|
||||
pub px: u16, // Resolved horizontal padding, derived from Transform.
|
||||
pub py: u16, // Resolved vertical padding, derived from Transform.
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `RawTransform::new() -> RawTransform`
|
||||
Creates a new `RawTransform` instance with all fields set to `0`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::style::RawTransform;
|
||||
let raw_t = RawTransform::new(); // x:0, y:0, width:0, height:0, px:0, py:0
|
||||
```
|
||||
|
||||
## `Position`
|
||||
|
||||
`Position` defines how a widget is placed horizontally or vertically relative to its parent container.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Position {
|
||||
/// Fixed position in cells from the origin (top/left).
|
||||
Const(u16),
|
||||
/// Centered in the parent's available space.
|
||||
Center,
|
||||
/// Aligned to the end (right for x, bottom for y) of the parent.
|
||||
End,
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Position::use_position(&self, size: u16, parent: u16, m: i32, r: &mut u16)`
|
||||
Applies a `Position` rule to determine a final coordinate.
|
||||
This method is used internally by `Transform::use_position` to resolve the `x` or `y` coordinate based on the widget's own size, its parent's available size, and any margin.
|
||||
|
||||
**Arguments:**
|
||||
* `size`: The widget's own resolved dimension (width for `x`, height for `y`).
|
||||
* `parent`: The parent's available dimension (parent width for `x`, parent height for `y`).
|
||||
* `m`: The margin value (mx for `x`, my for `y`).
|
||||
* `r`: A mutable reference to the `u16` where the resolved coordinate should be stored.
|
||||
|
||||
### Implementations
|
||||
|
||||
#### `impl From<u16> for Position`
|
||||
Allows `u16` values to be implicitly converted to `Position::Const(value)`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::style::Position;
|
||||
let pos_x: Position = 10; // Equivalent to Position::Const(10)
|
||||
```
|
||||
|
||||
## `Dimension`
|
||||
|
||||
`Dimension` defines the sizing rule for a widget's width or height.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Dimension {
|
||||
/// Fills the available space from the parent.
|
||||
Full,
|
||||
/// Automatically sized to fit content. The element determines its own size.
|
||||
Content,
|
||||
/// Fixed size in cells.
|
||||
Const(u16),
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Dimension::use_dimension(&self, parent: u16, r: &mut u16)`
|
||||
Applies a `Dimension` rule to determine a final size.
|
||||
This method is used internally by `Transform::use_dimensions` to resolve the `width` or `height`.
|
||||
|
||||
**Arguments:**
|
||||
* `parent`: The parent's available dimension (parent width for `width`, parent height for `height`).
|
||||
* `r`: A mutable reference to the `u16` where the resolved dimension should be stored.
|
||||
|
||||
### Implementations
|
||||
|
||||
#### `impl From<u16> for Dimension`
|
||||
Allows `u16` values to be implicitly converted to `Dimension::Const(value)`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::style::Dimension;
|
||||
let dim_w: Dimension = 50; // Equivalent to Dimension::Const(50)
|
||||
```
|
||||
|
||||
## `Background`
|
||||
|
||||
`Background` defines the visual appearance of a widget's background.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Background {
|
||||
/// Transparent / no background.
|
||||
NoBackground,
|
||||
/// Draws a basic rectangular outline using the given 24-bit RGB color.
|
||||
Outline(u32),
|
||||
/// Draws a rounded rectangular outline using the given 24-bit RGB color.
|
||||
RoundedOutline(u32),
|
||||
/// Fills the background with the specified 24-bit RGB color.
|
||||
Solid(u32),
|
||||
}
|
||||
```
|
||||
|
||||
## `Transform` (Component)
|
||||
|
||||
The `Transform` component is attached to widgets to define their layout rules using `Position` and `Dimension` enums.
|
||||
|
||||
```rust
|
||||
component!(Transform {
|
||||
pub x: Position,
|
||||
pub y: Position,
|
||||
pub mx: i32, // Horizontal margin (offset)
|
||||
pub my: i32, // Vertical margin (offset)
|
||||
pub px: u16, // Horizontal padding (internal spacing)
|
||||
pub py: u16, // Vertical padding (internal spacing)
|
||||
pub width: Dimension,
|
||||
pub height: Dimension,
|
||||
});
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Transform::new() -> Transform`
|
||||
Creates a default `Transform` with top-left alignment (`Const(0)` for x/y), no margins or padding, and content sizing (`Dimension::Content`).
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let default_transform = Transform::new();
|
||||
```
|
||||
|
||||
#### `Transform::center() -> Transform`
|
||||
Shortcut for centering both horizontally and vertically. Sets `x: Position::Center` and `y: Position::Center`, with other fields as default.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@Transform::center();
|
||||
Div { "I am centered" }
|
||||
}
|
||||
```
|
||||
|
||||
#### `Transform::bottom(mut self) -> Self`
|
||||
Fluent method to align the widget to the bottom of its parent. Sets `self.y = Position::End`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@Transform::new().bottom();
|
||||
Div { "I am at the bottom" }
|
||||
}
|
||||
```
|
||||
|
||||
#### `Transform::right(mut self) -> Self`
|
||||
Fluent method to align the widget to the right of its parent. Sets `self.x = Position::End`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@Transform::new().right();
|
||||
Div { "I am at the right" }
|
||||
}
|
||||
```
|
||||
|
||||
#### `Transform::margin(mut self, x: i32, y: i32) -> Self`
|
||||
Fluent method to add margin (offset) from the parent edge. Sets `self.mx = x` and `self.my = y`.
|
||||
|
||||
**Arguments:**
|
||||
* `x`: Horizontal margin. Positive moves right, negative moves left.
|
||||
* `y`: Vertical margin. Positive moves down, negative moves up.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@Transform::new().right().bottom().margin(-2, -1);
|
||||
Div { "2 cells from right, 1 cell from bottom" }
|
||||
}
|
||||
```
|
||||
|
||||
#### `Transform::padding(mut self, x: u16, y: u16) -> Self`
|
||||
Fluent method to add internal spacing (padding) around the content. Sets `self.px = x` and `self.py = y`. This padding is added *inside* the widget's determined `width` and `height`.
|
||||
|
||||
**Arguments:**
|
||||
* `x`: Horizontal padding.
|
||||
* `y`: Vertical padding.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@Transform::new().dimensions(10, 3).padding(1, 0);
|
||||
Div { "Padded text" } // Text will be 1 cell in from left/right edges
|
||||
}
|
||||
```
|
||||
|
||||
#### `Transform::dimensions(mut self, width: u16, height: u16) -> Self`
|
||||
Fluent method to set constant dimensions. Sets `self.width = Dimension::Const(width)` and `self.height = Dimension::Const(height)`.
|
||||
|
||||
**Arguments:**
|
||||
* `width`: Fixed width in cells.
|
||||
* `height`: Fixed height in cells.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
rsx! {
|
||||
@Transform::new().dimensions(20, 5);
|
||||
Div { "A 20x5 cell box" }
|
||||
}
|
||||
```
|
||||
|
||||
#### `Transform::use_dimensions(&self, parent_width: u16, parent_height: u16, raw: &mut RawTransform)`
|
||||
Resolves the `Dimension` rules (`width`, `height`) into absolute values and updates the `raw.width` and `raw.height` fields of the provided `RawTransform`. This is an internal method called during the rendering pipeline.
|
||||
|
||||
#### `Transform::use_position(&self, parent_width: u16, parent_height: u16, raw: &mut RawTransform)`
|
||||
Resolves the `Position` rules (`x`, `y`) into absolute coordinates and updates the `raw.x` and `raw.y` fields of the provided `RawTransform`. This is an internal method called during the rendering pipeline, usually *after* dimensions have been resolved.
|
||||
|
||||
## `Style` (Component)
|
||||
|
||||
The `Style` component defines the background and foreground appearance of a widget.
|
||||
|
||||
```rust
|
||||
component!(Style {
|
||||
pub background: Background,
|
||||
pub foreground: Option<u32>, // 24-bit RGB color, or None for default
|
||||
});
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Style::new() -> Self`
|
||||
Creates a default `Style` with `NoBackground` and `foreground: None`.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let default_style = Style::new();
|
||||
```
|
||||
|
||||
**Usage with `rsx!`:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
|
||||
rsx! {
|
||||
@Transform::new().dimensions(20, 5);
|
||||
@Style { background: Background::Solid(0x550055), foreground: Some(0xFFFFFF) };
|
||||
Div { "Purple background, white text" }
|
||||
|
||||
@Transform::new().dimensions(20, 5).margin(0, 6);
|
||||
@Style { background: Background::Outline(0x00FF00) };
|
||||
Div { "Green outline, default text color" }
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,135 @@
|
||||
# `osui::utils`
|
||||
|
||||
The `utils` module provides a collection of small, standalone utility functions for common terminal manipulations and string operations. These functions are used internally by OSUI but can also be helpful for developers building their own terminal-based applications.
|
||||
|
||||
## Functions
|
||||
|
||||
#### `clear() -> io::Result<()>`
|
||||
Clears the entire terminal screen and moves the cursor to the top-left corner (position 1,1).
|
||||
|
||||
**Returns:**
|
||||
A `std::io::Result<()>` indicating success or if an I/O error occurred.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::utils;
|
||||
utils::clear().expect("Failed to clear terminal");
|
||||
```
|
||||
|
||||
#### `hide_cursor() -> io::Result<()>`
|
||||
Hides the terminal cursor. This is typically called at the start of an OSUI application to provide a cleaner UI experience.
|
||||
|
||||
**Returns:**
|
||||
A `std::io::Result<()>` indicating success or if an I/O error occurred.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::utils;
|
||||
utils::hide_cursor().expect("Failed to hide cursor");
|
||||
```
|
||||
|
||||
#### `show_cursor() -> io::Result<()>`
|
||||
Shows the terminal cursor. This is typically called when the OSUI application exits or needs to return control to the user.
|
||||
|
||||
**Returns:**
|
||||
A `std::io::Result<()>` indicating success or if an I/O error occurred.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::utils;
|
||||
utils::show_cursor().expect("Failed to show cursor");
|
||||
```
|
||||
|
||||
#### `flush() -> io::Result<()>`
|
||||
Flushes the standard output buffer. This ensures that any buffered print commands are immediately written to the terminal. OSUI often calls this after printing to ensure visual updates are instantaneous.
|
||||
|
||||
**Returns:**
|
||||
A `std::io::Result<()>` indicating success or if an I/O error occurred.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::utils;
|
||||
print!("Some text that might be buffered.");
|
||||
utils::flush().expect("Failed to flush stdout");
|
||||
```
|
||||
|
||||
#### `str_size(s: &str) -> (u16, u16)`
|
||||
Calculates the dimensions (width and height) of a string as it would be rendered in a terminal.
|
||||
It accounts for newline characters (`\n`) to determine height and calculates the maximum line width.
|
||||
|
||||
**Arguments:**
|
||||
* `s`: The input string.
|
||||
|
||||
**Returns:**
|
||||
A tuple `(max_width, height)` where:
|
||||
* `max_width`: The maximum width of any line in the string, in characters.
|
||||
* `height`: The number of lines in the string.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::utils;
|
||||
let (width, height) = utils::str_size("Hello\nWorld!");
|
||||
assert_eq!((5, 2), (width, height)); // "World" is 5 chars, 2 lines
|
||||
```
|
||||
|
||||
#### `hex_ansi(hex: u32) -> String`
|
||||
Converts a 24-bit hexadecimal RGB color code (e.g., `0xFF0000` for red) into an ANSI escape sequence for setting the **foreground** color.
|
||||
|
||||
**Arguments:**
|
||||
* `hex`: A `u32` representing the RGB color (0xRRGGBB).
|
||||
|
||||
**Returns:**
|
||||
A `String` containing the ANSI escape code.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::utils;
|
||||
let red_fg = utils::hex_ansi(0xFF0000);
|
||||
println!("{}This text is red\x1b[0m", red_fg); // \x1b[0m resets color
|
||||
```
|
||||
|
||||
#### `hex_ansi_bg(hex: u32) -> String`
|
||||
Converts a 24-bit hexadecimal RGB color code into an ANSI escape sequence for setting the **background** color.
|
||||
|
||||
**Arguments:**
|
||||
* `hex`: A `u32` representing the RGB color (0xRRGGBB).
|
||||
|
||||
**Returns:**
|
||||
A `String` containing the ANSI escape code.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::utils;
|
||||
let blue_bg = utils::hex_ansi_bg(0x0000FF);
|
||||
println!("{}This text has a blue background\x1b[0m", blue_bg);
|
||||
```
|
||||
|
||||
#### `print(x: u16, y: u16, text: &str)`
|
||||
Prints a string to the terminal at specific coordinates. The coordinates are 1-based (row 1, column 1 is top-left). Includes an ANSI reset code `\x1b[0m` after the text.
|
||||
|
||||
**Arguments:**
|
||||
* `x`: The 1-based column.
|
||||
* `y`: The 1-based row.
|
||||
* `text`: The string to print.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::utils;
|
||||
utils::print(5, 3, "Hello at (5,3)");
|
||||
```
|
||||
|
||||
#### `print_liner(x: u16, y: u16, liner: &str, text: &str)`
|
||||
Prints a string to the terminal at specific coordinates, prefixed with an ANSI "liner" (e.g., a color escape sequence). This function is used by OSUI to apply colors to text. The `liner` string is printed *before* the text on each line. Includes an ANSI reset code `\x1b[0m` after the text.
|
||||
|
||||
**Arguments:**
|
||||
* `x`: The 1-based column.
|
||||
* `y`: The 1-based row.
|
||||
* `liner`: The ANSI escape sequence to apply (e.g., color code).
|
||||
* `text`: The string to print.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::utils;
|
||||
let green = utils::hex_ansi(0x00FF00);
|
||||
utils::print_liner(1, 1, &green, "This text is green");
|
||||
```
|
||||
@@ -0,0 +1,265 @@
|
||||
# `osui::widget`
|
||||
|
||||
The `widget` module defines the fundamental traits and types that constitute OSUI's widget system. It provides the building blocks (`Element`, `Component`) and the containers (`Widget`, `StaticWidget`, `DynWidget`) necessary to create and manage the UI tree.
|
||||
|
||||
## `Element` Trait
|
||||
|
||||
The core trait for anything that can be rendered in the UI. Elements are responsible for their own rendering logic and can define hooks for lifecycle events and child rendering.
|
||||
|
||||
```rust
|
||||
pub trait Element: Send + Sync {
|
||||
/// Called to perform rendering for the element. Elements draw their own content here.
|
||||
#[allow(unused)]
|
||||
fn render(
|
||||
&mut self,
|
||||
scope: &mut RenderScope,
|
||||
render_context: &crate::render_scope::RenderContext,
|
||||
) {}
|
||||
|
||||
/// Called after rendering, for follow-up logic or cleanup.
|
||||
/// Container elements typically trigger rendering of their children here.
|
||||
#[allow(unused)]
|
||||
fn after_render(
|
||||
&mut self,
|
||||
scope: &mut RenderScope,
|
||||
render_context: &crate::render_scope::RenderContext,
|
||||
) {}
|
||||
|
||||
/// Called by a parent widget to add a child to this element.
|
||||
#[allow(unused)]
|
||||
fn draw_child(&mut self, element: &Arc<Widget>) {}
|
||||
|
||||
/// Called to handle events for this element.
|
||||
#[allow(unused)]
|
||||
fn event(&mut self, event: &dyn Event) {}
|
||||
|
||||
/// Returns `true` if this element is a "ghost" element.
|
||||
/// Ghost elements primarily serve as layout or logical containers and do not draw themselves.
|
||||
fn is_ghost(&mut self) -> bool { false }
|
||||
|
||||
/// Returns a type-erased reference to this object for downcasting.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Returns a mutable type-erased reference to this object for downcasting.
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
```
|
||||
|
||||
## `Component` Trait
|
||||
|
||||
An optional trait for state or metadata attached to widgets. Components are dynamic extensions to a widget's behavior or data, stored in a `HashMap` by `TypeId`.
|
||||
|
||||
```rust
|
||||
pub trait Component: Send + Sync {
|
||||
/// Returns a type-erased reference to this object for downcasting.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
/// Returns a mutable type-erased reference to this object for downcasting.
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
```
|
||||
|
||||
## `BoxedElement`
|
||||
Type alias for a boxed, trait-object Element.
|
||||
`pub type BoxedElement = Box<dyn Element + Send + Sync>;`
|
||||
|
||||
## `BoxedComponent`
|
||||
Type alias for a boxed, trait-object Component.
|
||||
`pub type BoxedComponent = Box<dyn Component + Send + Sync>;`
|
||||
|
||||
## `WidgetLoad`
|
||||
|
||||
A temporary container for a widget during initial construction. It holds the root `BoxedElement` and any associated `BoxedComponent`s.
|
||||
|
||||
```rust
|
||||
pub struct WidgetLoad(BoxedElement, HashMap<TypeId, BoxedComponent>);
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `WidgetLoad::new<E: Element + 'static>(e: E) -> Self`
|
||||
Creates a new `WidgetLoad` with a given root `Element`.
|
||||
|
||||
**Arguments:**
|
||||
* `e`: The element to wrap.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let wl = WidgetLoad::new(String::from("Hello"));
|
||||
```
|
||||
|
||||
#### `WidgetLoad::component<C: Component + 'static>(mut self, c: C) -> Self`
|
||||
Attaches a component to the `WidgetLoad`. If a component of the same type already exists, it is *not* replaced. Use `set_component` for replacement.
|
||||
|
||||
**Arguments:**
|
||||
* `c`: The component to attach.
|
||||
|
||||
**Returns:**
|
||||
`self`, for chaining.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let wl = WidgetLoad::new(Div::new()).component(Transform::new().dimensions(10, 10));
|
||||
```
|
||||
|
||||
#### `WidgetLoad::set_component<C: Component + 'static>(mut self, c: C) -> Self`
|
||||
Replaces any existing component of the same type with the new component.
|
||||
|
||||
**Arguments:**
|
||||
* `c`: The component to set.
|
||||
|
||||
**Returns:**
|
||||
`self`, for chaining.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let wl = WidgetLoad::new(Div::new())
|
||||
.component(Transform::new().x(5)) // Adds first transform
|
||||
.set_component(Transform::new().x(10)); // Replaces with new transform
|
||||
```
|
||||
|
||||
#### `WidgetLoad::get<C: Component + 'static + Clone>(&self) -> Option<C>`
|
||||
Attempts to retrieve a component of the given type from the `WidgetLoad`. Requires the component to be `Clone`.
|
||||
|
||||
**Returns:**
|
||||
`Some(C)` if found, `None` otherwise.
|
||||
|
||||
**Example:**
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
let wl = WidgetLoad::new(Div::new()).component(Transform::new().x(5));
|
||||
let transform: Option<Transform> = wl.get(); // transform will be Some(Transform { x: Const(5), ... })
|
||||
```
|
||||
|
||||
## `StaticWidget`
|
||||
|
||||
A widget with fixed content and no dynamic behavior. It holds a `Mutex` wrapped `BoxedElement` and a `Mutex` wrapped `HashMap` of components.
|
||||
|
||||
```rust
|
||||
pub struct StaticWidget {
|
||||
element: Mutex<BoxedElement>,
|
||||
components: Mutex<HashMap<TypeId, BoxedComponent>>,
|
||||
focused: Mutex<bool>,
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `StaticWidget::new(e: BoxedElement) -> Self`
|
||||
Creates a new `StaticWidget` from a `BoxedElement`.
|
||||
|
||||
#### `StaticWidget::component<C: Component + 'static>(&self, c: C)`
|
||||
Attaches a component. If a component of the same type exists, it's not replaced.
|
||||
|
||||
#### `StaticWidget::set_component<C: Component + 'static>(&self, c: C)`
|
||||
Replaces any existing component of the same type.
|
||||
|
||||
#### `StaticWidget::get<C: Component + 'static + Clone>(&self) -> Option<C>`
|
||||
Retrieves a cloned component of the specified type.
|
||||
|
||||
## `DynWidget`
|
||||
|
||||
A widget with dynamic content and dependency tracking. It can be rebuilt using a provided `FnMut()` function when dependencies change, enabling reactive updates.
|
||||
|
||||
```rust
|
||||
pub struct DynWidget {
|
||||
element: Mutex<BoxedElement>,
|
||||
components: Mutex<HashMap<TypeId, BoxedComponent>>,
|
||||
load: Mutex<Box<dyn FnMut() -> WidgetLoad + Send + Sync>>, // The closure that rebuilds the widget
|
||||
dependencies: Mutex<Vec<Box<dyn DependencyHandler>>>, // Tracked dependencies
|
||||
injection: Mutex<Option<Box<dyn FnMut(WidgetLoad) -> WidgetLoad + Send + Sync>>>, // For runtime modification
|
||||
focused: Mutex<bool>,
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `DynWidget::new<F: FnMut() -> WidgetLoad + 'static + Send + Sync>(mut e: F) -> Self`
|
||||
Creates a new `DynWidget` from a closure that returns a `WidgetLoad`. The closure is immediately executed once to initialize the widget's content.
|
||||
|
||||
#### `DynWidget::inject<F: FnMut(WidgetLoad) -> WidgetLoad + 'static + Send + Sync>(&self, f: F)`
|
||||
Replaces or modifies the widget's structure on subsequent reloads and initializations. The provided closure takes the `WidgetLoad` generated by the `load` closure and returns a modified `WidgetLoad`. This is useful for dynamically adding components or wrapping elements. It triggers an immediate `refresh()`.
|
||||
|
||||
#### `DynWidget::refresh(&self)`
|
||||
Forces the widget to rebuild its content by re-evaluating the original `load` function. If an `injection` closure is present, it's applied after the `load` function. This method updates the internal `element` and `components`.
|
||||
|
||||
#### `DynWidget::auto_refresh(&self)`
|
||||
Checks if any registered dependencies have changed (via `DependencyHandler::check()`). If so, it calls `self.refresh()`. This method is called automatically by the `Screen` in its rendering loop for `DynWidget`s.
|
||||
|
||||
#### `DynWidget::dependency<D: DependencyHandler + 'static>(&self, d: D)`
|
||||
Adds a dependency to this widget. When `d` signals a change, the widget will `auto_refresh()`. The `add()` method of the `DependencyHandler` is called.
|
||||
|
||||
#### `DynWidget::dependency_box(&self, d: Box<dyn DependencyHandler>)`
|
||||
Adds a boxed dependency. Similar to `dependency` but takes a `Box<dyn DependencyHandler>`.
|
||||
|
||||
#### `DynWidget::component<C: Component + 'static>(&self, c: C)`
|
||||
Attaches a component. If a component of the same type exists, it's not replaced.
|
||||
|
||||
#### `DynWidget::set_component<C: Component + 'static>(&self, c: C)`
|
||||
Replaces any existing component of the same type.
|
||||
|
||||
#### `DynWidget::get<C: Component + 'static + Clone>(&self) -> Option<C>`
|
||||
Retrieves a cloned component of the specified type.
|
||||
|
||||
## `Widget` (Enum)
|
||||
|
||||
A reference-counted wrapper around either a static or dynamic widget. `Arc<Widget>` is the standard way to store and pass around widgets in the UI tree.
|
||||
|
||||
```rust
|
||||
pub enum Widget {
|
||||
Static(StaticWidget),
|
||||
Dynamic(DynWidget),
|
||||
}
|
||||
```
|
||||
|
||||
### Associated Methods
|
||||
|
||||
#### `Widget::new_static(e: BoxedElement) -> Self`
|
||||
Creates a new `Widget::Static` from a `BoxedElement`.
|
||||
|
||||
#### `Widget::new_dyn<F: FnMut() -> WidgetLoad + 'static + Send + Sync>(e: F) -> Self`
|
||||
Creates a new `Widget::Dynamic` from a closure that returns a `WidgetLoad`.
|
||||
|
||||
#### `Widget::is_focused(&self) -> bool`
|
||||
Returns `true` if the widget currently has focus. Focus is managed by extensions like `RelativeFocusExtension`.
|
||||
|
||||
#### `Widget::is_ghost(&self) -> bool`
|
||||
Delegates to the underlying `Element::is_ghost()`. Returns `true` if the element is primarily a layout container.
|
||||
|
||||
#### `Widget::set_focused(&self, f: bool)`
|
||||
Sets the focus status of the widget. This method is usually called by focus management extensions.
|
||||
|
||||
#### `Widget::get_elem(&self) -> MutexGuard<BoxedElement>`
|
||||
Provides a `MutexGuard` for mutable access to the underlying `BoxedElement`. Use with caution to avoid deadlocks.
|
||||
|
||||
#### `Widget::after_render(&self)`
|
||||
Internal: Calls `after_render` hooks for the underlying element and extensions.
|
||||
|
||||
#### `Widget::component<C: Component + 'static>(self: &Arc<Self>, c: C) -> &Arc<Self>`
|
||||
Attaches a component to the widget. If a component of the same type already exists, it is *not* replaced. Returns `self` for chaining.
|
||||
|
||||
#### `Widget::set_component<C: Component + 'static>(self: &Arc<Self>, c: C) -> &Arc<Self>`
|
||||
Replaces any existing component of the same type with the new component. Returns `self` for chaining.
|
||||
|
||||
#### `Widget::get<C: Component + 'static + Clone>(&self) -> Option<C>`
|
||||
Retrieves a cloned component of the specified type from the widget's component map.
|
||||
|
||||
#### `Widget::inject<F: FnMut(WidgetLoad) -> WidgetLoad + 'static + Send + Sync>(self: &Arc<Self>, mut f: F)`
|
||||
Injects a modification closure into a `DynWidget`. For `StaticWidget`s, it applies the modification directly to its components. This allows runtime structural changes to widgets.
|
||||
|
||||
#### `Widget::refresh(self: &Arc<Self>)`
|
||||
Forces a `DynWidget` to rebuild its content. Does nothing for `StaticWidget`s.
|
||||
|
||||
#### `Widget::auto_refresh(self: &Arc<Self>)`
|
||||
Triggers `auto_refresh` on a `DynWidget` (checking and rebuilding if dependencies changed). Does nothing for `StaticWidget`s.
|
||||
|
||||
#### `Widget::dependency<D: DependencyHandler + 'static>(self: &Arc<Self>, d: D) -> &Arc<Self>`
|
||||
Adds a dependency to a `DynWidget`. Does nothing for `StaticWidget`s. Returns `self` for chaining.
|
||||
|
||||
#### `Widget::dependency_box(self: &Arc<Self>, d: Box<dyn DependencyHandler>) -> &Arc<Self>`
|
||||
Adds a boxed dependency to a `DynWidget`. Does nothing for `StaticWidget`s. Returns `self` for chaining.
|
||||
|
||||
#### `Widget::event<E: Event + Clone + 'static>(self: &Arc<Self>, e: &E)`
|
||||
Dispatches an event to the widget. If the widget has a `Handler<E>` component, its callback is invoked. If the widget is focused, its underlying `Element::event` method is also called.
|
||||
Reference in New Issue
Block a user