No-Code Workspace — Block Reference

SearchBlock usage recipe

Moving Color (MOVING_COLOR)

Tracks whether a detected color changes position

Documented for app version 1.0.50Updated:

Block Preview

Captured from the in-app Visual Builder block card in the English UI.

English UI
Moving Color (MOVING_COLOR) block card

Block Preview

Captured from the English Visual Builder block card with the Advanced/extra settings panel expanded.

English UI
Moving Color (MOVING_COLOR) advanced settings panel

What Does It Do?

Finds each selected colour inside the region and tracks it during a short observation window. It goes TRUE when any colour meaningfully changes position or footprint, and FALSE when it stays still or is never found. This block answers "did the color move?" rather than only "does the color exist?".

Shared Mechanics for Search Blocks

Multiple search regions and excluded regions

IMAGE, TEXT, COLOR and the other search blocks that use the Search Regions editor can define several search regions with + Add Region; OCR_VALUE tries its read regions in order. Inside a search region, Exclude Region skips noisy areas such as a HUD or a badge, and it applies only to the search region it belongs to. From 1.0.51 one search region can hold several excluded regions, each with its own shape and its own variable or Dialog binding.Coming soonv1.0.51

Sticky anchors for edge-aware scaling

You can now assign a sticky anchor to each search region. This brings Region.sticky into the no-code surface so the region can stay aligned to anchors such as top_left, bottom_right, or center when screen geometry changes.

Scan rate, duration, and retry behavior

Search Duration (ms) defines one search window; 0 means a single look. Retry Mode in the Retry & Wait section is Off, Infinite Loop, Retry On True, Retry On False, Wait Until Found, or Wait Until Gone, so a block becomes a one-shot check, a controlled polling step, or a step that waits for a target to appear or to disappear. Retry Timeout (ms, 0 = unlimited) bounds the loop; Scan Rate (Hz) sets scans per second.

Match tuning and device variance

mScore, scale, and scan engine choices matter across themes, display sizes, and rendering differences. Smaller regions plus the right engine dramatically improve builder stability.

When to Use?

1

Use it when:

2

You need to know whether a moving target, icon, or colored fragment is actually shifting

3

You want to wait while the color is stable and act only after movement starts

4

You need to track a bright moving marker in a game or app

5

Color is enough and you want a faster movement check than IMAGE

How to Use?

1

Add "Moving Color (MOVING_COLOR)"

2

Pick a color or enter a HEX value

3

SCAN REGION: Limit it to the area where motion can happen; narrower is more stable

4

SCAN ENGINE: cluster_color is the default and safest choice; tolerant_color is the next best

5

OBSERVE WINDOW (ms): The window during which movement is expected (default 2000)

6

CHANGE THRESHOLD (px): Minimum displacement used to ignore small jitter (default 6)

7

MIN MOVING FRAMES: Requires consecutive motion instead of a one-frame jump (default 2)

8

OBSERVE SCAN RATE (Hz): How often the region is sampled (12 on a new block, up to 60)

9

Configure TRUE/FALSE branches based on the motion result

Parameters

Color (HEX)

Colour(s) to track. Pick from screen or enter manually; each colour is tracked on its own

Scan Region

Area where movement is observed. Narrower is more stable

Scan Engine

cluster_color (default), tolerant_color, exact_color, gradient_color, dominant_color

Observe Window (ms)

Observation window used while waiting for the color to move (default 2000)

Change Threshold (px)

Minimum pixel displacement counted as meaningful motion (default 6)

Min Moving Frames

Number of consecutive moving frames required to confirm motion (default 2)

Observe Scan Rate (Hz)

Samples per second while observing: 1–60 Hz, 12 on a new block. At 0 the block's Scan Rate is used, and if that is also 0, 12 Hz (~83 ms)

Match / Wait / Timeout

Shared wait and timeout controls used by other search blocks

Score Variable (Advanced)

Writes the 0.0–1.0 confidence to the variable named in Advanced; COMPARE it to drop weak matches (same as IMAGE)

Match Position (matchX/Y/W/H) (Advanced)

Writes the match box into your own variables such as matchX/Y/W/H; CLICK {matchX},{matchY} to act there (same as IMAGE)

