# Macro Argument Form and Runtime Dashboard: UI Builder

> Design a macro argument form (.uibproj) and runtime dashboard (.uibrt) in the MAS UI Builder, export ui.xml and script_args.py, and drive widgets from Python.

Source: https://automationmacro.com/docs/ui-builder (Studio, updated 2026-09-04)

The UI Builder is the drag-and-drop designer that ships with Macro Automation Studio. It gives a Python macro two things a command line cannot: a typed argument form the user fills in before a run, and a live runtime dashboard that shows progress while the run happens. You place widgets on a canvas; MAS turns the form into Python arguments and turns the dashboard into one-line `mas.ui.*` calls. This page is for anyone who has a working script and wants to make it usable by other people.

## The two project types

| Project | File | What it is | Widgets |
|---|---|---|---|
| Argument form | `.uibproj` | The window the user fills in before the macro runs | 11 argument widgets, 3 layout widgets, an action bar |
| Runtime dashboard | `.uibrt` | The live panel shown while the macro runs | 7 runtime widgets |

A macro can have one, the other, both, or neither. They are separate files in the project folder. Only the script itself, `src/app.py`, is required.

## Open the UI Builder from the Code Editor

The UI Builder is a separate application that MAS launches for you. It is part of the standard install from the [download page](/download), so there is nothing extra to set up. You never start it by hand.

1. Open your macro in the **Code Editor**.
2. In the toolbar, click **Add Args UI** to design an argument form, or **Add Runtime UI** to design a dashboard.
3. If the project has no such file yet, MAS creates `script_runner.uibproj` or `runtime_ui.uibrt`, saves it to the project, and then opens the UI Builder on it.
4. Once a file exists, the same buttons read **Edit Args UI** and **Edit Runtime UI**.

The UI Builder runs as a single instance. If it is already open, clicking a button switches it to the file you asked for. When a `.uibrt` exists, the Code Editor's bottom panel gains a runtime tab next to the console, with a refresh control that re-reads the file after you edit it.

> [!NOTE]
> A new argument form starts as a 980 by 640 pixel window with 24 pixel padding, one tab named General, and an action bar with **Cancel** and **Run**.

## The editor at a glance

- **Palette** (left): every widget, grouped into argument, layout and runtime kinds. Drag one onto the canvas.
- **Canvas** (center): a scale model of the window. Widgets have absolute pixel positions and sizes. A 10 pixel snap grid is on by default.
- **Tabs** (above the canvas): a form can have several tabs. Dashboards can use tabs as well.
- **Inspector** (right): every property of the selected widget, including its argument key or its runtime name.
- **Toolbar** (top): the snap grid toggle, an auto-layout button for the active tab, **Preview**, **Save** and **Save As**.

**Save** always writes the project file. For an argument form it also exports `ui.xml` and `src/script_args.py`, but only when validation passes. If there are issues, the project is saved and the exports are skipped until you fix them.

## The macro argument form (.uibproj)

An argument form is a window of input fields. Each field carries a key. At run time MAS turns every field into a `--key=value` argument that your script reads.

### The 11 argument widgets

Each one produces exactly one argument.

| Widget | Kind | Value in Python | Use for |
|---|---|---|---|
| Text Input | `text-input` | `str` | names, URLs, short free text |
| Text Area | `text-area` | `str` | long text, lists, pasted blocks; you set the rows |
| Number Input | `number-input` | `int` or `float` | counts and thresholds, with min, max, step, precision and a suffix |
| Slider | `slider` | `int` or `float` | a bounded number chosen by dragging; can show its value |
| Checkbox | `checkbox` | `bool` | on or off flags |
| Combo Box | `combobox` | one option's `str` | pick one from a list; can be editable |
| Radio Group | `radio-group` | one option's `str` | pick one when every option should be visible |
| File Picker | `file-picker` | `str` path | choose a file, with optional filters such as `*.txt;*.csv` |
| Directory Picker | `directory-picker` | `str` path | choose a folder |
| Date Picker | `date-picker` | `str` | a date in the format you set, for example `YYYY-MM-DD` |
| Time Picker | `time-picker` | `str` | a time as `HH:mm`, `HH:mm:ss`, `hh:mm A` or `hh:mm:ss A` |

