DOCUMENTATION

Driving the editor UI

UI driving lets an assistant read the Unreal Editor's own windows and widgets, and, only if you turn it on, click buttons, type into boxes and pick options. Read this if you want to know what it can reach, what it refuses, and how to switch it on or leave it off.

Off unless you turn it on

Reading the widget tree is always allowed. Acting on it is not. Clicking, typing, key presses, combobox selection and drags work only after a person ticks Allow Editor UI Driving under Project Settings, Plugins, Mif Bridge UI Driving. The setting is off by default. Even when it is on, the writes go through the safety gate and the call journal like any other write, and they are refused on controls that save, delete, quit, close the editor or discard work.

Your mouse is never moved. Where a widget has its own click function the bridge calls it directly. Otherwise it sends a pointer event to the widget's center inside the editor, and never repositions your cursor.

The seven endpoints

Endpoint Kind What it does
slate_snapshot read The visible windows (or one subtree) as a tree of nodes: ref, type, role, label, state flags, geometry, tag, command. Takes ref, maxDepth, maxNodes, windowsOnly
slate_find read Nodes matching text (contains, or exact), type, role, tag, command or path, anywhere or within a ref. Each result carries its path
slate_click write Click a widget: left (the default), right, or doubleClick
slate_type write Type text into a text box. Optionally clear it first, and submit with Enter
slate_press_key write Press one unmodified navigation or editing key on a widget
slate_select_option write Open a combobox and pick an option by its text
slate_drag write Drag one widget (fromRef) onto another (toRef)

The two reads work in every write mode. The five writes are refused in read mode and are subject to the setting above.

Other bridge calls already handled single cases: get_modal_dialog and answer_modal_dialog for dialogs, invoke_editor_command for bound commands, invoke_editor_tab for tabs and send_editor_key for a raw key. None of them could see or address an arbitrary widget. The slate_* endpoints can.

References

A ref looks like s12. It is assigned the first time a widget is reported and kept for that widget's whole life: the same widget always answers the same ref, across snapshots and finds, and the counter never resets. So a caller can snapshot once and act on refs later without reading again.

When the widget is destroyed, for example a menu closed or a panel rebuilt, its ref resolves to nothing and every endpoint refuses it by name with staleRef: true ("snapshot or find again"). A ref is never quietly re-pointed at a different widget with the same text, because a rebuilt list can put a different row under the same words.

Every node also carries a path: the window's title, then each meaningful ancestor's type and label, for example MyLevel > SDockTab "Content Browser" > SButton "Add". Use it for reading and for slate_find with path (a contains match). It is not an identity, because two buttons can share a path.

What is refused

A write is refused when any of these holds. The refusal names the rule (refusedRule), the widget, and the words or context that matched. Nothing is sent.

The control's own words

The bridge gathers the words on the target and on the control around it (the nearest button, check box, combo button, list row or menu entry): every text block inside it, its text tooltip, and for a menu or toolbar entry the bound command's name, label and description. Whole words match, ignoring case.

Category Words and command names
save save, saved, unsaved, save all, save as, don't save. Any command name containing Save. The status bar's "All Saved" button counts, since it opens the Save Content dialog
delete delete, force delete
quit or close quit, exit, close, restart
discard discard, revert, reload
play play, simulate, launch. Play-In-Editor takes the editor loop, which is why start_pie is gated
cook or package cook, package
source control submit, check in
disk moves move here, copy here, fix up, replace references, rename (in the Content Browser)

Where the control is

Location Why
a tab header itself (not its content) its only button is the tab's close button
a window's title bar the window's own minimize, maximize and close
Project Settings and Editor Preferences a change there is written to its .ini file at once, which is a save, and it is where this feature's own switch lives
the Plugins browser enabling a plugin writes the project file
the MifBridge panel it holds the write-mode control, and an assistant must not reach its own gate
the Content Browser's asset view and folder tree, for typing, Enter and drops a rename or a drop into a folder there goes through the engine's rename and move, which save. That is why rename_asset and move_folder are gated

