Table of Contents

Draw and animate a canvas

Use Canvas for diagrams, gauges, game-specific maps, and effects that standard widgets cannot express clearly. Prefer Text, ProgressBar, Table, and the form controls when they already provide the required structure or interaction.

An animated status canvas beside the TypeScript scene records that draw it

Draw a scene

A scene is an array of shape records. Shapes are painted in array order from back to front, except that canvas text is painted after non-text shapes.

import { createState } from "smudgy:core";
import { Canvas, createWidget } from "smudgy:widgets";
import type { CanvasShape } from "smudgy:widgets";
 
const hud = createState<{ scene: CanvasShape[] }>("manual-canvas");
hud.set({
  scene: [
    {
      kind: "rect",
      x: 4,
      y: 18,
      width: 92,
      height: 12,
      rx: 3,
      fill: {
        gradient: {
          from: [4, 0],
          to: [96, 0],
          stops: [[0, "#7a1f1f"], [1, "#d64541"]],
        },
      },
    },
    {
      kind: "circle",
      id: "warning-pulse",
      cx: 92,
      cy: 24,
      r: 4,
      stroke: { color: "#ffdd55", width: 2 },
      animate: {
        r: { from: 4, to: 10, duration: 800, ease: "in-out", repeat: "infinite" },
      },
    },
    { kind: "text", x: 4, y: 4, text: "Health", size: 11, color: "#ffffff" },
  ],
});
 
createWidget("status-canvas", (
  <Canvas
    width="fill"
    height={160}
    view_box={[0, 0, 100, 50]}
    fit="contain"
    scene={hud.bind("scene")}
  />
));

The available records include rectangles, circles, ellipses, lines, polylines, polygons, SVG-style paths, text, and transformed groups. A fill may be a CSS color or a linear gradient. A stroke may specify color, width, and a dash pattern.

Update without remounting

Bind scene to shared state when the number, order, or fields of the shapes will change. Writing a new array to that state repaints the existing canvas. This differs from Table, whose row membership is fixed until the widget is mounted again.

Do not mutate an old scene array in place and assume that the UI observed it. Assign a new scene value through the state handle.

Give animations stable identities

An animate block declares the destination, duration, delay, easing, and repeat behavior for supported fields. Smudgy calculates the intermediate frames.

Give every animated shape an id unique within the scene. If a later scene value contains the same ID and animation specification, the animation continues from its current position. Changing the specification restarts it. Without an ID, reordering shapes may restart an animation because position becomes its identity.

Set transient: true for a one-shot effect that should disappear when all its animations finish. Remove that record from the next scene value as well; later writes containing the same transient record do not make it visible again.

Size and clip the drawing

Without view_box, scene coordinates are pixels. With view_box, Smudgy maps the declared scene rectangle into the component:

Everything is clipped to the canvas bounds, including strokes and expanding animations. Size the component for the largest state it must display.

Handle pointer input

Add onPointer only when the canvas is interactive. The callback receives down, move, and up events in scene coordinates. Move events are sent while a button is held. A release is reported even when it occurs outside the component.

A display-only canvas without onPointer does not intercept pointer input intended for content behind it. With fit=“contain”, an event in an empty margin can have coordinates outside the view-box rectangle; check bounds before treating it as a hit.

Respond to an invalid scene

Smudgy validates the whole scene before displaying it. Excessive shape counts or nesting, overlong paths or text, and duplicate animation IDs cause the update to be rejected. The previous valid scene remains visible, and the script error identifies the rejected input. Correct the scene rather than repeatedly submitting the same value.

Reference: Canvas API.