External-pointer editing rail

State-managed boost widgets (slider_float, checkbox, combo, …) auto-emit a per-call global state struct that owns .value. That’s fine for new code, but doesn’t help when the data already lives in someone else’s struct, a daslang global, or a C-side configuration block.

The edit_* family takes a caller-owned T? pointer instead. The widget reads + writes through that pointer; a hidden internal mirror keeps the snapshot stable across frames.

Source: examples/tutorial/editing_external.das.

What the tour shows

Seven external-pointer widgets, each editing a var private global. The tour drives every one the way a person would — a real drag, type, click, or popup pick — so you can watch the bound global change in the readout, which is the whole point of the rail:

  • edit_slider_float(safe_addr(g_volume), (id = "VOL_SLIDER", text = "Volume", v_min, v_max)) — drag the handle.

  • edit_input_int(safe_addr(g_count), (id = "COUNT_INPUT", text = "Count", step = 1)) — click to focus, type a number, Enter.

  • edit_checkbox(safe_addr(g_enabled), (id = "ENABLED_CHECK", text = "Enabled")) — click.

  • edit_drag_float3(safe_addr(g_pos), (id = "POS_DRAG", ...)) — drag the first field; a Drag scrubs on click-drag (only a Ctrl+click would enter text), so the gesture is a drag, not a type.

  • edit_color_edit3(safe_addr(g_accent), (id = "ACCENT_COLOR", ...)) — drag the red channel; the swatch shifts toward red.

  • edit_combo(safe_addr(g_quality_idx), (id = "QUALITY_COMBO", items <- g_qualities)) — open the popup, click an item.

  • edit_slider_angle(safe_addr(g_angle), (id = "ANGLE_SLIDER", ...)) — drag the handle (the face shows degrees, the bound variable is radians).

Each widget’s read-only sibling under the // Readout separator shows the underlying var private value updating live with each edit.

safe_addr vs unsafe(addr)

safe_addr(local_or_global) is the right form for plain locals and globals. ImGui needs a stable pointer to T; safe_addr enforces that.

It rejects struct-field-via-reference lvalues at compile time – there the field’s lifetime is the reference’s, which the compiler can’t guarantee outlives the widget call. For those, fall back to unsafe(addr(struct.field)) and accept the responsibility for lifetime.

The id = "..." literal is REQUIRED on every edit_* call. It’s the path leaf in widget telemetry; missing it is a compile-time error from EditExternalCallMacro. No implicit :line:col fallback, because the edit_* family doesn’t auto-emit a state struct that could carry the path.

Driving the tour

Standalone (windowed):

daslang modules/dasImgui/examples/tutorial/editing_external.das

Live (with reload):

daslang-live modules/dasImgui/examples/tutorial/editing_external.das

The recorded tour above is captured by tests/integration/record_editing_external.das, which drives every widget with real synthetic input – a drag, a type, a click, a popup pick – and asserts each bound var private actually moved (the recording is also a test). Nothing is force_set: binding to your data is the subject, and the proof is that a real gesture writes through the pointer.

That said, an edit_* widget can also be driven externally without the gesture – imgui_force_set writes the bound pointer directly, imgui_focus + imgui_key_type script the keyboard. That path is the subject of Driving outside.

See also

Full source: examples/tutorial/editing_external.das

Recording driver: tests/integration/record_editing_external.das.

State telemetry — the snapshot path that edit_* widgets register under.

Driving outside — the imgui_force_set / imgui_mouse_play / imgui_key_type verbs for driving widgets programmatically.

Recording — the two-shell driver setup.