View
A View is Urushi’s central presentation value: a renderer-neutral description
of content and composition before any terminal surface draws it. Every View
resolves to a rectangle of styled terminal cells. The same View can become
static CLI output, a prompt frame, an Urushi TUI frame, cells in an existing
Ratatui application, or a fallback region beneath terminal graphics.
Styles decorate Views. Layout combines and sizes them. Components and themes produce them. Canvas is a free-positioned View node. Output surfaces decide when and where the resolved result is drawn. This shared value is what lets the rest of Urushi compose without sharing one event loop or terminal lifecycle.
See the shared presentation value
Section titled “See the shared presentation value”Every card above is still a View. The difference is how the value is built,
not which renderer or application surface can use it.
Run the complete View quickstart ↓
Choose a View node
Section titled “Choose a View node”| Need | Use |
|---|---|
| One plain or multi-style text flow | text or styled_text |
| Box model, border, title, dimensions, alignment, or overflow | block or titled_block |
| Horizontal arrangement | row |
| Vertical arrangement | column |
| Shared column widths | grid |
| Finite window onto larger content | viewport |
| Report a resolved region or cursor origin | anchor_block or anchor |
| Explicit coordinates, overlap, or line geometry | Canvas |
| Reusable list, table, tree, or scrollbar meaning | Components |
How View resolution works
Section titled “How View resolution works”A View is a value, not an event loop or a terminal session. Text, blocks, rows, columns, grids, viewports, and Canvas surfaces all resolve to rectangles. That common shape lets a bordered block sit beside a table or Canvas just as text can.
Build the complete tree first, then resolve it once under an Available area:
Resolution applies one available area to the complete immutable View tree.
- dataView treeContent and composition
- withdataAvailable areaFinite or unbounded axes
- resolveprocessLayout resolutionMeasure, allocate, place, and collect anchors
- resultdataResolvedViewA rectangle of styled cells
Resolution performs terminal-cell measurement, wrapping, box sizing,
allocation, placement, viewport projection, and anchor collection. A renderer
then turns the ResolvedView into ANSI output, a prompt frame, an Urushi TUI
screen, or Ratatui cells. print_view and println_view perform resolution and
rendering for standard streams.
Available::NONE means unbounded, not zero. Nodes with intrinsic content can
resolve without an allocation. Area-dependent claims such as Length::Fill,
projected viewport axes, and a Canvas without a fallback extent require a finite
allocation.
A viewport does not scroll itself, an anchor does not own focus, and a View does not read events or retain application state. The application changes its state and produces a new View; the selected output surface owns its lifecycle.
Quickstart
Section titled “Quickstart”This example stacks a heading above two bordered status blocks:
cargo new view-democd view-democargo add urushiReplace src/main.rs with:
use std::io;
use urushi::{ Align, BlockStyle, Border, Color, TextStyle, VerticalAlign, View,};
fn status(name: &str, value: &str, color: Color) -> View { View::titled_block( BlockStyle::new().border(Border::ROUNDED).padding((0, 1)), name, View::text(value, TextStyle::new().foreground(color).bold()), )}
fn main() -> io::Result<()> { let statuses = View::row( VerticalAlign::Top, [ status("Build", "ready", Color::GREEN), status("Tests", "passing", Color::CYAN), ], );
let view = View::column( Align::Left, [ View::text("Workspace", TextStyle::new().bold()), statuses, ], );
urushi::println_view(&view)}Run it with cargo run:
Rendered output
Workspace
╭ Build ╮╭ Tests ──╮
│ ready ││ passing │
╰───────╯╰─────────╯
row arranges children horizontally; column arranges them vertically. Each
block measures its title, padding, border, and child before the parent row
chooses its height.
Retain repeated resolution across frames
Section titled “Retain repeated resolution across frames”Use the free resolve function for a one-shot View or when the caller does not
retain a rendering object. Use one Resolver when the same host repeatedly
resolves immutable View snapshots and wants unchanged subtree output to be
reused between frames. Both paths produce the same observable ResolvedView.
This complete example resolves two frames. Only the viewport origin changes, so the retained child can be reprojected instead of being assembled again:
use std::error::Error;
use urushi::{ Available, Projection, ProjectionBoundary, RenderSettings, Resolver, TextStyle, View, Viewport, render,};
fn frame(origin: i64) -> View { View::viewport( Viewport::horizontal(Projection::new( origin, ProjectionBoundary::Preserve, )), View::text("ABCDEFGH", TextStyle::new()), )}
fn main() -> Result<(), Box<dyn Error>> { let mut resolver = Resolver::new(); let area = Available::size(4, 1);
let first = resolver.resolve(&frame(0), area)?; let second = resolver.resolve(&frame(2), area)?;
assert_eq!(render(&first, &RenderSettings::default()), "ABCD"); assert_eq!(render(&second, &RenderSettings::default()), "CDEF");
resolver.clear(); let rebuilt = resolver.resolve(&frame(2), area)?; assert_eq!(rebuilt, second); Ok(())}frame 1, origin 0: ABCDframe 2, origin 2: CDEFafter clear: CDEFclear drops every retained evaluation artifact; it does not change layout
semantics. Call it when the host deliberately wants a cold rebuild. Ordinary
View or available-area changes already invalidate the affected artifacts, so
application code does not clear the Resolver after every state update.
The Resolver retains reconciliation inputs and materialized View output only. It does not own application state, clocks, event handling, redraw scheduling, terminal capabilities, graphics protocol state, or the output surface. The host updates its state, builds the next immutable View, decides when to draw, and keeps one Resolver with that drawing lifecycle.