Combine Components and Canvas
Compose the component first, then store its resulting View in an immutable Canvas item. The component keeps its meaning and presentation while Canvas adds explicit placement or geometry.
use std::io;
use urushi::{ Canvas, CanvasContext, CanvasItem, LineGlyphs, LineNetwork, Position, Size, Table, TextStyle, ThemePreset, View,};
#[derive(Debug, Clone, PartialEq)]struct Dashboard { table: View,}
impl CanvasItem for Dashboard { fn draw(&self, canvas: &mut CanvasContext) { canvas.view( Position::new(1, 1), self.table.clone(), Some(22), Some(5), );
let mut connector = LineNetwork::new( LineGlyphs::ROUNDED, TextStyle::new(), ); connector .horizontal(0, 0..=10) .vertical(0, 0..=2); canvas.line_network(connector); }}
fn main() -> io::Result<()> { let theme = ThemePreset::get("Catppuccin Mocha") .expect("built-in theme") .theme(); let table = Table::text() .headers(["Job", "State"]) .row(["build", "ready"]);
let view = View::canvas( Canvas::new() .extent(Size::new(24, 6)) .item(Dashboard { table: theme.table(&table), }), );
urushi::println_view(&view)}Rendered output
╭──────────
│┌───────┬───────┐
││ Job │ State │
├───────┼───────┤
│ build │ ready │
└───────┴───────┘
Use view rather than flattening the table into strings. That preserves the
table’s layout, styles, wide-character handling, and anchors. The placed View
contains an intrinsically measured component Canvas, but a Canvas View placed
inside another Canvas still requires explicit width and height allocations.
The placed View defaults to Composition::Replace; record background lines
before it when the component should cover them, or choose view_with for
another rule.