108 lines
5.6 KiB
Markdown
108 lines
5.6 KiB
Markdown
---
|
|
sidebar_position: 1
|
|
title: The Component Model
|
|
---
|
|
|
|
# The Component Model
|
|
|
|
At the core of OSUI's design is a robust component model, defining how UI elements are created, composed, and managed. This model provides structure, promotes reusability, and facilitates a clear separation of concerns within your TUI application.
|
|
|
|
## `ComponentImpl`: The Building Block
|
|
|
|
The `ComponentImpl` trait is the most fundamental concept for any renderable unit in OSUI:
|
|
|
|
```rust
|
|
pub trait ComponentImpl: Send + Sync {
|
|
/// Renders the component within the given context, returning a View
|
|
fn call(&self, cx: &Arc<Context>) -> View;
|
|
}
|
|
```
|
|
|
|
* **`fn call(&self, cx: &Arc<Context>) -> View`**: This method is where a component's rendering logic resides. It takes a reference to the component instance itself and its `Context` (`cx`), and it must return a `View`. The `View` is OSUI's abstraction for "what to draw," essentially a closure that will populate a `DrawContext` with drawing instructions later in the rendering pipeline.
|
|
* **`Send + Sync`**: Components must be `Send` and `Sync` to ensure they can be safely passed between threads, as OSUI leverages concurrency for various operations (e.g., `use_effect` hooks).
|
|
|
|
Most often, you won't implement `ComponentImpl` manually. Instead, you'll use the `#[component]` procedural macro:
|
|
|
|
```rust
|
|
#[component]
|
|
fn MyComponent(cx: &Arc<Context>, some_prop: &String) -> View {
|
|
// Component logic and RSX here
|
|
rsx! {
|
|
format!("Prop: {}", some_prop)
|
|
}.view(&cx)
|
|
}
|
|
```
|
|
|
|
The `#[component]` macro automatically generates a struct for `MyComponent` (with `some_prop: String` as a field) and implements `ComponentImpl` for it, delegating the `call` method to your function's body.
|
|
|
|
## `Context`: The Component's Identity and State
|
|
|
|
Every active instance of a component in the UI tree has its own `Context` (`osui::component::context::Context`). The `Context` is the component's runtime identity and central hub for managing its internal state and interactions:
|
|
|
|
```rust
|
|
pub struct Context {
|
|
component: AccessCell<Component>,
|
|
view: AccessCell<View>,
|
|
event_handlers: AccessCell<HashMap<TypeId, Vec<EventHandler>>>,
|
|
pub(crate) scopes: Mutex<Vec<Arc<Scope>>>,
|
|
executor: Arc<dyn CommandExecutor>,
|
|
}
|
|
```
|
|
|
|
**Why `Context` is crucial**:
|
|
|
|
* **State Management**: It's the entry point for all state hooks (`use_state`, `use_effect`, etc.), ensuring that each component instance manages its own isolated, reactive state.
|
|
* **Event Handling**: `Context` provides methods (`on_event`, `emit_event`) for handling component-specific and application-wide events. Events propagate through the `Context` tree.
|
|
* **Rendering Result (`View`)**: It holds the `View` generated by the `ComponentImpl::call` method, which is later passed to the rendering engine.
|
|
* **Child Management (`scopes`)**: A `Context` aggregates child components through `Scope`s, forming the hierarchical UI tree.
|
|
* **Engine Interaction**: It provides access to the `CommandExecutor`, allowing components to send commands (like `Stop`) to the underlying engine.
|
|
|
|
When a component is instantiated (e.g., `MyComponent { ... }` in `rsx!`), a new `Context` is created for it. This `Context` lives as long as the component is part of the active UI tree.
|
|
|
|
## `Scope`: Organizing Children
|
|
|
|
While `Context` represents a single component instance, `Scope` (`osui::component::scope::Scope`) is responsible for grouping and managing collections of child `Context`s:
|
|
|
|
```rust
|
|
pub struct Scope {
|
|
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
|
|
executor: Arc<dyn CommandExecutor>,
|
|
}
|
|
```
|
|
|
|
**Relationship between `Context` and `Scope`**:
|
|
|
|
* A `Context` can contain multiple child `Scope`s (stored in `Context::scopes`).
|
|
* Each `Scope` then contains a `Vec` of `(Arc<Context>, Option<ViewWrapper>)`, representing the actual child components (and optional view modifiers) within that specific scope.
|
|
* This distinction allows for flexibility in how children are managed, particularly for dynamic `rsx!` constructs like `@if` and `@for` which might create or destroy entire `Scope`s based on conditions or iterations.
|
|
|
|
**How `Scope`s are used**:
|
|
|
|
* When you use `rsx!`, the macro emits calls to `context.scope()` or `context.dyn_scope()`, which create new `Scope`s.
|
|
* Within these `Scope`s, children are added using `scope.child()` or `scope.view()`.
|
|
* The `Context::draw_children` method then iterates through these `Scope`s and their children to orchestrate their rendering.
|
|
|
|
## The Component Tree
|
|
|
|
Together, `ComponentImpl`, `Context`, and `Scope` form a hierarchical component tree:
|
|
|
|
```
|
|
App Component (Context)
|
|
└── App Scope (from App's rsx!)
|
|
├── Child Component A (Context)
|
|
│ └── Child A Scope (from A's rsx!)
|
|
│ └── Grandchild Component X (Context)
|
|
├── Child Component B (Context)
|
|
│ └── Child B Scope
|
|
│ ├── Grandchild Component Y (Context)
|
|
│ └── Grandchild Component Z (Context)
|
|
└── Dynamic Scope (e.g., from an `@if` block)
|
|
└── (Conditionally rendered children)
|
|
```
|
|
|
|
This tree structure is fundamental to OSUI's rendering and event propagation. Events emitted by a child `Context` can traverse up the tree to parent `Context`s (if they listen for them) and always propagate down to all descendants.
|
|
|
|
The component model provides a clear, organized, and powerful way to structure your TUI applications, promoting maintainability and scalability through modularity and a well-defined lifecycle for each UI element.
|
|
|
|
**Next:** Understand how `State<T>` and `HookDependency` enable efficient UI updates in [Reactive State Flow](./02-reactive-state-flow.md).
|