RSX Internals
The rsx! macro is a cornerstone of OSUI, providing a declarative, JSX-like syntax for building UI trees. While it appears to directly construct widgets, it actually expands into an intermediate representation handled by the frontend module. This allows for powerful features like static/dynamic differentiation and dependency tracking.
The Problem: Expressing UI Trees in Rust
Directly constructing OSUI widgets in Rust code can be verbose:
use osui::prelude::*;
let my_div = Arc::new(Widget::Dynamic(DynWidget::new(|| {
WidgetLoad::new(Div::new())
.component(Transform::new().center())
.set_component(Style { background: Background::Solid(0x333333), foreground: Some(0xFFFFFF) })
})));
// How to add children? How to declare dependencies? How to make it static?
The rsx! macro aims to solve this by providing a compact, expressive syntax.
frontend Module: The RSX Intermediate Representation
The frontend module defines the data structures that the rsx! macro generates. It acts as a bridge between the high-level declarative syntax and the low-level Widget creation.
Rsx Struct
Rsx is essentially a wrapper around a Vec<RsxElement>. It represents a collection of UI elements, typically a list of siblings or children of a parent element.
pub struct Rsx(pub Vec<RsxElement>);
Rsx::draw(): The entry point to take anRsxtree and render it onto theScreen.Rsx::draw_parent(): Used recursively to draw children, passing theArc<Widget>of their parent.Rsx::create_element(): Adds a dynamic element definition to theRsxvector.Rsx::create_element_static(): Adds a static element definition to theRsxvector.Rsx::expand(): Allows merging anotherRsxtree into the current one.
RsxElement Enum
RsxElement is the core enum that represents a single node in the intermediate UI tree. It can be either a static or a dynamic element.
pub enum RsxElement {
/// A static widget with children.
Element(StaticWidget, Rsx),
/// A dynamically generated widget (e.g., with state) with associated dependencies and children.
DynElement(
Box<dyn FnMut() -> WidgetLoad + Send + Sync>, // The closure that builds the WidgetLoad
Vec<Box<dyn DependencyHandler>>, // Dependencies for dynamic updates
Rsx, // Its children
),
}
RsxElement::Element: Corresponds to elements declared withstaticinrsx!. It directly holds aStaticWidgetinstance and itsRsxchildren.RsxElement::DynElement: Corresponds to elements withoutstaticor with%dependencyinrsx!. It holds:- A
Box<dyn FnMut() -> WidgetLoad>: This is the actual code (a closure) that will be executed later to create theElementand its initialComponents. This closure captures any necessary environment variables (likeStateclones). Vec<Box<dyn DependencyHandler>>: A list of the dependencies declared with%. WhenDynWidget::auto_refresh()is called, these handlers are checked to decide if the widget needs rebuilding.Rsx: The children of this dynamic element.
- A
How rsx! Expands
The rsx! macro is implemented using multiple macro_rules! rules that parse different patterns (text, element types, properties, components, dependencies, children). It uses an internal recursive macro, rsx_inner!, to build the Rsx structure.
Let's look at a simplified conceptual expansion:
// Example rsx! input:
rsx! {
@Transform::new().center();
%my_state
Div {
"Hello: {my_state}"
static Input { }
}
}
This conceptually expands to something like this (highly simplified, actual expansion is more complex with error handling and exact types):
// Conceptual Expansion of rsx!
{
let mut r = osui::frontend::Rsx(Vec::new());
// Processing the 'Div' element
r.create_element(
// The closure to build the WidgetLoad for Div
{
let my_state = my_state.clone(); // Clone the Arc<State<T>> for capture
move || {
osui::widget::WidgetLoad::new(osui::elements::Div::new())
// Attach Transform component
.component(osui::style::Transform::new().center())
}
},
// Dependencies list
vec![
Box::new(my_state.clone()) as Box<dyn osui::state::DependencyHandler>
],
// Children of the Div
osui::frontend::Rsx(vec![
// Processing "Hello: {my_state}" text
osui::frontend::RsxElement::DynElement(
{
let my_state = my_state.clone();
move || {
osui::widget::WidgetLoad::new(
format!("Hello: {}", my_state) // Format string dynamically
)
}
},
vec![
Box::new(my_state.clone()) as Box<dyn osui::state::DependencyHandler>
],
osui::frontend::Rsx(Vec::new()) // No children for text
),
// Processing `static Input { }`
osui::frontend::RsxElement::Element(
osui::widget::StaticWidget::new(Box::new(osui::elements::Input::new())),
osui::frontend::Rsx(Vec::new()) // No children for Input here
),
])
);
r // The final Rsx object
}
How the Rsx Tree is Rendered
When you call .draw(&screen) on an Rsx object:
Rsx::draw_parent()is called, which iterates through itsVec<RsxElement>.- For each
RsxElement:- If it's
RsxElement::Element(static_widget, children_rsx):- An
Arc<Widget::Static(static_widget)>is created. - It's added to the screen's top-level widgets via
screen.draw_widget(). children_rsx.draw_parent(screen, Some(parent_widget_arc))is recursively called.
- An
- If it's
RsxElement::DynElement(build_fn, dependencies, children_rsx):screen.draw_box_dyn(build_fn)is called. This immediately executesbuild_fnonce to get the initialWidgetLoad, creates anArc<Widget::Dynamic(...)>, and adds it to the screen.- All
dependenciesare then registered with the newly createdDynWidgetusingwidget.dependency_box(). children_rsx.draw_parent(screen, Some(parent_widget_arc))is recursively called.
- If it's
This two-stage process (macro expansion to Rsx then Rsx::draw to Widgets) allows OSUI to efficiently manage static vs. dynamic content and set up the reactivity system.
Performance Implications
- Static vs. Dynamic: The
statickeyword inrsx!is critical for performance.staticelements expand intoRsxElement::Element, which directly holds aStaticWidget. ThisStaticWidgetis instantiated only once. Dynamic elements (RsxElement::DynElement) hold a closure that is re-executed every time the widget needs to refresh. Usestaticwhenever a UI part doesn't need to dynamically change itsElementtype or internalElementstate based on externalStates. - Closure Captures: Be mindful of what is captured by closures in dynamic elements. Cloning
Arcs (State<T>,Arc<Screen>, etc.) is efficient. Capturing large structs by value can increase memory usage on each re-render.
Understanding the internal representation generated by rsx! helps in optimizing your UI structure and debugging complex reactive behaviors.