Item conditions and multiple exclusionsComing soonv1.0.51

Same as IMAGE: a Condition in each entry's lightbulb; several exclusions per region

Example Usage

1

Scenario 1: Wait for a moving marker

2

MOVING_COLOR → choose the marker color

3

Scan region: Panel where the marker travels

4

Observe Window: 1500 ms

5

Change Threshold: 8 px

6

TRUE: CLICK on the match

7

FALSE: WAIT briefly and retry

8

Scenario 2: Ignore a warning until it starts moving

9

MOVING_COLOR → select the yellow warning dot

10

FALSE branch: LOG "warning is stable"

11

TRUE branch: Continue with SYSTEM or SWIPE after motion starts

Combinations / Used With

1

MOST COMMON COMBINATIONS:

2

MOVING_COLOR + CLICK: Click when motion is confirmed

3

MOVING_COLOR + WAIT: Wait when there is no motion, then retry

4

MOVING_COLOR + IMAGE: Detect motion quickly by color, then verify with image

5

MOVING_COLOR + LOG: Record whether movement started or not

6

MOVING_COLOR + SWIPE: Continue the screen flow after motion is detected

Tip

Tracks whether a detected color changes position

Warnings / Cautions

1

WARNING:

2

If the same color appears in many places, the block may track the wrong target; keep the scan region narrow

3

gradient_color and dominant_color are less stable for motion tracking; start with cluster_color

4

A very low threshold can treat one-frame jitter as real motion

5

If the color is never found, the block returns FALSE; that path also covers the "no motion" case

Step-by-Step Mastery: Basic → Advanced → Pro

The same block at three levels: a quick start, the real options, and professional techniques.

Basic

The MOVING_COLOR block first finds the target colour in the selected region, then watches whether that same match shifts position within a short observation window. If the colour moves meaningfully it goes TRUE; if it stays still or is never found it goes FALSE. So this block answers "did the colour move?" rather than "does the colour exist?". Simplest use: pick the moving marker's colour, shrink the region to the panel it travels in, and act on the TRUE branch when motion is detected.

Advanced

