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:
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 itsContext(cx), and it must return aView. TheViewis OSUI's abstraction for "what to draw," essentially a closure that will populate aDrawContextwith drawing instructions later in the rendering pipeline.Send + Sync: Components must beSendandSyncto ensure they can be safely passed between threads, as OSUI leverages concurrency for various operations (e.g.,use_effecthooks).
Most often, you won't implement ComponentImpl manually. Instead, you'll use the #[component] procedural macro:
#[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:
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:
Contextprovides methods (on_event,emit_event) for handling component-specific and application-wide events. Events propagate through theContexttree. - Rendering Result (
View): It holds theViewgenerated by theComponentImpl::callmethod, which is later passed to the rendering engine. - Child Management (
scopes): AContextaggregates child components throughScopes, forming the hierarchical UI tree. - Engine Interaction: It provides access to the
CommandExecutor, allowing components to send commands (likeStop) 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 Contexts:
pub struct Scope {
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
executor: Arc<dyn CommandExecutor>,
}
Relationship between Context and Scope:
- A
Contextcan contain multiple childScopes (stored inContext::scopes). - Each
Scopethen contains aVecof(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@ifand@forwhich might create or destroy entireScopes based on conditions or iterations.
How Scopes are used:
- When you use
rsx!, the macro emits calls tocontext.scope()orcontext.dyn_scope(), which create newScopes. - Within these
Scopes, children are added usingscope.child()orscope.view(). - The
Context::draw_childrenmethod then iterates through theseScopes 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 Contexts (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.