zgui

Vector and canvas

Draw declarative vector documents or mutate an imperative retained shape scene.

vector and canvas both produce paths for the vector renderer. Use vector for a drawing that the view declares. Use canvas for a drawing that Rust code generates or mutates.

Declarative drawing with vector

A vector element accepts path notation or a complete SVG document:

use zgui::prelude::*;

const TICK: &str = "M20.5 7.1 L18.9 5.5 L9.6 14.8 L5.1 10.3 L3.5 11.9 L9.6 18.0 Z";

view! {
    vector(class = "icon", prop:d = TICK, prop:viewBox = "0 0 24 24")
}
.icon { width: 24px; height: 24px; color: #6ea8ff; }
.icon:hover { color: #ffb648; }

The content properties are:

PropertyValue
dOne path per line
viewBoxMinimum x, minimum y, width, and height
svgA complete SVG source string

When a view box exists, zgui scales the drawing uniformly to fit the content box and centers the unused axis. Without a view box, coordinates are CSS pixels from the content box's top-left corner. The drawing does not give the element an intrinsic size. Set its width and height in CSS.

currentColor resolves to the vector element's computed color. Three inherited custom properties can override paint:

PropertyDefault
--zgui-fillThe computed color
--zgui-strokeNo stroke
--zgui-stroke-width1 CSS pixel

Use these custom properties. CSS declarations such as fill: red do not reach the vector pipeline.

The SVG reader supports paths, shapes, fills, strokes, fill and clip rules, transforms, opacity, clip paths, gradients, use, symbols, and markers. It does not render SVG text, embedded images, filters, masks, patterns, or blend modes. A document that cannot be parsed draws nothing. If both svg and d exist, svg wins.

For typed geometry, use zgui::elements::vector() with paths, document, view_box, fill, stroke, stroke_width, or hit_shape. paths, document, view_box, and hit_shape take plain values. Use a reactive prop:d or prop:svg closure when the source must change.

Imperative drawing with canvas

A canvas holds a retained Vec<Shape>. Shapes use the same path, fill, stroke, gradient, and clip types as vector documents. They paint in list order.

The closure form is the shortest option for a chart or generated drawing:

use zgui::canvas::{Brush, ShapeBuilder};
use zgui::canvas::zgui_color::Color;
use zgui::elements::{canvas, kurbo};
use zgui::elements::kurbo::Shape as _;
use zgui::prelude::*;

let progress = RwSignal::new(0.6_f64);

let view = canvas().class("meter").draw(move |cx| {
    let width = f64::from(cx.size.width.0) * progress.get();
    let height = f64::from(cx.size.height.0);
    let path = kurbo::Rect::new(0.0, 0.0, width, height).to_path(0.1);
    cx.scene.push(
        ShapeBuilder::new(path)
            .fill(Brush::Solid(Color::srgb(0.2, 0.5, 0.9, 1.0)))
            .build(),
    );
});

The closure reruns when a signal that it reads changes and when the content-box size changes. Each run starts with an empty scene. DrawCx::size is in CSS pixels. DrawCx::scale gives device pixels per CSS pixel. The first run has a zero size because layout has not run. A size-dependent drawing settles after the first layout frame.

The retained form is useful when event handlers update the scene:

let drawing = CanvasHandle::new();
let view = zgui::elements::canvas().scene(&drawing);

drawing.draw(|scene| {
    scene.clear();
    scene.push(shape);
});

CanvasScene provides clear, push, replace, and shapes. Always mutate through CanvasHandle::draw on the UI thread. It increments the scene revision and wakes each bound element.

For a background producer, clone the thread-safe SceneHandle from CanvasHandle::scene() and edit it off the UI thread. That edit increments the revision but does not wake the UI loop. Pair it with a signal update or a UI-side CanvasHandle::draw call.

A canvas without a view box uses CSS-pixel coordinates. .view_box(x, y, width, height) uses the same uniform fit as vector. The user-agent sheet gives an unstyled canvas a 300 by 150 CSS-pixel box; application CSS can override it.

Reuse an Arc<BezPath> with ShapeBuilder::shared when geometry stays the same. Stable allocation identity lets the rasterizer reuse its encoded geometry.

Rendering and caching

Small solid shapes use a fast path when all these conditions are true:

  • The device-pixel bounds are at most 96 by 96.
  • The transform is translation only.
  • The shape has no local clip.
  • The shape has one solid fill or one solid stroke.

zgui rasterizes the shape to a monochrome coverage mask and stores it in the shared atlas. The cache key includes the geometry, fill or stroke style, scale, and mask size. It excludes the colour and whole-pixel translation. You can recolour or move an icon without rasterizing its outline again. The same rule applies to eligible canvas shapes.

Gradients, local clips, larger shapes, non-translation transforms, and shapes with both fill and stroke use the general vector rasterizer. The renderer packs disjoint pass regions into compact scratch space. GPU scratch size therefore follows the drawn regions, not the empty distance between them.

On this page