Dialogs, keys and covered widgets

  • Modal dialogs are not this feature's. While a modal dialog is up, the bridge refuses every call except the dialog's own. slate_* cannot press a dialog's button. answer_modal_dialog does, under its own gate.
  • Keys. slate_press_key takes one key from a short list: Enter, Escape, Tab, BackSpace, the arrows, Home, End, PageUp, PageDown, SpaceBar. It takes no modifiers. The editor reads the real keyboard for Ctrl and Shift, so a chord such as Ctrl+S cannot be faked honestly, and Delete and the letter keys are not navigation. Enter and SpaceBar on a button are a click, so a key press is judged against the focused widget exactly as a click would be.
  • Covered widgets. A click lands on whatever is on top at that point, which need not be the widget named. Before a routed click is sent, the widget under that point is read. It must contain the target, and everything on that path is judged by the rules above. A covered target is refused as occluded.

What this cannot catch

A control with harmless words whose code saves, such as a third-party panel's "Apply" that writes a file, cannot be recognized by its words. That is why the feature is off by default and the setting's tooltip says so. The scratch watch still reports a real asset that the click dirtied, and the protected-root check still refuses, after the fact, a click that changed engine content, as for any other write.

How a click is sent and checked

Where a widget has its own function, the bridge calls it, so nothing depends on the window being on screen, focused or painted:

Widget Route
button its click function
check box toggles its checked state, which fires the same event a click does
combo button or combo box opens its menu
anything else, right-click, double-click, drags, a combobox option a pointer event routed to the widget's center

Typing focuses the box first and refuses unless the box then has focus, so characters never go to whatever else holds it. It sends one character event at a time, and an Enter after them when you pass submit.

Every write reads back in the same reply, in after:

  • the target's state: checked, text (for a text box it must equal what was typed, or the call answers ok:false, verified:false), enabled, and exists (a button that closed its own panel is gone, and says so);
  • the windows that opened and closed (windowsOpened[], windowsClosed[], by title), so a click that opened a menu or a dialog says so;
  • the keyboard focus afterward;
  • expectText and expectGone: optional text that must appear in, or disappear from, the visible tree after the action. When you give them, verified is their result and a miss is ok:false.

delivered says the event was sent. verified says what was read back. They are never merged into one ok.

Dialogs raised by a click. The call runs under the bridge's unattended mode, so the engine's ordinary message boxes answer their default instead of showing. A custom modal window raised by third-party code would park the call until someone answers it. The reply of the next call names that hazard.

Asking first: dryRun

Each write takes dryRun: true. It resolves the ref, runs every refusal rule, and reports what would be sent without sending anything. It is the way to ask "would this be refused?". The dry run answers in every write mode, including read.

How the setting is protected

self_audit reports the switch as editorUIDriving: {allowed, settingInMemory, settingOnDisk, configFile, section, key}. A write is allowed only when both the setting in memory and the line in Config/DefaultEditor.ini on disk say true. Either alone is not enough, because each has a road an assistant could try: the settings object has a name another call could address, and the config file is something set_property with saveConfig can write in full mode.

On top of that, every bridge call that names the settings class or the property, in any string of its parameters at any depth, including inside a batch op, is refused in every write mode with refusedRule: "self-unlock". The Project Settings window is a refused context, so the switch cannot be clicked either. A refusal names the setting and where to tick it.

This mirrors the write mode's own rule. No endpoint, console variable or UI command can change the mode, so an assistant cannot unlock its own gate.

How it composes with other safety features

  • Gate. The two reads pass in every mode. The five writes pass in scratch and full like any write, are refused in read mode, and get the scratch watch's dirtiedRealPackages report and the protected-root check. They are not on the gate's by-name refusal list, because the refused-control rules and the setting do that job. send_editor_key and invoke_editor_command stay on that list: they take a key or a command name with no target to judge.
  • Journal. Every call is journaled before it runs, so a click that kills the editor shows as a start with no end. The panel's Activity tab shows each one.
  • Undo. The writes are self-managed, like invoke_editor_command: a click runs code the bridge did not write, which may open its own transaction or compile, so the bridge opens none around it. What a click did is undone the way you would undo it by hand.
  • batch. Refused. Firing UI events inside batch's single open transaction is the hazard above.
  • Change cap and previews. Not applicable. Each call is one action whose effect is unknown until it runs.

When a call is refused

Failure Answer
stale or unknown ref refused with staleRef, nothing sent
widget disabled refused with disabled
widget hidden, collapsed or zero size refused for routed events (notOnScreen). The direct routes still work
target covered by another widget refused with occluded, naming what is on top
text box never takes focus refused with focusRefused, nothing typed
typed text does not read back ok:false, verified:false, textAfter
combobox option not found ok:false, the options that were found, and the dropdown is closed again
a modal dialog is up refused by the modal guard before the handler runs
no Slate (a commandlet without a UI) refused