8.18.1.30. Recording tutorial videos
Every tutorial recording is produced the same way: a driver script
uses with_recording_app to spawn a tiny subject app as
daslang-live, toggles visual aids, calls record_start,
narrates each interaction, performs the action, and lets the helper
shut down. The recorder writes .apng; a single ffmpeg pass
converts each to .mp4 (H.264) before the file ships in-tree.
This is the meta tutorial — the driver script is the artifact, and the
embedded video demonstrates itself.
The helper passes --imgui-content-scale=1.0 +
--no-hdpi-framebuffer to the spawned daslang-live so
APNGs stay at logical 1x — small files, fast encode, even on retina.
Tutorials run by users directly (no driver) keep their native HDPI
look.
Source files:
modules/dasImgui/examples/tutorial/recording.das— the subject: a one-slider one-button window. Tiny on purpose so every frame in the APNG corresponds to one specific driver call.modules/dasImgui/tests/record_recording.das— the driver. Walked through below.
8.18.1.30.1. The subject
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
19// =============================================================================
20// TUTORIAL: recording — the meta tutorial.
21//
22// Every previous tutorial's APNG was produced by the same workflow:
23//
24// daslang runs a DRIVER script that:
25// - uses with_recording_app to SPAWN this SUBJECT app as daslang-live
26// - the helper auto-toggles imgui_mouse_trail + imgui_cursor_sprite
27// - the helper auto-bracketts record_start / record_stop
28// - the body posts imgui_narrate + cursor moves + clicks / drags
29//
30// The DRIVER is the artifact that teaches recording — the subject is
31// just whatever app you want to record. This file is intentionally tiny
32// (one slider, one button) so the recording recipe is obvious: every
33// frame of the APNG corresponds to one specific driver-body call.
34//
35// The driver script for this tutorial is:
36// tests/integration/record_recording.das
37//
38// The helper passes --imgui-content-scale=1.0 + --no-hdpi-framebuffer
39// to the spawned daslang-live so APNG capture stays at logical 1x.
40// When you run this tutorial DIRECTLY (no driver), no flags are passed,
41// so retina users get native HDPI scaling.
42//
43// Read the driver alongside the walkthrough in the RST companion — the
44// .das + driver + APNG triple is the tutorial.
45//
46// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/recording.das
47// LIVE: daslang-live modules/dasImgui/examples/tutorial/recording.das
48//
49// DRIVE: see tests/integration/record_recording.das
50// =============================================================================
51
52[export]
53def init() {
54 live_create_window("dasImgui recording tutorial", 940, 560)
55 live_imgui_init(live_window)
56 DisableIniPersistence()
57 let io & = unsafe(GetIO())
58 GetStyle().FontScaleMain = 1.5
59}
60
61[export]
62def update() {
63 if (!live_begin_frame()) return
64 begin_frame()
65
66 ImGui_ImplGlfw_NewFrame()
67 apply_synth_io_override()
68 NewFrame()
69
70 SetNextWindowPos(ImVec2(30.0f, 30.0f), ImGuiCond.FirstUseEver)
71 SetNextWindowSize(ImVec2(520.0f, 280.0f), ImGuiCond.FirstUseEver)
72 window(REC_WIN, (text = "subject", closable = false,
73 flags = ImGuiWindowFlags.None)) {
74 text("Tiny subject - driver narrates what's happening.")
75 slider_float(VOLUME, (text = "Volume"))
76 if (button(SAVE_BTN, (text = "Save"))) {}
77 text("SAVE_BTN.click_count = {SAVE_BTN.click_count}")
78 }
79
80 end_of_frame()
81 Render()
82 var w, h : int
83 live_get_framebuffer_size(w, h)
84 glViewport(0, 0, w, h)
85 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
86 glClear(GL_COLOR_BUFFER_BIT)
87 live_imgui_render()
88
89 live_end_frame()
90}
91
92[export]
93def shutdown() {
94 live_imgui_shutdown()
95 live_destroy_window()
96}
97
98[export]
99def main() {
100 init()
101 while (!exit_requested()) {
102 update()
103 }
104 shutdown()
105}
The subject is a normal dasImgui live-reload app — same shape as every other tutorial’s subject. The recorder doesn’t touch the subject at all; it operates entirely through the live-command HTTP surface.
8.18.1.30.2. The driver
The video below is produced by the driver listed under it — the meta loop closes
on itself. It drags Volume (asserting the value moved) and clicks Save
(asserting the click landed), narrating the recipe as it goes; a silently-broken
beat would abort the recording instead of shipping.
1options gen2
2options _comment_hygiene = true
3options indenting = 4
4options no_unused_block_arguments = false
5options no_unused_function_arguments = false
6
7require imgui public
8require imgui/imgui_playwright public
9require daslib/json public
10require daslib/json_boost public
11
12//! Driver: record ``recording.apng`` against ``examples/tutorial/recording.das``.
13//! This is the META tutorial - the driver IS the artifact that teaches recording.
14//! It demonstrates the recipe every other recording uses (narrate caption + cursor
15//! trail + scripted gesture + self-verify) on a tiny subject, and verifies each:
16//! 1. scripted drag - VOLUME.value must CHANGE (drag_through_voice).
17//! 2. scripted click - SAVE_BTN must register (hold_through_voice).
18
19def widget_bbox(var snap : JsonValue?; ident : string) : float4 {
20 var b = snap?["globals"]?[ident]?["bbox"]
21 return float4(b?["x"] ?? 0.0f, b?["y"] ?? 0.0f, b?["z"] ?? 0.0f, b?["w"] ?? 0.0f)
22}
23
24def widget_center(var snap : JsonValue?; ident : string) : tuple<float; float> {
25 let b = widget_bbox(snap, ident)
26 return ((b.x + b.z) * 0.5f, (b.y + b.w) * 0.5f)
27}
28
29[export]
30def main {
31 with_recording_app("examples/tutorial/recording.das",
32 "recording.apng", 35) $(app) {
33 let T_VOLUME = "REC_WIN/VOLUME"
34 let T_SAVE = "REC_WIN/SAVE_BTN"
35
36 var snap = wait_for_render(app, T_SAVE, 10.0f)
37 if (snap == null) {
38 panic("{T_SAVE} never rendered - wrong app running?")
39 }
40 let p_save = widget_center(snap, T_SAVE)
41 let vol_bb = widget_bbox(snap, T_VOLUME)
42 let vol_y = (vol_bb.y + vol_bb.w) * 0.5f
43 let vol_x0 = vol_bb.x + (vol_bb.z - vol_bb.x) * 0.15f
44 let vol_x1 = vol_bb.x + (vol_bb.z - vol_bb.x) * 0.80f
45
46 // Park the cursor at the slider's left and warm up the capture pipeline.
47 move_to(app, (vol_x0, vol_y), 800)
48 hold_content(app, 1800u)
49
50 // ---- Beat 1: the recipe - narrate, trail, scripted drag ----
51 drag_through_voice(app, say_begin(app, "narrate + scripted drag", T_VOLUME,
52 [voice = "This is the recording recipe itself. A driver shows a caption box, moves the cursor along a trail, and scripts a real drag - here the Volume slider, and the snapshot confirms the value moved."]),
53 T_VOLUME, (vol_x1, vol_y))
54
55 // ---- Beat 2: scripted click + self-verify ----
56 move_to(app, p_save, 600)
57 hold_content(app, SETTLE_MS)
58 hold_through_voice(app, say_begin(app, "scripted click + verify", T_SAVE,
59 [voice = "Then it scripts a click and asserts the effect landed. Every recording you have seen is this recipe - narrate, move, act, verify - so a demo that quietly broke aborts instead of shipping."]),
60 [T_SAVE])
61 }
62}
8.18.1.30.2.1. Anatomy of a driver
The driver’s main is a single with_recording_app(...)
$(app) { ... } call. The helper owns the boilerplate; the body owns
the timeline.
Helper-owned: spawn + ready-wait.
with_recording_appspawnsdaslang-live <feature_path>with--imgui-content-scale=1.0+--no-hdpi-framebuffer(and forwards-project_rootfrom the driver’s own argv), waits for the HTTP server to answer/status200, and yields anImguiAppto the body block.Body-owned: resolve widget centers. Inside the body,
wait_for_renderpollsimgui_snapshotuntil the named widget shows up in the registry (covers the first-frame gap where the subject’s window exists but hasn’t rendered yet). The helperwidget_bbox/widget_centerextract the geometry from the snapshot JSON.Helper-owned: enable visual aids + ``record_start``. The helper posts
imgui_mouse_trail+imgui_cursor_sprite(enabled=true)and thenrecord_startwith the(file, fps, max_seconds)you passed in. The writer pulls from a PBO ring on the GL side (4 buffers by default) —glReadPixelsreturns immediately, the actual readback happens 3 frames later, the encoder runs on a worker thread. Frames drop only if the worker can’t keep up with the GL output rate, and even then dropping is graceful (the APNG just gets slightly choppier — never breaks).The absolute APNG path is
<dasimgui>/doc/source/_static/tutorials/<basename>, resolved viaget_this_module_dir()so caller cwd is irrelevant.Body-owned: narrate, act, verify — repeat. Each beat pairs an ear-first caption with a real gesture and a self-check:
say_begin(app, caption, target, [voice = "..."])posts the caption box and returns the voiceover’s length, so the beat is paced by the spoken line — no hand-tuned frame counts. The terse on-screencaptionis decoupled from the natural spokenvoice(which keys the TTS manifest). Both must be ASCII — the bundled ImGui font has no em-dash / arrow / smart-quote glyph, so non-ASCII renders as tofu.The gesture runs UNDER the voice and is VERIFIED:
drag_through_voicescrubs a slider and asserts the value changed;hold_through_voiceclicks and asserts the effect registered;force_set_verifieddrives a value from outside and asserts it took. A no-op accumulates a miss and the recording aborts at teardown (g_record_failurespanic) — a silently broken demo never ships. The driver below is exactly this shape.
Helper-owned: ``record_stop`` + shutdown. When the body returns, the helper posts
record_stop(flushes the writer, joins the encoder thread, prints(saved, frames, duration_s, dropped, ok)), disables the visual aids, posts/shutdown, drains stdout. The driver process exits when daslang-live does.
8.18.1.30.2.2. The visual-aids stack
Four [live_command] toggles in imgui_visual_aids.das:
imgui_mouse_trail— fading line behindio.MousePos. Args:enabled,fade(seconds),color(RGBA uint).imgui_cursor_sprite— visible pointer drawn atio.MousePos. Without it the cursor only exists in the OS layer, which screen recordings don’t capture.imgui_narrate— a sticky-note callout with optional connector line to a target widget. Auto-fits to avoid sibling overlap;framescontrols visibility duration.imgui_highlight— a colored rectangle around a widget’s bbox for N frames. Useful for “look here” without text.imgui_auto_highlightflips a global flag that fires highlight on every accepted live command — a one-shot debug aid.
Two more for keyboard work:
imgui_key_hud— bottom-center keycap overlay + modifier strip. Pops a keycap for every synth key event; lights modifier pills while held. Useful for input-heavy demos.imgui_focus_rect— a rectangle aroundio.active_widgetso the viewer can tell which widget keystrokes will land in.
8.18.1.30.2.3. The driver workflow in shell
One shell, one command:
bin/Release/daslang.exe -project_root <dasimgui> \
<dasimgui>/modules/dasImgui/tests/record_recording.das
The helper spawns daslang-live, runs the body, posts /shutdown,
drains stdout. Wall time = max_seconds + ~3s headroom. The APNG
lands at <dasimgui>/doc/source/_static/tutorials/recording.apng,
which is gitignored — it’s the raw artifact, not the deliverable.
After the recorder finishes, one ffmpeg pass converts to the shipped
.mp4:
ffmpeg -y -i recording.apng -c:v libx264 -crf 23 -pix_fmt yuv420p \
-movflags +faststart recording.mp4
Typical UI recording: 50-300 KB MP4 (vs 50-100 MB APNG, ~300x smaller).
The .mp4 is what RSTs reference; it ships on the rolling docs-assets GitHub release, not in git.
If you want to iterate without re-recording the host’s state, drive a
manually-launched host via mcp__daslang__live_command instead
(see “Verifying a recording” in skills/imgui_recording.md). The
driver script is for the canonical artifact pass.
8.18.1.30.2.4. Stop conditions
record_stop is the clean exit. Three other ways the writer
finalizes:
max_secondsexpires — same APNG, frame count + duration match the cap.stbi_apng_framereturns an error (rare; usually disk full or permission denied) — writer auto-stops, returns the partial APNG.daslang-live shuts down — the
[on_app_exit]hook on the recorder finalizes any in-flight ring before tearing down GL state.
In practice record_stop is the only exit you’ll see; the others
are safety nets.
8.18.1.30.2.5. Replay stability
The same driver run against the same subject produces APNGs that look
the same to a viewer — but they’re not byte-identical. ImGui’s frame
times jitter, vsync alignment shifts, the PBO ring may stall once or
twice on encoder backpressure. dropped in the response is the
useful metric: anything under capture_pbo_count + a few is fine.
Higher means the encoder couldn’t keep up — try lowering the subject’s
render rate, simplifying the subject, or raising capture_pbo_count.
8.18.1.30.2.6. Standalone vs live
The whole recording surface — visual aids, record_start /
record_stop, the playwright helpers — runs only under
daslang-live. Standalone daslang.exe runs the subject, but
the HTTP-bound live commands aren’t registered. The driver script
itself is a normal daslang.exe script — it just talks HTTP to a
host running on another process.
8.18.1.30.2.7. Driving from outside
The same JSON commands the driver issues are reachable from curl:
curl -X POST -d '{"name":"imgui_mouse_trail","args":{"enabled":true}}' localhost:9090/command
curl -X POST -d '{"name":"imgui_cursor_sprite","args":{"enabled":true}}' localhost:9090/command
curl -X POST -d '{"name":"record_start","args":{"file":"manual.apng","fps":30,"max_seconds":15}}' \
localhost:9090/command
# do whatever interactions interactively in the live window ...
curl -X POST -d '{"name":"record_stop"}' localhost:9090/command
Use this for ad-hoc captures when the scripted driver would be overkill — bug repros, “show me what this looks like” pings.
8.18.1.30.2.8. The end of the curriculum
The 12 tutorials in this set walk the dasImgui surface end-to-end:
basics, widgets, layout, docking, style, identity, state, containers,
live reload, the JSON-driven view, the visual aids tour, and this
recording recipe. Beyond the curriculum, every modules/dasImgui/examples/features/
demo is a richer reference for one specific surface — those are the
go-to once the tutorials are familiar.
See also
Subject source: modules/dasImgui/examples/tutorial/recording.das
Driver source: modules/dasImgui/tests/record_recording.das
Recorder implementation: modules/dasOpenGL/opengl/opengl_live.das
(record_start / record_stop plus the PBO ring).
Visual aids: modules/dasImgui/widgets/imgui_visual_aids.das.
Playwright helpers: modules/dasImgui/widgets/imgui_playwright.das.
Previous tutorial: Visual aids tour
Curriculum top: dasImgui tutorials