Perch
Lua APIperch.g — the draw API

perch.g — the draw API

The immediate-mode canvas recorded inside on_draw painters — every op, argument by argument.

2 min readUpdated Jul 21, 2026

host.g records drawing ops inside a draw node’s on_draw painter. Outside a painter the ops are inert; inside one, up to 256 ops are recorded per paint and replayed onto a crisp, DPI-aware canvas.

-- displays.lua
{ type = "draw", w = 64, h = 64, bind = "reading", on_draw = M.draw_gauge }

-- logic.lua
function M.draw_gauge(host, d, w, h)   -- d = the full payload; w,h = design px
  local g = host.g
  g.circle(w/2, h/2, 28, { fill = "raised", stroke = "rim" })
  g.arc(w/2, h/2, 24, -2.36, -2.36 + 4.72 * d.reading.frac,
        { stroke = "accent", width = 4, cap = "round" })
  g.text(d.reading.label, w/2 - 10, h - 6, { size = 10, fill = "half" })
end

The painter runs only when the bound field changes — never per frame. That’s the whole idle-cost story: an unchanging gauge costs zero.

The ops

Every op takes coordinates in design px, with an optional style table last.

g.line(x1, y1, x2, y2, style?)

g.line(32, 32, 32 + 14 * math.sin(a), 32 - 14 * math.cos(a),
       { stroke = "primary", width = 3, cap = "round" })

g.rect(x, y, w, h, style?)

g.rect(4, 20, 56, 8, { fill = "well" })

g.circle(cx, cy, r, style?)

g.circle(32, 32, 30, { fill = "raised", stroke = "rim", width = 1 })

g.arc(cx, cy, r, a0, a1, style?)

Angles in radians, standard canvas orientation (0 = 3 o’clock, increasing clockwise).

g.arc(32, 32, 24, 0, math.pi * 1.5, { stroke = "@brand", width = 4 })

g.path(points, style?)

A polyline from an array of {x, y} points.

g.path({ {4, 28}, {18, 12}, {32, 20}, {60, 6} }, { stroke = "live", width = 2 })

g.text(str, x, y, style?)

Bold Inter, baseline at y.

g.text("42%", 22, 38, { size = 13, fill = "primary" })

The style table

KeyApplies toValues
fillclosed shapes, textany color: roles, @palette, hex
strokeoutlines, lines, arcs, pathssame
widthstrokespx, default 1
capline endsbutt (default) round square
sizetextpx, default 13

Role and palette colors resolve against the active theme at paint time — a draw gauge recolors with a theme switch exactly like every declarative node.

esc
Type to search
navigate open