Files
docs/versioned_docs/version-0.2.0/concepts/01-the-component-model.md
T
2026-01-31 20:52:02 +01:00

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:

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:

#[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: 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 Scopes, 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 Contexts:

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 Scopes (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 Scopes based on conditions or iterations.

How Scopes are used:

  • When you use rsx!, the macro emits calls to context.scope() or context.dyn_scope(), which create new Scopes.
  • Within these Scopes, children are added using scope.child() or scope.view().
  • The Context::draw_children method then iterates through these Scopes 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.