Overlay and canvas
Two ways to put something on screen: a retained widget tree that looks like the rest of Axiom, and a draw list for mods that want their own pixels.
A retained widget tree. You describe it; the host keeps it and renders every frame without asking again. What crosses the boundary is the diff, not the draw calls.
An immediate-mode API is pleasant to write and the wrong shape here: every
Text() call would be a crossing, so a panel with sixty rows would
cost sixty crossings every frame. With a retained tree, a frame where nothing
changed costs zero.
A top-level panel. Gets its own tab in the overlay.
A list whose contents you replace wholesale.
Replace the contents. Diffed host-side, so an unchanged list costs nothing.
void frame(Framework& f) override { list.set(rows); // 1 crossing, and only if rows changed }
Bound to a config key, so persistence and validation are free.
[config]; the value is clamped to its declared rangeFires through event() on the mod’s thread, next frame. It does not fire inside the click, which matters if you expected to read input state in it.
One row. Format args are marshaled once.
A line of text.
A table with headers; set() like a list.
A checkbox bound to a config key.
A dropdown.
A color picker.
A rolling series. The host keeps the ring buffer, so you push one value a frame rather than an array.
Layout only, no behavior.
Collapsible, state remembered per user.
Quiet explanatory text. Use it to say why something is unavailable.
Panels inherit the overlay’s tokens so twelve mods look like one
application. A mod cannot set a font, a background or a corner radius. If you
need your own pixels, that is canvas, deliberately a separate
thing.
A draw list, not a draw API. Commands accumulate in your own memory and cross the boundary once, at submit.
Start a list. Nothing has crossed yet.
World under the game’s HUD, Hud over it. Overlay is denied: a mod that could draw over Axiom’s panels could cover the panel used to disable it.One crossing, whatever the list length. Twelve shapes or twelve thousand costs the same.
auto d = f.canvas().begin(Layer::World); auto ss = f.project().world_to_screen_many(ps); for (auto& s : ss) { if (s.failed()) continue; d.rect(s->x - 20, s->y - 40, 40, 80, Color::accent, 1.5f); } d.submit(); // 4 crossings for the whole frame
Draw commands
line(a, b, color, width) rect(x, y, w, h, color, width) rect_filled(x, y, w, h, color) circle(c, r, color, width, segments) poly(points, color, width) poly_filled(points, color) text(x, y, s, align, size) image(x, y, w, h, handle, tint) push_clip(rect) pop_clip() push_transform(mat) pop_transform()
color_space() unless you tone map yourself.size is DPI-scaled pixels.measure_text