Three calibration levers work together. Observe Window (movingColorObserveTimeoutMs, default 2000ms) is the window you wait for the colour to move; enlarge it when motion is rare/slow, keep it small when motion is frequent/fast to speed up the decision. Change Threshold (movingColorThresholdPx, default 6px) is the minimum displacement needed to ignore jitter; raise it to 8–12px if the screen shakes slightly. Min Moving Frames (movingColorMinMovedFrames, default 2) requires consecutive moving frames so a one-frame jump is not counted as real motion; set 2–3 in a noisy scene. Practical calibration order: raise the threshold first (cuts false positives), then increase min-frames (drops one-frame noise), and finally tune the timeout to the natural period of the motion. Observe Scan Rate (movingColorScanRateHz) is the samples per second during observation (1–60 Hz, 12 on a new block; at 0 the block's Scan Rate is used, and if that is also 0, 12 Hz ≈ 83 ms): a high Hz catches fast motion but keeps the CPU busy throughout the observation window, so the battery/heat cost on older devices is noticeable; balance smoothness against battery by experimenting. For the Scan Engine, cluster_color is the safest choice and tolerant_color is the next best. ADVANCED → Score Variable (scoreVar) is critical here too: write the tracked colour match's confidence into a variable and reject a weak match with COMPARE — that way the block does not chase a false/faint colour blob as if it were the real target.

Pro

The block measures motion by tracking the nearest same-colour match; if the colour appears in many places it may follow the wrong target, so keep the region as narrow as possible. gradient_color and dominant_color are unstable for motion tracking; start with cluster_color. If the threshold is too low it mistakes one-frame jitter for real motion; balance this by raising movingColorMinMovedFrames to 2–3 and setting the threshold to 6–10px. Use scoreVar + COMPARE to drop weak colour matches and avoid following the wrong target. If the colour is never found the block returns FALSE, which is the same branch as "no motion"; if you need to separate them, add a COLOR test on the FALSE branch to check whether the colour exists at all.

Real-World Scenarios — Step-by-Step Recipes

Small, reproducible examples that combine several blocks — build them straight into your own macro.

Wait for a moving marker, then click
  1. 1Add MOVING_COLOR and choose the marker colour
  2. 2Shrink the scan region to the panel where the marker travels
  3. 3Observe Window = 1500ms, Change Threshold = 8px, Min Moving Frames = 2
  4. 4Scan Engine = cluster_color
  5. 5Add a CLICK with "Click on match" on TRUE; WAIT + retry on FALSE
Ignore a warning until it starts moving
  1. 1Add MOVING_COLOR and select the yellow warning dot
  2. 2Observe Window = 2000ms, Change Threshold = 10px
  3. 3On FALSE branch LOG "warning is stable"
  4. 4On TRUE branch start the next flow with SWIPE or SYSTEM
Calibrate away false positives on a shaky screen
  1. 1Add MOVING_COLOR, Scan Engine = cluster_color
  2. 2First raise Change Threshold from 6 to 10–12px
  3. 3Increase Min Moving Frames from 2 to 3
  4. 4ADVANCED → scoreVar = "colorScore"; follow with COMPARE colorScore >= 0.85
  5. 5If false triggers persist, shorten Observe Window and lower scanRateHz

Which Blocks to Pair With — Pro Combinations

This block is useful on its own — but it shines when paired with the right partners.

+ CLICK (Click on Match)

When motion is confirmed, click the colour's last position via contextX/Y; you catch the roaming target.

+ SWIPE

When a movement direction is detected, SWIPE in that direction to build a flow that follows the moving target.

+ IMAGE

Detect motion quickly by colour, then verify visually with IMAGE that the right element actually moved.

+ WAIT

When there is no motion, WAIT briefly on the FALSE branch and re-observe; you avoid wasting CPU.

+ COMPARE (scoreVar)

Write the tracked colour match's confidence with ADVANCED scoreVar and reject a weak blob via COMPARE; the block will not mistake a noisy colour patch for the real moving target.

Common Mistakes and Fixes

Leaving the Change Threshold too low (e.g. 1px); small jitter is mistaken for motion. Fix: set the threshold to 6–10px and Min Moving Frames to 2–3.

Tracking the wrong target when many copies of the same colour exist in a wide region. Fix: shrink the region to only the area where motion happens.

Choosing gradient_color/dominant_color for motion tracking and getting unstable results. Fix: try cluster_color first, then tolerant_color if needed.

Pro Tips

Raising movingColorScanRateHz catches fast motion but increases CPU load on older devices; balance smoothness against battery by experimenting.

To disambiguate the FALSE branch, follow it with a COLOR block: colour present but no motion means "stationary", colour absent means "gone".

Each selected colour is tracked on its own; the block goes TRUE when any of them meaningfully changes position or footprint. One block can watch several markers in the same panel.

Coming soonv1.0.51Each colour can get a Condition from its lightbulb (same rules as IMAGE): a colour whose condition is off is not tracked and its previous position is discarded; with no active colour the observation pass ends.

This Block's Code (Editor View)

Lua

The Lua below is what the Code Editor generates for this block with sample settings. Adding cases, branches, actions or loops, or changing settings, extends the generated code accordingly. Study it to see what the block does under the hood, to learn Lua, or to copy and adapt it. Use the Copy button (top-right) to paste it into the Code Editor, or “Try in Editor →” to run it in the live editor.

local function _pickMovingColor1_0(_refX, _refY, _refW, _refH)
 local _best = nil
 local _bestCost = nil
 local _bestScore = nil
 local _bestArea = nil
 do
  local _reg2 = Region():noScale()
  local _list2 = _reg2:findMulti(Asset.color("#00FF00"):mScore(0.8), {engine="cluster_color"})
  local _i2 = 1
  while _i2 <= #_list2 do
   local _cand2 = _list2[_i2]
   local _pt2 = _cand2 and _cand2:getPoint() or nil
   if _pt2 ~= nil then
    local _px2 = _pt2:getX()
    local _py2 = _pt2:getY()
    local _pw2 = math.max(1, _cand2:getW() or 1)
    local _ph2 = math.max(1, _cand2:getH() or 1)
    local _pScore2 = _cand2:getScore() or 0
     if _refX == nil or _refY == nil then
      local _pArea2 = _pw2 * _ph2
      if _best == nil or _pScore2 > (_bestScore or -1) or (_pScore2 == (_bestScore or -1) and _pArea2 > (_bestArea or 0)) then
       _best = _cand2
       _bestScore = _pScore2
       _bestArea = _pArea2
      end
     else
      local _dw2 = _refW ~= nil and math.abs(_pw2 - _refW) or 0
      local _dh2 = _refH ~= nil and math.abs(_ph2 - _refH) or 0
      local _sizeCost2 = (_dw2 * _dw2) + (_dh2 * _dh2)
      local _dx2 = _px2 - _refX
      local _dy2 = _py2 - _refY
      local _dist2 = (_dx2 * _dx2) + (_dy2 * _dy2)
      local _cost2 = _dist2 + (_sizeCost2 * 0.35)
      if _best == nil or _cost2 < (_bestCost or _cost2 + 1) or (_cost2 == _bestCost and _pScore2 > (_bestScore or -1)) then
       _best = _cand2
       _bestCost = _cost2
       _bestScore = _pScore2
      end
     end
   end
   _i2 = _i2 + 1
  end
 end
 return _best
end
local _movingTimeout1 = math.max(1, math.floor(1000))
local _movingThresholdRef1 = math.max(0, math.floor(5.0))
local _mvsOk1, _mvsX1, _mvsY1 = pcall(function() local _o = Point(2, 2); local _ux = Point(3, 2); local _uy = Point(2, 3); local function _bad(_p) return _p == nil or (type(_p) == "table" and _p.__mh_point_unresolved == true) end; if _bad(_o) or _bad(_ux) or _bad(_uy) then error("scale probe failed") end; return math.abs(_ux:getX() - _o:getX()), math.abs(_uy:getY() - _o:getY()) end)
if not _mvsOk1 or type(_mvsX1) ~= "number" or _mvsX1 <= 0 then _mvsX1 = 1 end
if type(_mvsY1) ~= "number" or _mvsY1 <= 0 then _mvsY1 = 1 end
local _movingMinFrames1 = math.max(1, math.floor(2))
local _movingDelay1 = (math.max(1, math.floor(1000 / 12)))
local _movingSearchDeadline1 = System.currentTime() + _movingTimeout1
local _movingObserveDeadline1 = nil
local _movingActiveTrackerIndex1 = nil
local _movingTrackers1 = {
 { helper = _pickMovingColor1_0, color = "#00FF00", baselineX = nil, baselineY = nil, baselineW = nil, baselineH = nil, lastX = nil, lastY = nil, lastW = nil, lastH = nil, movedFrames = 0, hasDetectedMotion = false },
}
local m1 = nil
local _movingDetectedColor1 = nil
local _movingDetectedDx1 = 0
local _movingDetectedDy1 = 0
local _movingDetectedDw1 = 0
local _movingDetectedDh1 = 0
local _movingDone1 = false
_lastMovingColorMotion = nil
while not _movingDone1 and ((_movingObserveDeadline1 == nil and System.currentTime() <= _movingSearchDeadline1) or (_movingObserveDeadline1 ~= nil and System.currentTime() <= _movingObserveDeadline1)) do
 Snap.screenRefresh()
 local _movingTrackerIndex1 = 1
 while not _movingDone1 and _movingTrackerIndex1 <= #_movingTrackers1 do
  local _movingTracker1 = _movingTrackers1[_movingTrackerIndex1]
  local _movingCandidate1 = _movingTracker1.helper(_movingTracker1.lastX, _movingTracker1.lastY, _movingTracker1.lastW, _movingTracker1.lastH)
  if _movingCandidate1 ~= nil then
   local _movingPoint1 = _movingCandidate1:getPoint()
   if _movingPoint1 ~= nil then
    local _movingX1 = _movingPoint1:getX()
    local _movingY1 = _movingPoint1:getY()
    local _movingW1 = math.max(1, _movingCandidate1:getW() or 1)
    local _movingH1 = math.max(1, _movingCandidate1:getH() or 1)
    if _movingTracker1.baselineX == nil or _movingTracker1.baselineY == nil then
     _movingTracker1.baselineX = _movingX1
     _movingTracker1.baselineY = _movingY1
     _movingTracker1.baselineW = _movingW1
     _movingTracker1.baselineH = _movingH1
     _movingTracker1.lastX = _movingX1
     _movingTracker1.lastY = _movingY1
     _movingTracker1.lastW = _movingW1
     _movingTracker1.lastH = _movingH1
     if _movingActiveTrackerIndex1 == nil then
      _movingActiveTrackerIndex1 = _movingTrackerIndex1
      _movingObserveDeadline1 = System.currentTime() + _movingTimeout1
     end
    else
     _movingTracker1.lastX = _movingX1
     _movingTracker1.lastY = _movingY1
     _movingTracker1.lastW = _movingW1
     _movingTracker1.lastH = _movingH1
     local _movingSignedDx1 = _movingX1 - _movingTracker1.baselineX
     local _movingSignedDy1 = _movingY1 - _movingTracker1.baselineY
     local _movingDx1 = math.abs(_movingSignedDx1)
     local _movingDy1 = math.abs(_movingSignedDy1)
     local _movingDw1 = math.abs(_movingW1 - (_movingTracker1.baselineW or _movingW1))
     local _movingDh1 = math.abs(_movingH1 - (_movingTracker1.baselineH or _movingH1))
     local _movingRDx1 = _movingDx1 / _mvsX1
     local _movingRDy1 = _movingDy1 / _mvsY1
     local _movingRDw1 = _movingDw1 / _mvsX1
     local _movingRDh1 = _movingDh1 / _mvsY1
     local _movingDist1 = math.sqrt((_movingRDx1 * _movingRDx1) + (_movingRDy1 * _movingRDy1))
     local _movementDetected1 = false
     if _movingThresholdRef1 <= 0 then
      _movementDetected1 = _movingDx1 > 0 or _movingDy1 > 0 or _movingDw1 > 0 or _movingDh1 > 0
     else
      _movementDetected1 = _movingRDx1 >= _movingThresholdRef1 or _movingRDy1 >= _movingThresholdRef1 or _movingRDw1 >= _movingThresholdRef1 or _movingRDh1 >= _movingThresholdRef1 or _movingDist1 >= _movingThresholdRef1
     end
     if _movementDetected1 then
      _movingTracker1.movedFrames = _movingTracker1.movedFrames + 1
      if _movingTracker1.movedFrames >= _movingMinFrames1 then
       _movingTracker1.hasDetectedMotion = true
      end
     else
      _movingTracker1.movedFrames = 0
     end
     if _movingTracker1.hasDetectedMotion then
      _movingDetectedColor1 = _movingTracker1.color
      _movingDetectedDx1 = _movingSignedDx1
      _movingDetectedDy1 = _movingSignedDy1
      _movingDetectedDw1 = _movingDw1
      _movingDetectedDh1 = _movingDh1
      m1 = _movingCandidate1
      _lastMovingColorMotion = {
       match = _movingCandidate1,
       color = _movingTracker1.color,
       startX = _movingTracker1.baselineX,
       startY = _movingTracker1.baselineY,
       endX = _movingX1,
       endY = _movingY1,
       dx = _movingSignedDx1,
       dy = _movingSignedDy1,
       dw = _movingDw1,
       dh = _movingDh1,
      }
      _movingDone1 = true
     end
    end
   else
    _movingTracker1.movedFrames = 0
   end
  else
   _movingTracker1.movedFrames = 0
  end
  _movingTrackerIndex1 = _movingTrackerIndex1 + 1
 end
 if not _movingDone1 then
  wait(_movingDelay1)
 end
end
if m1 ~= nil then
 System.log("MovingColor[cluster_color] change detected for " .. tostring(_movingDetectedColor1 or "?") .. " dx=" .. tostring(_movingDetectedDx1) .. ", dy=" .. tostring(_movingDetectedDy1) .. ", dw=" .. tostring(_movingDetectedDw1) .. ", dh=" .. tostring(_movingDetectedDh1))
else
 System.log("MovingColor[cluster_color] no change detected within " .. tostring(_movingTimeout1) .. " ms")
end
_lastMatch = m1

Note: generated code can evolve across versions; helper names (e.g. _reg1, m1) and internal optimizations may change. The logic and the called APIs reflect the block’s behavior.

Block ↔ Lua Mapping →

Shared Controls for This Block Family

The guidance below is not identical for every block. It summarizes the professional controls that most often repeat in the selected block family.

🔎Scan Region

Keep the search close to the target. Smaller regions are faster and more stable.

🚫Exclude Region

Exclude static noise, badges, animations, or reflective areas to reduce false matches.

⏱Timeout and Scan Rate

Timeout and scan rate define whether the block acts as a single check or controlled polling step.

🎯Match Tuning

mScore, scale, and engine choice must be tuned together for theme, resolution, and rendering differences.

🧭Result Variables

OCR_VALUE writes the number it reads to its result variable (default ocr_val); TEXT writes the text it passed through its find/replace step to the Save Read Value variable (default ocr_val). In Match Variables, the score variable (0.0–1.0 confidence) and the X/Y/W/H variables carry the match confidence and position into later blocks: branch on the score with COMPARE, and in CLICK bind the coordinates to them with {x} or turn on Click on match. _lastMatch is the last match that Click on match uses; _lastOcrText is kept only for older CUSTOM_CODE scripts.

Builder vs Lua Boundary

Raw touch pointer lifecycle stays code-first

TOUCH and TOUCH_BREAK cover recipe-style touch flows. Raw pointer choreography such as Touch.down, Touch.move, Touch.up, Touch.dispatch, Touch.reset, Touch.breakAll, and Touch.releaseAfter is not exposed as a first-class builder block.

Touch.downTouch.moveTouch.upTouch.dispatchTouch.resetTouch.breakAllTouch.releaseAfter
Bounded dialog design is builder-backed; freer composition stays code-first

The DIALOG block and dialog designer cover a bounded field set in the builder: Info Text, Text Input, Checkbox, Description, Radio Group, Dropdown, Multi Select, Tab Group, Date Time, Number Range, Slider, Image Picker, Color Picker, File Picker, Recorder Picker, Tag, Signature, and Spacer. The buttons are designed too: the Positive button and Negative button tabs set each button’s text, background, and border color, corner radius, and minimum height next to a preview. Freer Setting.builder composition, multi-step wizard flows, and mixed HUD/dialog choreography still do not map 1:1 into the builder.

Coming soonv1.0.51Two more fields: Agent Detect references (agent_detect_editor) lets the user pick a saved reference set or add and edit references, and Save confirms the selection; Navigation setup editor (navigation_editor) edits a saved map, marker and route from the Dialog and returns the setup and route selection without moving the character.

Setting.builderDialogHudTextViewAgentDetectEditorNavigationEditor
Advanced HTTP request chaining stays code-first

HTTP_GET, HTTP_POST, and HTTP_PUT cover common fixed-method flows; HTTP_REQUEST covers one bounded request with method, header lines, body, content type, query parameters (GET and DELETE), timeout, and a bounded retry. These blocks never throw; they write the status code (-1 on a network failure). Cookie handling, chained request objects, and lower-level client flows still need the code editor.

Request()Request.setCookieRequest.getCookiesCUSTOM_CODE
General programming surfaces stay code-first

The MAP, LIST, JSON_PARSE, REGEX_MATCH, and METRICS blocks cover the everyday cases: map actions, a static list, reading a value from a JSON path, regex match, find, replace, and split, and metrics actions. The rest of the Map, Array, JSON, Regex, and Metrics APIs (for example Array sort, filter, and map) is broader than the builder recipe model and stays on the CUSTOM_CODE and code editor side.

MapArrayJSONRegexMetrics
Low-level runtime and Lua composition stays code-first

Runtime helpers, raw Request objects, Lua built-ins such as print/pcall/xpcall, and free-form object chaining patterns are represented through CUSTOM_CODE or the code editor.

RuntimeRequestprintpcallxpcallCUSTOM_CODE

Debug Mode and Breakpoints

Each block card now exposes an independent breakpoint toggle. When enabled, the breakpoint preference is stored with the block.

Breakpoints are only emitted into runtime code when Debug Mode is enabled from Visual Builder settings. When Debug Mode is off, breakpoints stay saved but do not pause execution.

Use ASSERT for fail-fast validation, and use breakpoints for step-by-step tracing and controlled pauses in the same flow.

Variable Scope

1

Variables defined with SET_VARIABLE are accessible throughout the macro (global scope). All blocks can read/write the same variable.

2

However, a variable defined inside a GROUP will be 'nil' (undefined) until the GROUP executes. Blocks outside the GROUP using this variable may produce unexpected results.

3

FOR_EACH loop variables (item and index) only carry valid values inside the loop body. Outside the loop they are outside local scope and should not be used.

4

TRY_CATCH error variable is only valid within the catch block. If the try block succeeds, the catch branch is not entered and the error variable remains undefined.

5

TIP: Before using a variable inside a GROUP, assign a default value with SET_VARIABLE outside the GROUP. This way the variable won't be nil even if the GROUP has not run.

Inline Expressions

You can embed variable values and calculations into text fields using {{ expression }} syntax. Expressions inside double curly braces are evaluated as Lua code and the result is inserted into the text.

Syntax
{{ expression }}
Examples
Skor: {{ score + 1 }} → "Skor: " .. tostring(score + 1)
{{ name }} kazandı! → tostring(name) .. " kazandı!"
X:{{ x }} Y:{{ y }} → "X:" .. tostring(x) .. " Y:" .. tostring(y)
Supported Blocks
SET_VARIABLELOGCOMPARECLIPBOARD_WRITENOTIFY_TOASTKV_SET
Security Note

Expressions are checked by the sandbox policy. Security-sensitive calls like loadstring, require, debug are automatically blocked.

General Tips

1

Keep scan regions as small as possible — improves speed, reduces false matches.

2

Always add an exit condition when using infinite loops (BREAK, timeout, or conditional exit).

3

Use network operations and error-prone steps inside TRY_CATCH.

4

If you need global error handling, use the Error Handler (ERROR_HANDLER) block as a singleton in the project.

5

Small coordinate offsets (CLICK/SWIPE) improve tap accuracy across different DPI and screen scales.

6

Customize macros with Dialog block — different parameters each run.

7

Define repeating steps once with GROUP_CALL, call from many places.

Cross-macro local library

Local Shared Macro Area

The Local Shared Macro Area moves blocks, variables, dialog fields, gallery assets, and imported media dependencies between macros on the same device. It is device-local, not cloud sync, not Firebase sharing, and not a replacement for public macro sharing.

Professional usage model: put a reusable login flow, common scan region, shared dialog form, variable group, template image, color profile, replay recording, or imported PLAY_SOUND media file into the shared area from one macro, then import it from the relevant + menu in another macro.

VISUAL_BLOCKSVARIABLESDIALOG_FIELDSTEMPLATE_IMAGECOLOR_SWATCHREPLAY_RECORDMEDIA_FILE
1

Send to the shared area

When one item or a multi-selection is active, the shared-area button can copy the selected part from the workspace, group, true/false action branch, dialog, and variables surfaces into the device-local library.

2

Import into another macro

The shared-area entry under the + menu imports the selected item into the surface that opened it. Main workspace, group, action branch, dialog, and variables each receive the compatible item type.

3

Share gallery assets

Gallery items include template images, color swatches, replay records, and imported audio/media files. They can move between macros on the same device so repeated templates, colors, recordings, and PLAY_SOUND media assets do not need to be captured again.

4

Keep it clean

The shared area is not a permanent dumping ground. Delete stale shared items from the shared-library panel; imports create a copy in the target macro and do not rewrite the source macro flow.

Checkpoint: after importing, review block names, linked template/image/color/record/media assets, and variable names in the target macro. The shared area creates a copy; delete stale shared items from the shared-library panel to keep the library clean.

AI assistant

Build a block chain with MH AI in natural language

Tell MH AI what you want in plain language; it proposes the right block sequence and lands it in the workspace after you approve. In the current release it works with cloud providers such as Gemini, Claude, and OpenAI; on-device local models are on the roadmap.

Learn more →