Layout & Render Lifecycle
Understanding how TUIX Core computes layout and renders components is key to building effective terminal UIs. This page describes the lifecycle from engine.render.draw() through screen output.
Render Cycle Overview
- Screen clear — os.system('cls'/'clear') wipes the terminal
- Layout computation — LayoutEngine._compute_all() reads terminal size and computes pixel positions for all components
- Component rendering — RenderEngine draws borders, label text, and interactive buttons
- Input loop — InputHandler.listen() starts a blocking loop for keyboard navigation
- Re-render on change — Arrow key presses trigger RenderEngine._refresh() which clears and redraws
Layout Computation
The LayoutEngine reads the current terminal size via shutil.get_terminal_size() on every render. It converts proportional modifiers (0.0–1.0) into actual character coordinates. Each component gets computed x, y (width/height in chars), margin_top, margin_left, and corner coordinates.
Custom vs Centered Margins
In 'custom' mode, margins are computed as modifier * terminal_size. In 'centered' mode, margins are auto-calculated as (terminal_size - component_size) // 2, placing the component in the center of the terminal.
Computed Layout Properties
| Property | Description |
|---|---|
| x | Component width in characters (width_modifier * terminal_cols) |
| y | Component height in characters (height_modifier * terminal_rows) |
| margin_top | Top offset in rows (computed based on mode) |
| margin_left | Left offset in columns (computed based on mode) |
| corners.top_left | Tuple (col, row) of top-left corner |
| corners.bottom_right | Tuple (col, row) of bottom-right corner |
Rendering Phase
The RenderEngine draws the component using Unicode box-drawing characters (━, ┃, ┏, ┓, ┗, ┛). For choice components, it: (1) draws the top border, (2) renders the label text with word-wrap and centering via _wrap_and_center(), (3) draws interactive buttons via _draw_buttons(), (4) draws the bottom border.
Text Wrapping
The _wrap_and_center() method splits text into lines that fit within the component's inner width, then centers the text block horizontally within the available space. It respects explicit newlines in the label text.
Button Rendering
The _draw_buttons() method renders choice buttons vertically aligned and horizontally centered. The currently selected button is highlighted using the selected_background and selected_text colors from the active style. Long button text is automatically wrapped based on visual_width() calculations.
Refresh Cycle
When the user presses an arrow key, InputHandler updates selected_row and selected_index, then calls RenderEngine._refresh(). This clears the screen and calls draw() again, which recomputes layout and redraws all components with the updated selection state.
Input Phase
After rendering, _draw_choice() calls engine.input.listen(choices) to start a blocking input loop. The InputHandler polls for keyboard events, updates selected_row and selected_index, and triggers _refresh() on each navigation. Enter exits the loop.
Engine Structure Summary
TuixEngine aggregates all subsystems:
- engine.styles — manages themes and visual properties
- engine.components — manages component creation and updates
- engine.layout — calculates dimensions and positions
- engine.render — draws components to the terminal
- engine.input — handles keyboard events
Components data is shared between ComponentAPI and LayoutEngine via a common objects dictionary. The render engine reads computed layout and cached styles to produce terminal output.
Current Limitations
- Only single choice component rendering is fully implemented
- Multi-component layouts raise NotImplementedError
- No partial/differential updates — full redraw on every interaction
- Input handling only supports arrow keys and Enter