A Number Input or Slider becomes `float` when its step is not a whole number, otherwise `int`.

### Layout widgets

These three add structure and produce no argument.

- **Label**: static helper text with an optional font size.
- **Separator**: a horizontal or vertical divider with an optional caption.
- **Group Box**: a titled container that groups related fields.

### Argument metadata

Every argument widget has a block of metadata in the Inspector.

- **key** (required): the argument name. It must be a valid Python identifier (a letter or underscore, then letters, digits or underscores) and unique across the whole form.
- **required**: the form will not run without a value, and the generated argument is marked `required=True`.
- **helpText**: a short explanation that becomes the argument's help string.
- **showLabel**, **labelPosition** (top or left) and **labelGap**: control how the field's label is drawn.

### Validation

Before exporting, the UI Builder checks that every argument widget has a key, that each key is a valid identifier, and that no key is used twice. A project must also contain at least one tab. Fix every issue, then click **Save** again to export.

### The action bar

Below the tabs sits the row of buttons that closes the window. New forms start with **Cancel** and **Run**. In the Inspector you can edit each button's text, role (`run`, `cancel`, `reset` or `custom`) and style (`primary`, `secondary` or `danger`), and set the bar's alignment to right, center, left or spread. For most macros the default is right.

### What the form exports

- `src/script_args.py`: generated argparse code, grouped by tab. It parses `sys.argv` on import. Never hand-edit it; it is rewritten on every **Save**.
- `ui.xml`: the structural description Studio reads to render the native input dialog.

### Read the values in your script

Each tab becomes a namespace. A tab's name is lowercased and non-alphanumeric characters become underscores, so "My Settings" becomes `my_settings`. Access a value as `args.<tab>.<key>`.

```python
import mas
from src.script_args import args

target = args.general.target_name    # Text Input, key "target_name"
rounds = args.general.rounds         # Number Input, key "rounds"
dry_run = args.advanced.dry_run      # Checkbox, key "dry_run"

for i in range(rounds):
    mas.log(f"round {i + 1} of {rounds}")
    if dry_run:
        continue
    match = mas.find_object_retry(1234, total_tries=3, time_sleep=2.0)
    if match:
        mas.click(match.x, match.y)
```

Combo Box and Radio Group values are restricted to their options with `choices=[...]`. A Checkbox uses `action='store_true'`, so it is `False` unless ticked.

## The native settings dialog on a device

Studio does not embed the UI Builder canvas at run time. It reads `ui.xml` and renders one native form, with the same tabs and fields, in two places:

- In the **Code Editor**, when you click **Run** on a script that has arguments.
- On a device card in **Device Groups**, behind the **Macro settings** button.

On a device card the values you enter are saved for that device, and a **Profile** bar at the top of the dialog lets you share one set of values across many devices. See [Settings profiles](/docs/settings-profiles). If a macro has no `ui.xml`, Studio falls back to reading the argparse definitions in `script_args.py`.

## The runtime dashboard (.uibrt), driven from Python

A runtime dashboard is the live panel Studio shows while a macro runs. You design it in the UI Builder and drive it from your script with `mas.ui.*` calls. The dashboard appears in the Code Editor's bottom panel and in the **Dashboard** view of the device card in Device Groups.

The one rule that connects everything is the widget **name**, set in the Inspector. That name is the first argument to every `mas.ui.*` call. A typo between the name in the file and the name in your code is the most common dashboard bug: the call simply updates nothing. Make each name a valid Python identifier, unique within the dashboard, and descriptive (`status_label`, `items_chart`).

### The 7 runtime widgets

