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 shape has no local clip.
  • The shape has one solid fill or one solid stroke.
  • The mask uses at most 64 KiB of texels, is no wider than 1024 texels, and is no taller than 256.
  • A non-uniform transform is not applied to a 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, raster density, and mask size. It excludes colour, translation, mirrors, and quarter turns. You can recolour, move, mirror, or quarter-turn an icon without rasterizing its outline again. Scaling reuses the tile while the required density is unchanged. The same rule applies to eligible canvas shapes.

Gradients, local clips, larger masks, arbitrary rotations or shears, non-uniformly scaled strokes, and shapes with both fill and stroke use the general vector rasterizer. An unchanged general-vector item replays from its paint record. Movement re-encodes it because its curves are already in device coordinates. 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