Skip to main content
Version: 0.1.1

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 Elements and Components, all encapsulated within Widgets.

Elements: The Renderable Unit

The Element trait is the fundamental building block for anything that can be rendered on the screen.

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.

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 Components. It's the primary way to interact with UI nodes in the OSUI tree.

pub enum Widget {
Static(StaticWidget),
Dynamic(DynWidget),
}

Widget provides a unified interface to access its underlying Element and Components, 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 BoxedComponents.

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 Elements, 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 Components. It tracks dependencies (via DependencyHandler) and automatically refreshes 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

DynWidgets 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 Components, leading to a re-render. This mechanism is explained in detail in State Management.