| Widget | Kind | Shows or does | Driven or read by |
|---|---|---|---|
| Label | `runtime-label` | one line of text with font size, color and alignment | `set_text` |
| Progress bar | `runtime-progress` | a value between `min` and `max`, optional percentage | `set_progress`, `set_text` for its label |
| Chart | `runtime-chart` | a line, bar or pie chart that keeps up to `maxDataPoints` | `add_data_point`, `set_chart_data`, `clear_chart` |
| Text area | `runtime-textarea` | scrolling text or a log, sans or mono, optional line numbers and `maxLines` | `append_text`, `set_textarea`, `clear_text` |
| Table | `runtime-table` | rows under fixed columns, optional `maxRows` | `set_table_data`, `append_table_row`, `clear_table`, `cell_button` |
| Button | `runtime-button` | a clickable button in the default, outline or destructive style | `wait_for_event`, `on_click` |
| Input | `runtime-input` | a text field the user types into | `get_input_value`, `on_change` |

The first five are display widgets: the script writes, the user reads. The last two are interactive: the user acts, the script reads.

### How updates reach the dashboard

Every `mas.ui.*` call is one JSON-RPC message from your script to the MAS app. The app forwards it as an event keyed by the device, and the dashboard re-renders that widget. Text areas and tables keep a local mirror in the SDK so `append_text` and `append_table_row` can build on the previous content without asking the app. Wrap a multi-widget refresh in `mas.ui.batch()` so it travels as a single message; that is the difference between a smooth dashboard and one that flickers inside a tight loop. Batches cannot be nested.

```python
import mas

steps = ["open_app", "collect", "claim"]
mas.ui.set_text("status", "Starting")

for i, step in enumerate(steps, start=1):
    mas.log(f"running {step}")
    with mas.ui.batch():
        mas.ui.set_text("status", f"Step {i} of {len(steps)}: {step}")
        mas.ui.set_progress("main_bar", i * 100 // len(steps))
        mas.ui.append_text("log", f"{step} done")
```

For interactive widgets, register handlers and start the listener once. The listener is a daemon thread, so the script still exits when your main function returns.

```python
import mas

paused = False

def toggle_pause():
    global paused
    paused = not paused
    mas.ui.set_text("status", "Paused" if paused else "Running")

mas.ui.on_click("pause_btn", toggle_pause)
mas.ui.start_listener()
```

`mas.ui.wait_for_event(timeout=30.0)` is the blocking alternative when you want the script to stop and wait for a click. A table can hold buttons too: build a cell with `mas.ui.cell_button(id=..., label=...)` and handle its `id` like any other button. The full method list, with signatures and defaults, is on [Runtime UI](/docs/sdk/ui).

## Checklists before you ship

Argument form:

- Every argument widget has a non-empty, identifier-safe, unique key.
- Required fields are marked required.
- Combo Box and Radio Group widgets have their options filled in.
- Number Input and Slider have sensible min and max values.
- You ran **Preview** and the form is legible.
- You clicked **Save** after the last change, so `ui.xml` and `script_args.py` are current.

Runtime dashboard:

- Every widget has a meaningful, identifier-safe, unique name.
- Every `mas.ui.*` call uses a name that exists in the `.uibrt` file.
- Table row keys match the table's columns.
- Multi-widget refreshes are wrapped in `mas.ui.batch()`.
- `start_listener()` is called once, after all handlers are registered.
- The macro still completes if the user never touches an interactive widget.

## Layout tips

- Work within 932 pixels. The default window is 980 pixels wide with 24 pixels of padding on each side.
- Stack top to bottom. Forms read as a vertical list. Use the auto-layout button to tidy a tab, and the snap grid to keep edges aligned.
- Group with intent. A Group Box or a captioned Separator does more for readability than pixel nudging.
- Preview often. **Preview** renders the real window and is the fastest way to catch a cramped layout.
- Keep tabs shallow. Two or three tabs is plenty; more usually means the macro is trying to do too much.
