Canvas Overlay

Draw shapes and text directly on top of the screen

Draw shapes and text directly on top of the screen using a transparent overlay. Useful for visualising match results, showing status indicators, or highlighting regions while a macro runs.

All drawing is organised into named slots. Each slot holds one drawing expression and can be updated or removed independently, so you never have to redraw elements that have not changed.

A slot can also follow the content underneath it. Pinned with canvas.follow, it is redrawn on every frame at wherever that content has scrolled to — without your script having to search again — which is the difference between an overlay that tracks and one that visibly trails.

canvas.set(id, fn)

Registers the drawing for the given slot ID. The function runs once, immediately, to capture its canvas.draw* calls; those captured commands are then replayed on every frame redraw — the function itself is not re-invoked per frame, so values read inside it are fixed at the moment you call canvas.set. To update what is drawn, call canvas.set again with the same id (this replaces the previous drawing).

Calls may be nested — the slot that was open around a canvas.set is restored when it returns. If your drawing function throws, the unfinished drawing is discarded rather than left half-painted on screen, and the error reaches your script as usual.

ParameterTypeDescription
idstringUnique name for this slot. Anything other than a string throws a TypeError.
fnfunctionZero-argument function containing one or more canvas.draw* calls

Returns void

// Static label — draw once, stays until cleared
canvas.set("status", function() {
  canvas.drawText("Macro running", 40, 80, { color: "#FFFFFF", textSize: 36 });
});

// Dynamic cursor — update position each loop iteration
while (!isStopped()) {
  var pos = findObject("target");
  if (pos !== null) {
    canvas.set("cursor", function() {
      canvas.drawCircle(pos.cx, pos.cy, 40, { color: "#FF4444" });
      canvas.drawText("found", pos.cx - 20, pos.cy - 50, { color: "#FF4444", textSize: 28 });
    });
  }
  wait(100);
}

canvas.clear()

canvas.clear(id)

Removes drawing slots from the overlay. With no argument, all slots are cleared. With an id, only that slot is removed — all others stay visible.

ParameterTypeDescription
idstring (optional)Slot to remove; omit to clear everything. A non-string id throws a TypeError.

Returns void

canvas.clear("cursor");   // remove only the cursor slot
canvas.clear();           // remove all slots

canvas.follow(id, region?)

Pins a slot to on-screen content so it stays glued to it while that content scrolls.

Redrawing by search alone always trails: a capture, a match and a round trip through the script engine cost 100 ms and up, and during a fling that is hundreds of pixels of visible lag. canvas.follow moves the per-frame work out of your script — the slot's recorded drawing is redrawn translated by however far the tracked content has moved, on every mirrored frame, without re-entering the engine.

The coordinates you drew with are taken to describe the frame the search that produced them ran on, not the frame on screen when you call follow. Motion during the search is replayed forward, so a fix that lands late does not snap the overlay backwards.

ParameterTypeDescription
idstringSlot to pin. Anything other than a string throws a TypeError.
regionRegion (optional)Where the scrolling content lives. Omit for the whole screen — but prefer passing it: a band containing a fixed HUD drags the estimate toward zero, so the region should cover content that actually moves. Four distinct regions can be tracked at once. Passing a small one — a text match, as below — is fine: tracking samples at least 256 px on each axis, growing around what you pass, because measuring a fling needs more of the scrolling surface than a line of text covers.

Returns boolean — true if the slot is now following. false means no tracking band was available (more than four distinct regions): the slot stays drawn where it was but is no longer pinned, so retry on the next iteration once another slot releases its region.

// Overlay a label on a row of text and keep it there while the list scrolls.
// The search runs a few times a second; the overlay tracks every frame.
while (!isStopped()) {
  var m = findText("Balance");
  if (m !== null) {
    canvas.set("hud", function() {
      canvas.drawRect(m.x, m.y, m.w, m.h, { color: "#00FF88" });
    });
    canvas.follow("hud", m);
  }
  wait(400);
}

A follow survives a later canvas.set on the same slot — that is what keeps the loop above from flickering — and is dropped by canvas.clear on that slot.

Limits. Following tracks translation only, and gives up in two different ways. Zoom and a wholesale screen change (a page transition) read as low confidence: the offset stops advancing and the overlay holds where it is rather than sliding somewhere wrong. A rotation is caught separately, by watching the display rather than the pixels — the frames on either side of a turn correlate perfectly well, so confidence would never notice one. Everything measured before the turn describes a layout that no longer exists, so it is discarded and the overlay again holds position until you re-anchor. Either way it does not re-find your target for you, so keep the re-anchor loop running. Like every search, it needs an active screen capture.

