Rendering Pipeline
OSUI's rendering pipeline orchestrates how your declarative UI definitions are translated into actual terminal output. It involves several distinct stages and components working in concert to efficiently draw frames to the screen.
Overview of the Pipeline
The rendering process is driven by the Screen::run()
method, which enters a continuous loop. Within each iteration of this loop (a "frame"), the Screen::render()
method executes the core pipeline:
- Clear Screen: The entire terminal is cleared to prepare for a new frame.
- Initialize Render Scope: A
RenderScope
is created or reset for each top-level widget. This scope provides the drawing context for the current element. - Extension Pre-Render Hook: Registered
Extension
s can inject logic before a widget's main rendering via theirrender_widget
method. - Element Rendering (
Element::render
): The widget's rootElement
is asked to draw its own content into theRenderScope
. This queues drawing commands. - Transform Resolution: The
Transform
component's rules (Position
,Dimension
) are applied to calculate the absoluteRawTransform
(position, size) of the element within theRenderScope
. - Child Rendering (
Element::after_render
for containers): If the currentElement
is a container (likeDiv
,FlexRow
, etc.), itsafter_render
method recursively initiates the rendering pipeline for each of its children. This involves setting up a newRenderScope
context for each child. - Flush to Terminal (
RenderScope::draw
): Once all drawing commands for an element (and its children) are queued within itsRenderScope
, theRenderScope::draw()
method translates these commands into ANSI escape codes and writes them to the terminal. - Extension Post-Render/Cleanup Hook: The
Element::after_render
method is also used for cleanup or final adjustments after drawing children. - Reactivity Check (
Widget::auto_refresh
): ForDynWidget
s, a check is performed to see if anyState
dependencies have changed. If so, the widget is marked for rebuilding for the next frame. - Throttle: The loop pauses briefly to control the frame rate.
Key Components in the Pipeline
1. Screen
The orchestrator. It holds the list of top-level Widget
s, manages extensions, and drives the main rendering loop (run()
, render()
).
- Initializes terminal raw mode.
- Calls
Extension::init
on startup. - Manages the frame rate using
std::thread::sleep
. - Iterates through top-level widgets, initiating their rendering.
- Calls
Extension::on_close
and restores terminal state on shutdown.
(See Reference: Screen API for more details)
2. Widget
(Arc<Widget>
)
The container for an Element
and its Component
s. It's the unit passed around the UI tree.
StaticWidget
: ItsElement
is instantiated once.DynWidget
: ItsElement
can be re-instantiated (rebuilt) if its dependencies change. This rebuild happens before itsrender
method is called in a subsequent frame.- Provides access to its
Element
(get_elem()
) andComponent
s (get()
,set_component()
).
(See Reference: Widget API for more details)
3. Element
(Box<dyn Element>
)
The actual drawable logic. Each Element
implementation defines how it appears.
render(&mut self, scope: &mut RenderScope)
: Puts drawing instructions into theRenderScope
.after_render(&mut self, scope: &mut RenderScope)
: For containers, this is where children are processed and recursively rendered. It's also where the element might determine its finalDimension::Content
size based on children.draw_child(&mut self, element: &Arc<Widget>)
: Called byrsx!
to establish parent-child relationships. Children processed by a parent are markedNoRenderRoot
to prevent theScreen
from rendering them independently.
(See Reference: Widget API - Element Trait and Guides: Custom Elements for more details)
4. RenderScope
The drawing context for a single element. It's a mutable structure that holds:
-
The element's current
RawTransform
(absolute position and size). -
The element's current
Style
. -
The
parent_width
andparent_height
(critical for relative layout calculations). -
A
render_stack
of primitive drawing commands (text, rectangles). -
set_transform()
: Uses aTransform
component to calculate theRawTransform
. -
set_style()
: Applies aStyle
component. -
draw_text()
,draw_rect()
, etc.: Queue drawing commands. These also update the scope's dimensions forDimension::Content
sizing. -
draw()
: Flushes all queued commands and the background style to the terminal using ANSI escape codes. -
clear()
: Resets the scope for the next element. -
set_parent_size()
: Crucial for container elements to establish the bounding box for their children.
(See Reference: RenderScope API for more details)
5. Transform
and Style
Components
These components, attached to a Widget
, provide the declarative rules for layout and appearance.
Transform
: ContainsPosition
andDimension
rules, plusmargin
andpadding
. These are resolved intoRawTransform
byRenderScope
.Style
: ContainsBackground
andforeground
color. Applied toRenderScope
.
(See Reference: Style API for more details)
6. Extension
s
Extensions are hooks into the pipeline.
Extension::render_widget(scope, widget)
: Called for each top-level widget before itsElement::render
. Allows extensions to inspect or modify theRenderScope
or widget before rendering.
(See Reference: Extensions API for more details)
Flow Diagram (Conceptual)
graph TD
A[Screen::run() Loop] --> B{Frame};
B --> C[utils::clear()];
C --> D{For each top-level Arc<Widget> w};
D --> E{Create/Clear RenderScope (rs)};
E --> F{Set rs.parent_size};
F --> G{Get w's Transform & Style Components};
G --> H{rs.set_transform(w.transform_comp)};
H --> I{rs.set_style(w.style_comp)};
I --> J{For each Extension ext};
J --> K[ext.render_widget(rs, w)];
K --> L[w.get_elem().render(rs)];
L --> M{w.get_elem().after_render(rs)};
M --> N{rs.draw()};
N --> O{w.auto_refresh() if DynWidget};
O --> P{Check for screen.close()};
P --> Q[Wait 28ms];
Q --> B;
subgraph Element/Container Flow in M
M_start[Element::after_render(scope)] --> M1{For each child_widget};
M1 --> M2[child_scope = scope.clone()];
M2 --> M3[child_scope.clear()];
M3 --> M4[child_scope.set_parent_size(self_resolved_width, self_resolved_height)];
M4 --> M5[child_scope.set_transform(child_transform_comp)];
M5 --> M6[child_scope.set_style(child_style_comp)];
M6 --> M7[child_widget.get_elem().render(child_scope)];
M7 --> M8[child_widget.get_elem().after_render(child_scope)];
M8 --> M9[child_scope.draw()];
M9 --> M10{Next child / Return};
end
Understanding this pipeline is crucial for debugging rendering issues, optimizing performance, and building advanced custom elements or extensions that interact deeply with OSUI's drawing logic.