8.18.1.26. Driving from outside
Every boost widget that previous tutorials wrote registers a path in
the telemetry tree (DRIVE_WIN/USER, DRIVE_WIN/SPEED,
DRIVE_WIN/RUN_BTN …). That path is also an HTTP endpoint:
the boost layer ships [live_command] handlers (imgui_snapshot,
imgui_force_set, imgui_click, imgui_open, imgui_close,
imgui_focus) that look up the target in the registry. imgui_click
fires a real synthetic mouse click at the widget’s center;
imgui_force_set / imgui_open / imgui_close write the matching
field on the widget’s state struct directly. Either way ImGui sees the
effect as if a real input device — or an external editor — drove it.
This tutorial flips the point of view: instead of writing the daslang side, write the driver — a curl / Python / Bash script that issues JSON commands at a running daslang-live app. Every interaction the user could perform via mouse/keyboard has a curl equivalent, and the two surfaces use one in-memory model.
Source: modules/dasImgui/examples/tutorial/driving_outside.das — a small target app
exposing five widget kinds. The recording is driven entirely by JSON
commands: value writes and container toggles mutate state directly
(no mouse), while imgui_click fires a real synthetic click.
8.18.1.26.1. Walkthrough
1options gen2
2options _comment_hygiene = true
3
4require imgui
5require imgui_app
6require opengl/opengl_boost
7require live/glfw_live
8require live/live_api
9require live/live_commands
10require live/live_vars
11require live_host
12require imgui/imgui_live
13require imgui/imgui_boost_runtime
14require imgui/imgui_boost_v2
15require imgui/imgui_widgets_builtin
16require imgui/imgui_containers_builtin
17require imgui/imgui_visual_aids
18
19let PRESETS : array<string> <- ["Calm", "Mellow", "Active", "Frantic"]
20var PRESET_ROW : table<int; ClickState>
21
22// nolint:STYLE014 — file-header tutorial banner (curl driving recipes) displaced below decls
23// =============================================================================
24// TUTORIAL: driving_outside — every boost widget is also a JSON endpoint.
25//
26// Previous tutorials wrote the daslang side. This one is the inverse view:
27// what the JSON command surface looks like as a programming model in its
28// own right. Every widget the boost layer ships registers a path in the
29// telemetry tree; that path is also addressable from outside via the
30// [live_command] HTTP endpoints `imgui_snapshot` / `imgui_force_set` /
31// `imgui_click` / `imgui_open` / `imgui_close` / `imgui_focus`.
32//
33// The target app is small — slider + button + input + popup + combo — so
34// the driving recipes are easy to spot. Every interaction the user could
35// perform via mouse/keyboard has a curl equivalent.
36//
37// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/driving_outside.das
38// LIVE: daslang-live modules/dasImgui/examples/tutorial/driving_outside.das
39//
40// DRIVE (curl recipes — pair these with the RST walkthrough):
41//
42// # Snapshot the world (always the first read in any driver)
43// curl -X POST -d '{"name":"imgui_snapshot"}' localhost:9090/command
44//
45// # imgui_force_set — drive a slider value
46// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/SPEED","value":7}}' \
47// localhost:9090/command
48//
49// # imgui_click — fire a button
50// curl -X POST -d '{"name":"imgui_click","args":{"target":"DRIVE_WIN/RUN_BTN"}}' \
51// localhost:9090/command
52//
53// # imgui_force_set — string into a text input
54// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/USER","value":"Alice"}}' \
55// localhost:9090/command
56//
57// # imgui_force_set — combo by selected-index
58// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/PRESET","value":2}}' \
59// localhost:9090/command
60//
61// # imgui_open / imgui_close — popups, closable windows, tabs
62// curl -X POST -d '{"name":"imgui_open","args":{"target":"DRIVE_WIN/STATUS_POPUP"}}' \
63// localhost:9090/command
64// curl -X POST -d '{"name":"imgui_close","args":{"target":"DRIVE_WIN/STATUS_POPUP"}}' \
65// localhost:9090/command
66// =============================================================================
67
68[export]
69def init() {
70 live_create_window("dasImgui driving_outside tutorial", 880, 620)
71 live_imgui_init(live_window)
72 // Deterministic FirstUseEver layout for the recording: disable imgui.ini so a prior
73 // session's window pos/size can't drift the framing. Tutorial-scoped on purpose.
74 DisableIniPersistence()
75 let io & = unsafe(GetIO())
76 GetStyle().FontScaleMain = 1.5
77}
78
79[export]
80def update() {
81 if (!live_begin_frame()) return
82 begin_frame()
83
84 ImGui_ImplGlfw_NewFrame()
85 apply_synth_io_override()
86 NewFrame()
87
88 SetNextWindowPos(ImVec2(30.0f, 30.0f), ImGuiCond.FirstUseEver)
89 SetNextWindowSize(ImVec2(640.0f, 460.0f), ImGuiCond.FirstUseEver)
90 window(DRIVE_WIN, (text = "driving_outside", closable = false,
91 flags = ImGuiWindowFlags.None)) {
92
93 // ---- A text input — driven by `imgui_force_set` with a string value ----
94 input_text(USER, (text = "User"))
95 text("USER.value = \"{USER.value}\"")
96
97 separator(DR_SEP_1)
98
99 // ---- A slider — driven by `imgui_force_set` with a number value ----
100 slider_int(SPEED, (text = "Speed"))
101 text("SPEED.value = {SPEED.value}")
102
103 separator(DR_SEP_2)
104
105 // ---- A combo — driven by `imgui_force_set` with the selected index ----
106 var preset_label = "(none)"
107 if (PRESET.value >= 0 && PRESET.value < length(PRESETS)) {
108 preset_label = PRESETS[PRESET.value]
109 }
110 combo_select(PRESET, (text = "Preset",
111 preview_value = preset_label,
112 flags = ImGuiComboFlags.None)) {
113 for (i in range(length(PRESETS))) {
114 let is_sel = (i == PRESET.value)
115 if (selectable_label(PRESET_ROW[i], PRESETS[i], is_sel)) {
116 PRESET.value = i
117 }
118 }
119 }
120 text("PRESET = {preset_label} (idx {PRESET.value})")
121
122 separator(DR_SEP_3)
123
124 // ---- A button — fired by `imgui_click` ----
125 if (button(RUN_BTN, (text = "Run"))) {}
126 text("RUN_BTN.click_count = {RUN_BTN.click_count}")
127
128 separator(DR_SEP_4)
129
130 // ---- A popup — opened/closed via `imgui_open` / `imgui_close` ----
131 text("STATUS_POPUP - driven by imgui_open / imgui_close.")
132 popup(STATUS_POPUP, (text = "StatusPopup",
133 flags = ImGuiWindowFlags.None)) {
134 text("Driven from outside via imgui_open.")
135 separator(DR_SEP_5)
136 text("RUN_BTN.click_count = {RUN_BTN.click_count}")
137 }
138 }
139
140 end_of_frame()
141 Render()
142 var w, h : int
143 live_get_framebuffer_size(w, h)
144 glViewport(0, 0, w, h)
145 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
146 glClear(GL_COLOR_BUFFER_BIT)
147 live_imgui_render()
148
149 live_end_frame()
150}
151
152[export]
153def shutdown() {
154 live_imgui_shutdown()
155 live_destroy_window()
156}
157
158[export]
159def main() {
160 init()
161 while (!exit_requested()) {
162 update()
163 }
164 shutdown()
165}
8.18.1.26.1.1. The command surface
Two kinds of command — faithful input (does what a user does) and bypass (does what a user can’t):
Raw synth IO (faithful):
imgui_mouse_pos,imgui_mouse_button,imgui_mouse_play,imgui_key_press,imgui_key_type. The driver pretends to be a mouse or keyboard, feeding events into the ImGui input queue. Used byimgui_playwrightfor cursor-visible recordings.Click a widget by name (faithful):
imgui_click,imgui_focus.imgui_clickresolves the widget by path (or hex_id), warps to its center, and presses/releases through ImGui’s own input path — a real click, so the widget behaves exactly as a user click would (it errors if the target isn’t rendered this frame).imgui_focusforces keyboard focus. No trajectory to script, but the widget must be on screen.Write a value directly (bypass):
imgui_force_set. The framework looks up the widget and queuesstate.has_pending = true+state.pending_value = ...; the render function submits it next frame. Does what a click can’t — an exact value, an off-screen or inactive widget.
Plus the read side and the container channel:
imgui_snapshot— full registry as JSON, the first call in any driver.imgui_open/imgui_close— setstate.pending_open/state.pending_closeon container widgets (popups, closable windows, tabs, tree nodes).
Prefer imgui_click for clicks and imgui_force_set for values —
the first is a faithful click, the second a deterministic value write.
Drop to raw synth IO only when there’s no higher-level counterpart
(drag along a custom trajectory, paste a long string into a focused
input, sustain a chord, …).
8.18.1.26.1.2. imgui_snapshot — read the world
The first call every driver makes:
curl -X POST -d '{"name":"imgui_snapshot"}' localhost:9090/command
Response shape:
{
"frame": 412,
"globals": {
"DRIVE_WIN": {
"kind": "window",
"bbox": [30, 30, 670, 490],
"hex_id": "0x2c1a8f4b",
"payload": { "open": true, "size": [640, 460], ... }
},
"DRIVE_WIN/SPEED": {
"kind": "slider_int",
"bbox": [...],
"hex_id": "0x...",
"payload": { "value": 5, "bounds": [0, 10], ... }
},
"DRIVE_WIN/RUN_BTN": {
"kind": "button",
"bbox": [...],
"payload": { "click_count": 0 }
},
...
},
"io": {
"mouse_pos": [320, 180],
"active_widget": "..."
}
}
Use it to:
discover what’s on screen and what kind each widget is;
read
bboxfor L1 mouse synthesis (when needed);check
payloadfor current state (test assertions);read
hex_idfor fallback dispatch when the path isn’t stable.
8.18.1.26.1.3. imgui_force_set — value writes
imgui_force_set is the universal value-write endpoint — slider, checkbox,
combo, color, text input, dock-window position. Type-dispatched on the
value’s JSON shape:
# string into a text input
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/USER","value":"Alice"}}' \
localhost:9090/command
# int into a slider
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/SPEED","value":7}}' \
localhost:9090/command
# int into a combo (selected index)
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/PRESET","value":2}}' \
localhost:9090/command
# array-of-floats into a color picker
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/TINT","value":[0.2,0.7,0.4]}}' \
localhost:9090/command
Under the hood: the registered dispatcher for the widget’s state struct
unpacks the JSON, type-checks it against the state’s value field,
flips has_pending = true, stores pending_value. The render
function picks it up next frame; ImGui submits the new value through
its own UpdateValue path.
8.18.1.26.1.4. imgui_click — fire a click
imgui_click is a real synthetic mouse click: it resolves the
target to its on-screen bbox, warps the cursor to the center, and
presses then releases the button across one frame — through ImGui’s own
input path, so the widget can’t tell it apart from a hardware click:
curl -X POST -d '{"name":"imgui_click","args":{"target":"DRIVE_WIN/RUN_BTN"}}' \
localhost:9090/command
The button’s render function returns true, click_count
increments, and the daslang side sees both the inline if
(button(...)) and RUN_BTN.clicked / RUN_BTN.click_count as
expected. Pass "button": 1 for a right-click (context menus), 2
for middle. Because it’s a real click, the target must be rendered this
frame — clicking an unrendered widget returns an error (use
imgui_force_set to drive a widget that isn’t on screen).
8.18.1.26.1.5. imgui_open / imgui_close — containers
Containers expose an open-state channel through state.pending_open
and state.pending_close. imgui_open flips pending_open;
imgui_close flips pending_close. The next frame’s render
function applies the change:
curl -X POST -d '{"name":"imgui_open","args":{"target":"DRIVE_WIN/STATUS_POPUP"}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_close","args":{"target":"DRIVE_WIN/STATUS_POPUP"}}' \
localhost:9090/command
The same channel handles popups, closable windows, tabs (closable-tab
visibility specifically — see tutorial_containers for the tab-item
caveat), tree nodes, and collapsing headers.
8.18.1.26.1.6. The flow on a single command
Every command runs the same path:
daslang-liveHTTP server receivesPOST /command.Routes by
nameto the registered[live_command]handler.Handler looks up
targetin the registry’s path map (or hex_id reverse map).Either spawns a synthetic-input coroutine (
imgui_click— warp + press/release over a frame) or mutates the matching state struct’s pending field (imgui_force_set/imgui_open/imgui_close); returns{"ok": true, ...}on the HTTP response.Over the next frame(s) the script’s
update()runs; ImGui processes the synthetic input, or the render function applies the pending field, and the updated state is observable from the nextimgui_snapshot.
So commands settle over the next frame or two by design — there’s no
ambiguity about which frame’s state corresponds to a given response.
For test harnesses that need to read the result, the canonical pattern
is: command, then await_quiescent (waits a frame), then
imgui_snapshot.
8.18.1.26.1.7. Standalone vs live
The HTTP server only exists under daslang-live. Standalone
daslang.exe runs the same script but the live-command endpoints
aren’t bound — drive-from-outside scenarios require the live host.
8.18.1.26.1.8. Driving from outside (recap)
A complete drive sequence for this tutorial’s app:
# Read the world
curl -X POST -d '{"name":"imgui_snapshot"}' localhost:9090/command
# Write each widget kind
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/USER","value":"Alice"}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/SPEED","value":7}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/PRESET","value":2}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_click","args":{"target":"DRIVE_WIN/RUN_BTN"}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_open","args":{"target":"DRIVE_WIN/STATUS_POPUP"}}' \
localhost:9090/command
The recording at the top of this page runs this exact sequence — just
JSON commands. The value writes and container toggles flow through the
state-struct pending channel with no mouse motion; the imgui_click
is a real synthetic click at the button’s center.
8.18.1.26.1.9. Next steps
Now that the JSON-driven view is explicit, the visual aids tour
walks through every overlay the recordings used: highlight, mouse
trail, cursor sprite, narrate, key HUD, focus rect — all
[live_command]-wrapped so the same curl pattern reaches them.
See also
Full source: modules/dasImgui/examples/tutorial/driving_outside.das
Richer reference: modules/dasImgui/examples/features/io_synth_text.das —
imgui_key_type streams text as synthetic key + char events through the
key timeline; the synthetic keyboard layer in action.
Snapshot contract: imgui_boost_runtime.das’s
g_serializers per-kind payload definitions.
Previous tutorial: Live reload
Boost macros — the macro layer.