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
RenderScopeis created or reset for each top-level widget. This scope provides the drawing context for the current element. - Extension Pre-Render Hook: Registered
Extensions can inject logic before a widget's main rendering via theirrender_widgetmethod. - Element Rendering (
Element::render): The widget's rootElementis asked to draw its own content into theRenderScope. This queues drawing commands. - Transform Resolution: The
Transformcomponent's rules (Position,Dimension) are applied to calculate the absoluteRawTransform(position, size) of the element within theRenderScope. - Child Rendering (
Element::after_renderfor containers): If the currentElementis a container (likeDiv,FlexRow, etc.), itsafter_rendermethod recursively initiates the rendering pipeline for each of its children. This involves setting up a newRenderScopecontext 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_rendermethod is also used for cleanup or final adjustments after drawing children. - Reactivity Check (
Widget::auto_refresh): ForDynWidgets, a check is performed to see if anyStatedependencies 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 Widgets, manages extensions, and drives the main rendering loop (run(), render()).
- Initializes terminal raw mode.
- Calls
Extension::initon startup. - Manages the frame rate using
std::thread::sleep. - Iterates through top-level widgets, initiating their rendering.
- Calls
Extension::on_closeand restores terminal state on shutdown.
(See Reference: Screen API for more details)
2. Widget (Arc<Widget>)
The container for an Element and its Components. It's the unit passed around the UI tree.
StaticWidget: ItsElementis instantiated once.DynWidget: ItsElementcan be re-instantiated (rebuilt) if its dependencies change. This rebuild happens before itsrendermethod is called in a subsequent frame.- Provides access to its
Element(get_elem()) andComponents (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::Contentsize based on children.draw_child(&mut self, element: &Arc<Widget>): Called byrsx!to establish parent-child relationships. Children processed by a parent are markedNoRenderRootto prevent theScreenfrom 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_widthandparent_height(critical for relative layout calculations). -
A
render_stackof primitive drawing commands (text, rectangles). -
set_transform(): Uses aTransformcomponent to calculate theRawTransform. -
set_style(): Applies aStylecomponent. -
draw_text(),draw_rect(), etc.: Queue drawing commands. These also update the scope's dimensions forDimension::Contentsizing. -
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: ContainsPositionandDimensionrules, plusmarginandpadding. These are resolved intoRawTransformbyRenderScope.Style: ContainsBackgroundandforegroundcolor. Applied toRenderScope.
(See Reference: Style API for more details)
6. Extensions
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 theRenderScopeor 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.