canvas.unfollow(id)

Unpins a slot, leaving its drawing exactly where it is on screen. The accumulated offset is kept, not discarded — the drawing stays where the last frame put it rather than jumping back to the coordinates you originally drew with. Clearing a slot unpins it too, so this is only needed when you want the drawing to stay put.

ParameterTypeDescription
idstringSlot to unpin. A non-string id throws a TypeError.

Returns void

canvas.unfollow("hud");

canvas.drawCircle(x, y, r, options?)

Draws a circle. Must be called inside a canvas.set callback.

ParameterTypeDescription
x, ynumberCenter in screen pixels
rnumberRadius in screen pixels
optionsobjectOptional paint settings (see Paint options below)

Returns void

canvas.set("dot", function() {
  canvas.drawCircle(540, 960, 30, { color: "#00FF00", alpha: 180 });
});

canvas.drawRect(x, y, w, h, options?)

Draws a rectangle. Must be called inside a canvas.set callback.

ParameterTypeDescription
x, ynumberTop-left corner in screen pixels
w, hnumberWidth and height in screen pixels
optionsobjectOptional paint settings

Returns void

// Highlight a matched object's bounding box
var pos = findObject("enemy");
if (pos !== null) {
  canvas.set("box", function() {
    canvas.drawRect(pos.x, pos.y, pos.w, pos.h, { color: "#FF4444", strokeWidth: 3 });
  });
}

canvas.drawLine(x1, y1, x2, y2, options?)

Draws a straight line. Must be called inside a canvas.set callback.

ParameterTypeDescription
x1, y1numberStart point in screen pixels
x2, y2numberEnd point in screen pixels
optionsobjectOptional paint settings

Returns void

canvas.set("divider", function() {
  canvas.drawLine(0, 960, 1080, 960, { color: "#FFFFFF", strokeWidth: 2, alpha: 100 });
});

canvas.drawText(text, x, y, options?)

Draws a text string. The point (x, y) is the left baseline of the text. Must be called inside a canvas.set callback.

ParameterTypeDescription
textstringText to render
x, ynumberLeft baseline position in screen pixels
optionsobjectOptional paint settings

Returns void

canvas.set("label", function() {
  canvas.drawText("Step: " + step, 40, 120, { color: "#FFFF00", textSize: 40, bold: true });
});

Paint options

All canvas.draw* functions accept an optional options object. Unrecognised keys are silently ignored, and each option is applied independently — an unparseable color falls back to the default without discarding the other options you passed.

OptionTypeDefaultDescription
colorstring"#FFFFFF"Hex color in "#RRGGBB" or "#AARRGGBB" format
alphanumber255Opacity 0–255, applied on top of any alpha encoded in color
fillbooleanshape-dependenttrue = filled, false = outline only. Omitted, the shapes (drawCircle, drawRect, drawLine) stroke while drawText fills.
strokeWidthnumber4Outline thickness in screen pixels, used when fill is false
textSizenumber48Font size in screen pixels for drawText
boldbooleanfalseBold typeface for drawText
// Filled semi-transparent red circle
canvas.drawCircle(300, 500, 50, { color: "#FF0000", alpha: 160 });

// Outlined blue rect, 4 px border
canvas.drawRect(100, 200, 300, 150, { color: "#2563EB", strokeWidth: 4 });

// Bold yellow label, 44 px
canvas.drawText("HP: 80%", 40, 100, { color: "#FACC15", textSize: 44, bold: true });

Full example — live match highlight

// Spawn a background thread that keeps the canvas in sync
spawnThread(function() {
  while (!isStopped()) {
    var pos = findObject("enemy_icon");
    if (pos !== null) {
      canvas.set("highlight", function() {
        canvas.drawRect(pos.x, pos.y, pos.w, pos.h,
          { color: "#FF4444", strokeWidth: 3 });
        canvas.drawText("enemy", pos.x, pos.y - 8,
          { color: "#FF4444", textSize: 28 });
      });
    } else {
      canvas.clear("highlight");
    }
    wait(150);
  }
  canvas.clear();
});

// Main script continues independently
while (!isStopped()) {
  findAndTap("attack_button");
  wait(500);
}