Skip to content
UrushiDocumentation

Canvas

Canvas is Urushi’s finite, renderer-neutral drawing surface. Use it when the result depends on signed cell coordinates, overlap, clipping, sparse cells, or connected line geometry.

Canvas is one kind of View. It can appear inside rows, columns, blocks, CLI output, prompts, or full-screen applications.

The current Canvas is a cell-space surface: each integer coordinate addresses one terminal cell. It does not provide sampled, pixel-like, Braille, or other subcell drawing. Use the documented cell commands and line networks; sampled and subcell drawing are not implemented.

Run the complete Canvas quickstart ↓

Need Start with
Text and boxes in normal horizontal or vertical flow Layout
A list, table, tree, or scrollbar with reusable meaning Components
Content at explicit (x, y) positions Canvas
Overlapping layers or custom cell composition Canvas
Lines, polylines, rectangles, or connected junctions Canvas

These models compose. A component produces a View; a Canvas can place that View at a coordinate; and the resulting Canvas remains an ordinary View.

Canvas separates immutable frame data from drawing after layout:

  1. The surrounding View layout chooses a finite Canvas size.
  2. Canvas calls each owned CanvasItem once, in insertion order.
  3. Each item records commands into a frame-scoped CanvasContext.
  4. Urushi rasterizes, composes, and clips those commands into the settled rectangle.

An item can read CanvasContext::size() while it records commands. It therefore sees the final size and can place content relative to the right or bottom edge. Items do not measure the Canvas.

Position::new(x, y) is relative to the Canvas’s top-left cell:

Canvas coordinate system

The origin is the top-left cell. Positive x moves right and positive y moves down; the highlighted cell is at (3, 2).

Canvas coordinates increase rightward and downwardA cell grid begins at zero comma zero in the top left. Arrows show positive x to the right and positive y downward. Cell three comma two is highlighted.(0, 0)(3, 2)+x+y

Negative coordinates are valid. Commands outside the finite surface are clipped rather than shifting the Canvas or increasing its size.

Items and their commands draw in insertion order. Later commands combine with existing cells using a Composition rule:

  • Replace writes a complete cell;
  • Overlay changes only supplied symbol or style fields; and
  • Custom calls an application function for each affected cell.

view defaults to Replace. Text, paths, line networks, and sparse cells default to Overlay.

Rows, columns, grids, blocks, and viewports measure and place children. Canvas does not infer positions from its items or derive an intrinsic size from them. Application-defined items require a finite parent allocation or fallback extent.

Canvas also does not carry the meaning of a list, table, tree, or scrollbar. Keep that meaning in a Component and place its resulting View into Canvas when it needs explicit coordinates.

This example draws a finite status panel and places text inside it:

Terminal window
cargo new canvas-demo
cd canvas-demo
cargo add urushi

Replace src/main.rs with:

use std::io;
use urushi::{
Canvas, CanvasContext, CanvasItem, Grapheme, Position, Size, TextStyle,
View,
};
#[derive(Debug, Clone, PartialEq)]
struct StatusPanel;
impl CanvasItem for StatusPanel {
fn draw(&self, canvas: &mut CanvasContext) {
canvas.rectangle(
Position::new(0, 0),
9,
3,
Grapheme::new("·"),
TextStyle::new(),
);
canvas.text(
Position::new(2, 1),
"ready",
TextStyle::new().bold(),
);
}
}
fn main() -> io::Result<()> {
let view = View::canvas(
Canvas::new()
.extent(Size::new(9, 3))
.item(StatusPanel),
);
urushi::println_view(&view)
}

Run it with cargo run:

Rendered output

·········
· ready ·
·········

extent supplies a fallback size because standard output does not provide a finite height. CanvasItem::draw receives the settled size and records signed coordinate commands. View::canvas makes the result composable with every other Urushi View.