1.2. Boost runtime — widget state structs, registry, and dispatcher

Runtime support layer for the [widget] / [container] macros: the per-kind state structs, the per-frame widget registry, the imgui_* live-command dispatcher, and the AwaitModifiers / Result framing that lets clients drive ImGui from outside the GLFW main thread. Widget state structs mix @live fields (preserved across hot-reload) and @optional fields (transient, dropped from JV when zero-valued). Common per-frame ImGui state — hex_id, bbox, hover / active / focus — lives in WidgetEntry, not the per-kind structs, so each state struct stays small.

The live-command surface (imgui_snapshot, imgui_click, imgui_force_set, imgui_open, imgui_close, imgui_focus, imgui_await) is the wire-protocol that imgui_playwright and external test harnesses drive. (Multi-frame drag/type live in imgui_live_core’s imgui_mouse_play / imgui_key_type timelines, which playwright composes.) Commands resolve targets via the registry’s path key (e.g. MAIN_WIN/SETTINGS_TAB/RPS) or raw 0x<hex_id>; results carry an AwaitResult whose await_* modifiers tell the client to wait for quiescence or a specific frame number before reading the response.

1.2.1. Type aliases

KindDispatcher = function<(state_addr:void? const;action:string const;payload:string const ):void>

typedef KindDispatcher = function<(state_addr:void? const;action:string const;payload:string const):void> aka KindDispatcher

1.2.2. Structures

ClickState
Fields:
  • click_count : int - cumulative across reloads

  • clicked : bool - true on the frame the button fired

SliderStateFloat
Fields:
  • value : float - preserved across reload

  • bounds : tuple<float;float> - (min, max)

  • has_pending : bool - imgui_force_set L3 sets; render fn consumes

  • pending_value : float - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

SliderStateFloat2
Fields:
  • value : float2 - preserved across reload

  • bounds : tuple<float;float> - (min, max) applied to every component

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float2 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

SliderStateFloat3
Fields:
  • value : float3 - preserved across reload

  • bounds : tuple<float;float> - (min, max) applied to every component

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float3 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

SliderStateFloat4
Fields:
  • value : float4 - preserved across reload

  • bounds : tuple<float;float> - (min, max) applied to every component

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float4 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

SliderStateInt
Fields:
  • value : int - preserved across reload

  • bounds : tuple<int;int> - (min, max)

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

SliderStateInt2
Fields:
  • value : int2 - preserved across reload

  • bounds : tuple<int;int> - (min, max) applied to every component

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int2 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

SliderStateInt3
Fields:
  • value : int3 - preserved across reload

  • bounds : tuple<int;int> - (min, max) applied to every component

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int3 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

SliderStateInt4
Fields:
  • value : int4 - preserved across reload

  • bounds : tuple<int;int> - (min, max) applied to every component

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int4 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

ToggleState

Shared across Checkbox / RadioButton (bool form) / Selectable / MenuItem. pending_toggle + pending_value let imgui_force_set steer the next frame’s value deterministically; the renderer consumes-then-clears.

Fields:
  • value : bool - current toggle state; preserved across reload

  • pending_toggle : bool - imgui_force_set L3 sets; consumed next frame

  • pending_value : bool - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

RadioIntState

Backs RadioButton(int form). value is the currently-selected button id; the widget def takes the per-call v_button and writes value=v_button when the user clicks. imgui_force_set {“value”:N} steers the selection.

Fields:
  • value : int - currently-selected button id

  • pending_set : bool - imgui_force_set queued; consumed next frame

  • pending_value : int - next-frame selection queued by imgui_force_set

  • changed : bool - true on the frame the selection changed

DragStateFloat
Fields:
  • value : float - preserved across reload

  • bounds : tuple<float;float> - (min, max); zero-init = unclamped

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

DragStateFloat2
Fields:
  • value : float2 - preserved across reload

  • bounds : tuple<float;float> - (min, max); zero-init = unclamped

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float2 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

DragStateFloat3
Fields:
  • value : float3 - preserved across reload

  • bounds : tuple<float;float> - (min, max); zero-init = unclamped

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float3 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

DragStateFloat4
Fields:
  • value : float4 - preserved across reload

  • bounds : tuple<float;float> - (min, max); zero-init = unclamped

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float4 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

DragStateInt
Fields:
  • value : int - preserved across reload

  • bounds : tuple<int;int> - (min, max); zero-init = unclamped

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

DragStateInt2
Fields:
  • value : int2 - preserved across reload

  • bounds : tuple<int;int> - (min, max); zero-init = unclamped

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int2 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

DragStateInt3
Fields:
  • value : int3 - preserved across reload

  • bounds : tuple<int;int> - (min, max); zero-init = unclamped

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int3 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

DragStateInt4
Fields:
  • value : int4 - preserved across reload

  • bounds : tuple<int;int> - (min, max); zero-init = unclamped

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int4 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

DragStateRangeFloat

value.x = current_min, value.y = current_max. Bounds clamp both. imgui_force_set {“value”:[lo,hi]} writes the whole range atomically.

Fields:
  • value : float2 - (current_min, current_max); preserved across reload

  • bounds : tuple<float;float> - (min, max) clamp applied to both endpoints

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float2 - next-frame range queued by imgui_force_set

  • changed : bool - true on the frame the range changed

DragStateRangeInt

value.x = current_min, value.y = current_max.

Fields:
  • value : int2 - (current_min, current_max); preserved across reload

  • bounds : tuple<int;int> - (min, max) clamp applied to both endpoints

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int2 - next-frame range queued by imgui_force_set

  • changed : bool - true on the frame the range changed

InputStateFloat
Fields:
  • value : float - preserved across reload

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

InputStateFloat2
Fields:
  • value : float2 - preserved across reload

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float2 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

InputStateFloat3
Fields:
  • value : float3 - preserved across reload

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float3 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

InputStateFloat4
Fields:
  • value : float4 - preserved across reload

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float4 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

InputStateInt
Fields:
  • value : int - preserved across reload

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

InputStateInt2
Fields:
  • value : int2 - preserved across reload

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int2 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

InputStateInt3
Fields:
  • value : int3 - preserved across reload

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int3 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

InputStateInt4
Fields:
  • value : int4 - preserved across reload

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int4 - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

InputStateDouble
Fields:
  • value : double - preserved across reload

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : double - next-frame value queued by imgui_force_set

  • changed : bool - true on the frame the widget fired

ColorState3
Fields:
  • value : float3 - RGB in [0..1]

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float3 - next-frame RGB queued by imgui_force_set

  • changed : bool - true on the frame the color changed

ColorState4
Fields:
  • value : float4 - RGBA in [0..1]

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : float4 - next-frame RGBA queued by imgui_force_set

  • changed : bool - true on the frame the color changed

ComboState
Fields:
  • value : int = -1 - selected index, -1 = none

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int - next-frame selection queued by imgui_force_set

  • changed : bool - true on the frame the selection changed

  • items_buf : array<uint8> - transient zero-separated buffer; rebuilt each frame

ListBoxState
Fields:
  • value : int = -1 - selected index, -1 = none

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int - next-frame selection queued by imgui_force_set

  • changed : bool - true on the frame the selection changed

InputTextState
Fields:
  • value : string = “” - current text content (clone of buffer)

  • capacity : int = 256 - working buffer size in bytes (incl. trailing null)

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : string = “” - next-frame text queued by imgui_force_set

  • changed : bool - true on the frame the text changed

  • buffer : array<uint8> - working buffer; mirrors value, allocated to capacity on first frame / post-reload

TextShowState

Backs the text_show display widget (master plan §4.3a). Read-only from the user’s perspective — value is set either by an initializer at the call site (text_show(STATUS, value=”saved”)) or programmatically (STATUS.value = compute_status()). The standard serializer surfaces value for telemetry / playwright expect_value(app, STATUS, “saved”). Tests can also drive the value externally via imgui_force_set — the dispatcher writes state.value directly.

Fields:
  • value : string = “” - displayed text; settable via call-site initializer or imgui_force_set

PlotState

Backs the plot_lines / plot_histogram display widgets. The plotted values are passed per-frame as a parameter (not stored in state) — same shape as the imgui_test_engine and daslib v1 conventions. State carries last-frame’s call args for telemetry: the visible label, the (scale_min, scale_max) range, and the sample count. Read-only — no dispatcher.

Fields:
  • title : string - overlay text

  • bounds : tuple<float;float> - (scale_min, scale_max)

  • samples : int - length(values) last frame

NarrativeState

Backs every text-bearing narrative widget — text / text_unformatted / text_wrapped / text_colored / text_disabled / bullet_text / separator_text / set_tooltip / set_item_tooltip. The displayed string is per-call (the call site is the source of truth) and echoed into value every frame for telemetry. expect_value(MSG, “literal”) works uniformly across the family. Read-only — no dispatcher.

Fields:
  • value : string - per-frame echo of call-site text arg

LabelTextState

Backs the label_text display widget. ImGui’s LabelText(label, fmt) displays the label-key on the left and the value-text on the right; both strings are per-call and echoed for telemetry. Read-only — no dispatcher. (Field is key not labellabel is reserved in gen2.)

Fields:
  • key : string - per-frame echo of the left-side label string

  • value : string - per-frame echo of the right-side value string

EmptyMarkerState

Backs the stateless decoration widgets — separator, spacing, new_line, dummy, same_line, bullet. The state global exists only because [widget] auto-emit needs something to instantiate; mirrors GroupState / MenuBarState / TooltipState shape.

Fields:
  • _placeholder : bool - unused; keeps the struct non-empty for [widget] auto-emit

ImageState

Backs the image display widget. Texture handle is opaque (void?), so telemetry echoes only the actionable per-call args — size, uv rectangle, tint/border colors. Read-only — no dispatcher.

Fields:
  • size : float2 - per-call display size

  • uv0 : float2 - upper-left uv

  • uv1 : float2 - lower-right uv

  • tint_col : float4 - tint applied to texels

  • border_col : float4 - border color (zero alpha = no border)

ProgressBarState

Backs the progress_bar display widget. fraction is per-frame per-call (the caller drives progress); overlay is the optional center-aligned label; size is the per-call display size. Read-only — no dispatcher.

Fields:
  • fraction : float - per-frame progress in [0,1]; -1 means indeterminate

  • overlay : string - optional overlay text (e.g. “42/100”)

  • size : float2 - per-call display size; (-1, 0) defaults to full-width

WindowState

Backs the window container. open defaults to true so a closable window renders from frame 1; the X-button or imgui_close flips it false. @live carries that across reload. Everything else is per- frame observed (pos/size/collapsed/scroll) or echoed from per-call args (flags). ImGui persists window pos itself via imgui.ini, so duplicating it as @live would only fight that. imgui_open / imgui_close flow through pending_open/close; the renderer applies them at the top of the next frame. Per-call config (closable, flags) is supplied via the named-tuple at the call site — same convention as [widget].

Fields:
  • open : bool = true - window open state; preserved across reload

  • flags : ImGuiWindowFlags - per-call ImGuiWindowFlags (echoed for telemetry)

  • pos : float2 - observed window position (top-left, screen coords)

  • size : float2 - observed window size

  • collapsed : bool - observed collapsed state

  • scroll : float2 - (GetScrollX, GetScrollY) of the window

  • scroll_max : float2 - (GetScrollMaxX, GetScrollMaxY)

  • pending_open : bool - imgui_open queued; applied next frame

  • pending_close : bool - imgui_close queued; applied next frame

  • pending_size : float2 - imgui_set_window_size queued — applied via SetNextWindowSize next frame

  • pending_size_set : bool - gates pending_size (so (0,0) is a valid request, not a sentinel)

  • pending_scroll : float2 - imgui_click scroll-into-view queued — applied via SetNextWindowScroll next frame

  • pending_scroll_set : bool - gates pending_scroll (so (0,0) is a valid request, not a sentinel)

ChildState

Backs the child container. All fields are observed (scroll); per- call config (size, child_flags, window_flags) is supplied via the named-tuple at the call site.

Fields:
  • size : float2 - per-call child region size (echoed for telemetry)

  • child_flags : ImGuiChildFlags - per-call ImGuiChildFlags

  • window_flags : ImGuiWindowFlags - per-call ImGuiWindowFlags

  • scroll : float2 - (GetScrollX, GetScrollY)

  • scroll_max : float2 - (GetScrollMaxX, GetScrollMaxY)

GroupState

Backs the group container. Pure layout grouping — no persistent or observable state, but the [container] macro still needs a struct to hang the auto-emitted global on. The _placeholder field stays @optional so JV(state) emits {} when the group is rendered.

Fields:
  • _placeholder : bool - unused; keeps the struct non-empty for [container] auto-emit

MenuBarState

Backs the menu_bar container. Stateless wrapper.

Fields:
  • _placeholder : bool - unused; keeps the struct non-empty for [container] auto-emit

MenuState

Backs the menu container — a single drop-down inside a menu bar. enabled is per-call; opened is per-frame observed.

Fields:
  • enabled : bool - per-call enable flag (echoed for telemetry)

  • opened : bool - observed per-frame: menu is currently expanded

PopupState

Shared by popup and popup_modal. open is @live so the user-driven state survives reload. imgui_open / imgui_close route through pending_open/close; the renderer issues OpenPopup/CloseCurrentPopup on the next frame in response.

Fields:
  • open : bool - popup open state; preserved across reload

  • flags : ImGuiWindowFlags - per-call ImGuiWindowFlags

  • pending_open : bool - imgui_open queued; OpenPopup fires next frame

  • pending_close : bool - imgui_close queued; CloseCurrentPopup fires next frame

TabBarState

Backs the tab_bar container. ImGui drives active-tab selection itself; we just observe.

Fields:
TableState

Backs the table container. ImGui owns column-resize/sort state internally (TableSettings). Per-call config is echoed for snapshot consumers; sort_specs / multi-select capture is deferred to the tables.das port.

Fields:
  • columns : int - per-call column count

  • flags : ImGuiTableFlags - per-call ImGuiTableFlags

  • outer_size : float2 - per-call outer_size (echoed)

  • inner_width : float - per-call inner_width (echoed)

TabItemState

Backs the tab_item container. open defaults to true so a closable tab renders from frame 1; the X-button or imgui_close flips it false. @live carries that across reload.

Fields:
  • open : bool = true - tab open state; preserved across reload

  • flags : ImGuiTabItemFlags - per-call ImGuiTabItemFlags

  • pending_open : bool - imgui_open queued; applied next frame

  • pending_close : bool - imgui_close queued; applied next frame

  • pending_select : bool - imgui_select queued; applied next frame (SetSelected)

  • selected : bool - observed: is this the active tab this frame (snapshot-visible)

WrapTabBarState

Backs the wrap_tab_bar container (the multi-row / wrapping tab bar ImGui lacks). Active tab is an index into the tab list, like combo: imgui_force_set {“value”:N} selects tab N next frame. labels mirrors the tabs seen last frame (the header strip is laid out a frame behind so it can precede the bodies).

Fields:
  • value : int - active tab index; preserved across reload

  • has_pending : bool - imgui_force_set queued; consumed next frame

  • pending_value : int - next-frame index queued by imgui_force_set

  • labels : array<string> - tab labels from the previous frame (snapshot-visible)

TooltipState

Backs both tooltip and item_tooltip. Tooltips don’t persist — they’re a per-frame chrome pop.

Fields:
  • _placeholder : bool - unused; keeps the struct non-empty for [container] auto-emit

PopupContextItemState

Backs popup_context_item. Right-click attaches to the previous item and ImGui drives open/close internally; no pending flags needed.

Fields:
  • _placeholder : bool - unused; keeps the struct non-empty for [container] auto-emit

PopupContextWindowState

Backs popup_context_window. Right-click anywhere in the enclosing window opens the popup; ImGui drives open/close internally.

Fields:
  • _placeholder : bool - unused; keeps the struct non-empty for [container] auto-emit

PopupWindowState

Backs popup_window. Stateless BeginPopup/EndPopup wrapper for the manual-trigger pattern — caller drives open_popup(str_id, flags) themselves (typically on hover + mouse-release), then popup_window brackets the body when ImGui reports the popup as open. ImGui owns the open/close lifecycle by str_id, so no pending flags here.

Fields:
  • _placeholder : bool - unused; keeps the struct non-empty for [container] auto-emit

TextFilterState

Backs text_filter. Holds the bound C++ ImGuiTextFilter value inline — zero-init is safe (empty InputBuf, empty Filters ImVector, CountGrep = 0), so no explicit ctor call is needed. text_filter renders the inline InputText editor via the bound Draw() method; passes_filter(state, line) delegates to the

Fields:
  • value : string - hand-bound PassFilter(filter, text) standalone helper.

  • filter : ImGuiTextFilter - mirrored InputBuf text for snapshot/telemetry

DragDropSourceState

Backs drag_drop_source. Drag activation lives entirely in ImGui’s internal drag-drop state machine; body runs only on active drag.

Fields:
  • _placeholder : bool - unused; keeps the struct non-empty for [container] auto-emit

DragDropTargetState

Backs drag_drop_target. Body runs only when a payload-bearing drag is hovering this target — caller still calls AcceptDragDropPayload inside.

Fields:
  • accepted : int - running count of drops ImGui delivered here; telemetry rail to self-verify a real drop landed

TreeNodeState

Backs the tree_node container. ImGui owns the open/close state internally; we mirror what’s observable so snapshots reflect truth, and the dispatcher routes open/close through SetNextItemOpen.

Fields:
  • flags : ImGuiTreeNodeFlags - per-call ImGuiTreeNodeFlags

  • opened : bool - observed per-frame: TreeNode returned true

  • pending_open : bool - imgui_open queued; SetNextItemOpen(true) fires next frame

  • pending_close : bool - imgui_close queued; SetNextItemOpen(false) fires next frame

CollapsingHeaderState

Backs the collapsing_header container. Same shape as TreeNodeState plus a TabItemState-style @live open slot for the X-button form (CollapsingHeader(label, bool* p_visible, flags)). open defaults to true so a closable section renders from frame 1; the X-button or imgui_close flips it false.

Fields:
  • open : bool = true - closable form’s p_visible state; preserved across reload

  • flags : ImGuiTreeNodeFlags - per-call ImGuiTreeNodeFlags

  • opened : bool - observed per-frame: CollapsingHeader returned true

  • pending_open : bool - imgui_open queued; SetNextItemOpen(true) fires next frame

  • pending_close : bool - imgui_close queued; SetNextItemOpen(false) fires next frame

MainMenuBarState

Backs the main_menu_bar container — BeginMainMenuBar() / EndMainMenuBar(). Screen-top, persistent; ImGui owns visibility, we just register the container path. Stateless wrapper.

Fields:
  • _placeholder : bool - unused; keeps the struct non-empty for [container] auto-emit

WidgetEntry

Per-frame snapshot of one rendered widget. Common fields populated every frame; per-kind detail is materialized on demand from the (addr, ti) pair stored in g_widgets[ident] and pasted into payload at imgui_snapshot time via sprint_json_at + read_json. No per-widget serializer lambda — the addr+ti pair survives g_registry’s per-frame clear because it’s pinned in the long-lived g_widgets table. Stateless widgets/containers (no state global) instead set the serialize hook + serialize_ctx/serialize_id; the payload is built lazily at snapshot from those, so per-frame render does zero JSON.

Fields:
  • kind : string - widget kind (“button”, “slider_float”, …)

  • hex_id : uint - ImGui’s GetID() for this widget on this frame

  • bbox : float4 - packed [x0, y0, x1, y1] — ALWAYS screen-space

  • canvas_bbox : float4 - packed [x0,y0,x1,y1] in a transformed canvas’s LOCAL space. Emitted ONLY when the widget was captured under a non-identity snapshot item-transform (e.g. inside a node-editor canvas) — bbox is then its screen-space mapping. Absent for the common case where the widget is already screen-space.

  • sub_bboxes : array<float4> - per-component sub-rects for multi-component widgets (slider_float2/3/4, slider_int2/3/4, drag_float2/3/4, drag_int2/3/4, and the two-handle drag_*_range2 forms where [0]=lo, [1]=hi): each entry is one handle’s screen-space [x0,y0,x1,y1]. Lets a driver target component N deterministically (widget_component_point) instead of guessing x within the combined bbox. Absent for single-component widgets.

  • hover : bool - IsItemHovered() this frame

  • active : bool - IsItemActive() this frame

  • focus : bool - IsItemFocused() this frame

  • payload : JsonValue? - populated by widget_entry_jv from (addr, ti) via sprint_json_at + read_json, or from the serialize hook below

  • serialize_id : int - id/key passed to serialize (stateless lazy-payload hook); JV-skipped at 0

  • serialize_ctx : void? - opaque context passed to serialize (e.g. editor handle); JV skips void?

  • serialize : function<(ctx:void?;id:int): JsonValue?> - lazy per-snapshot payload builder for stateless widgets — never called per frame; JV skips functions

WidgetMeta
Fields:
  • module_name : string - owning module; lets clear_module_widgets drop on reload

  • kind : string - widget kind (“button”, “slider_float”, …)

  • state_addr : void? - non-indexed: stable address of the module-scope state global (set by register_widget). Indexed widgets pass null here and re-resolve per-k through addr_getter_int / addr_getter_str. Named state_addr (not addr) because daslang reserves addr as the builtin address-of operator.

  • ti : TypeInfo? - TypeInfo of the state value at addr (or the indexed slot’s element). Drives sprint_json_at at snapshot time and sscan_json_at on dispatcher set-actions without per-call-site daslang thunks.

  • addr_getter_int : function<(k:int):void?> - int-indexed path only: resolves live state-struct address per-call against the user’s table<int; …>. Null for non-indexed widgets — use addr instead.

  • k_int : int - int key stored at registration for indexed-int widgets; 0 when not indexed.

  • addr_getter_str : function<(k:string):void?> - string-indexed path only: resolves live state-struct address for table<string; …>. Null when has_str_key=false.

  • k_str : string - string key stored at registration; empty when has_str_key=false.

  • has_str_key : bool - true => use addr_getter_str + k_str; false => use state_addr (non-indexed) or addr_getter_int + k_int.

  • last_seen_frame : int - last g_frame this path appeared in g_registry; -1 = never rendered

AwaitModifiers

Per-command await knobs. Pulled from input[“await”] by with_await; all fields optional with sensible defaults (5s, no-op).

Fields:
  • await_frames : int = -1 - min frames to advance before considering the command settled; -1 = no frame floor

  • await_until : string = “” - quiescence predicate name (empty = default is_quiescent())

  • timeout_sec : float = 5f - client-side wait ceiling (seconds)

  • fire_and_forget : bool = false - when true, client skips the await loop entirely

MousePosArgs

struct MousePosArgs

MouseClickArgs

struct MouseClickArgs

MouseScrollArgs

struct MouseScrollArgs

KeyPressArgs

struct KeyPressArgs

KeyCharArgs

struct KeyCharArgs

UserControlArgs

struct UserControlArgs

AwaitArgs
Fields:
  • until : string = “quiescent” - quiescence predicate name to poll

  • frame : int = -1 - when >=0, return quiescent once g_frame >= this value

AwaitResult
Fields:
  • quiescent : bool = false - true when the requested condition currently holds

  • frame : int = 0 - server’s current g_frame

  • pending_coroutines : int = 0 - length(g_coroutines) — in-flight L1 input drivers

  • active_id : uint = 0x0 - GetActiveID() — non-zero when a widget is being interacted with

1.2.3. Per-frame registry

begin_frame()

Per-frame reset: bumps the frame counter and clears the per-frame registry, hex_id→bbox map, and container path. Call before rendering widgets; L1 coroutines advance separately via advance_coroutines.

clear_module_widgets(module_name: string )

Drop every widget tagged with this module from the long-lived registries (g_widgets / id_to_path / focusable / pending_focus); called from each module’s [init] so hot-reload re-binds cleanly.

Arguments:
  • module_name : string

end_of_frame()

Per-frame painter drain — invokes every hook registered via register_end_of_frame_hook. Call after rendering all widgets.

id_to_bbox(hid: uint ): float4

Per-frame hex_id → bbox lookup; returns float4(0) when the id didn’t render this frame.

Arguments:
  • hid : uint

id_to_path(hid: uint ): string

Resolve a raw ImGui hex_id to its boost path_key, or "" when unmapped.

Arguments:
  • hid : uint

lookup_state_addr(path: string ): void?

Resolve a widget’s current state-struct address by path. For non-indexed widgets returns meta.state_addr directly; for indexed widgets invokes the registered addr_getter so the live table slot is re-resolved on every call (slots may be erased between frames). Returns null when path is absent from g_widgets or the getter reports the slot is gone.

Arguments:
  • path : string

register_clickable_bbox(alias_ident: string; id_tag: string; kind: string )

Surface a draw-time clickable region (a raw Button / InvisibleButton a [container] draws itself, with no [widget] of its own) as the child alias <container>/alias_ident, so external drivers and recordings can target a REAL synthetic click/drag by path instead of only imgui_force_set. Geometry-only (no state): written straight into g_registry, so it rides the per-frame clear and surfaces via imgui_snapshot’s registry-only pass with no freshness bookkeeping. Call from inside the [container] body, right after the Button(id_tag, …) — the open-container chain still names this container, and GetItemRectMin/Max / GetID(id_tag) still report that item. id_tag is the ImGui label/id passed to the button (used for GetID); alias_ident is the snapshot path leaf (usually the same string). The generic form behind register_handle_bbox.

Arguments:
  • alias_ident : string

  • id_tag : string

  • kind : string

register_end_of_frame_hook(name: string; hook: function<():void> )

Register a per-frame painter fired by end_of_frame; re-registering the same name replaces, so live-reload doesn’t multiply hooks.

Arguments:
  • name : string

  • hook : function<void>

register_focusable(widget_ident: string )

Tag this widget’s path as accepting keyboard focus; called from interactive finalize helpers so imgui_focus accepts the target.

Arguments:
  • widget_ident : string

register_handle_bbox(tag: string )

Surface a split/dock drag-handle’s geometry as a child alias <container>/HANDLE, so external drivers and recordings can target a REAL synthetic drag at the handle instead of only imgui_force_set-ing the fraction. Call from inside the [container] body, right after the handle InvisibleButton(tag, …). Thin specialization of register_clickable_bbox with the fixed HANDLE alias (a split has exactly one handle). Mirrors register_radio_site_alias.

Arguments:
  • tag : string

register_post_command_hook(name: string; hook: function<(command:string;target:string):void> )

Register a hook fired after every successful imgui_* command with (command_name, resolved_target); same name-keyed idempotent semantics as register_end_of_frame_hook.

Arguments:
  • name : string

  • hook : function<(command:string;target:string):void>

register_radio_site_alias(widget_ident: string; v_button: int; state_addr: void?; ti: TypeInfo const? )

Register a per-site geometry alias <path>#<v_button> for a grouped radio_button_int, so each option is independently click-/test-targetable even though all sites share one ident (hence one <path>) for their state. Shared state stays at <path>; the alias carries only this site’s bbox. Call right after the site’s normal finalize — GetItemRectMin/Max still reports this radio’s rect (we re-capture rather than reuse the parent entry, which widget_finalize already moved into g_registry). widgets_mark_seen(alias, alias, …) skips the bare-ident migration (its two args are equal) and lazily creates/refreshes the meta.

Arguments:
  • widget_ident : string

  • v_button : int

  • state_addr : void?

  • ti : TypeInfo?

register_widget(module_name: string; bare_ident: string; kind: string; state_addr: void?; ti: TypeInfo const? )

Module-init registration of a non-indexed widget into the long-lived g_widgets table. state_addr is the stable address of the module-scope state global (passed by the [widget] macro as unsafe(addr(IDENT))); ti is its TypeInfo (typeinfo rtti_typeinfo(IDENT)). Both feed sprint_json_at at snapshot time — no per-widget getter/serializer functions emitted. For indexed widgets use register_widget_indexed / register_widget_str.

Arguments:
  • module_name : string

  • bare_ident : string

  • kind : string

  • state_addr : void?

  • ti : TypeInfo?

register_widget_dispatch_for_type(ti: TypeInfo const?; dispatcher: KindDispatcher )

Register a dispatcher for every widget whose state has this TypeInfo. Emitted by the [widget_dispatch] annotation. The user-facing function name is irrelevant — what matters is the first parameter’s struct type.

Arguments:
register_widget_indexed(module_name: string; bare_ident: string; kind: string; addr_getter: function<(k:int):void?>; ti: TypeInfo const?; k: int )

Int-indexed sibling of register_widget. addr_getter(k) re-resolves per-call against the user’s table<int; …> (slots may be erased between frames); ti is the element’s TypeInfo. k is the table key, stored once at registration.

Arguments:
  • module_name : string

  • bare_ident : string

  • kind : string

  • addr_getter : function<(k:int):void?>

  • ti : TypeInfo?

  • k : int

register_widget_str(module_name: string; bare_ident: string; kind: string; addr_getter: function<(k:string):void?>; ti: TypeInfo const?; k: string )

String-keyed sibling of register_widget_indexed for table<string; …>. Sets has_str_key so the lookup routes through addr_getter_str(k_str).

Arguments:
  • module_name : string

  • bare_ident : string

  • kind : string

  • addr_getter : function<(k:string):void?>

  • ti : TypeInfo?

  • k : string

widget_prelude(widget_ident: string )

Top-of-body helper for [widget] macros: PushID(widget_ident) and apply any pending imgui_focus request.

Arguments:
  • widget_ident : string

widget_rect(target: string ): tuple<bool;float4>

Resolve a target string (path or 0x... hex_id) to its last-known bbox (x0,y0,x1,y1); returns (false, float4(0)) on miss.

Arguments:
  • target : string

widget_registered(path: string ): bool

Idempotency probe used by indexed-form wrappers — true when path is already in g_widgets.

Arguments:
  • path : string

with_state(path: string; t: type<auto(T) const>; blk: block<(var s:T&):void> ): auto

Safe dispatcher-side helper for [widget] finalize lambdas — resolves the live state-struct address at path and invokes blk with a mutable reference to the state. No-op when the slot is gone (erased indexed key). The block body looks like a plain state.field = ... mutation; the unsafe / reinterpret cast is hidden in this helper.

Arguments:
  • path : string

  • t : auto(T)

  • blk : block<(s:T&):void>

1.2.4. Path machinery

active_widget_path(): string

Path of the currently active/focused boost-wrapped widget, or "" when none.

container_path_key(): string

Registry/dispatcher key for the currently-open container’s own entry.

container_path_pop()

Pop the tail container ident off the open-container chain; idempotent on empty.

container_path_push(widget_ident: string )

Append widget_ident to the open-container chain; emitted by the [container] macro prelude.

Arguments:
  • widget_ident : string

widget_path_key(widget_ident: string ): string

Build a leaf widget’s registry key: open container chain ("/"-joined) + widget_ident; bare ident when unnested.

Arguments:
  • widget_ident : string

1.2.5. Coroutines

advance_coroutines()

Step every active L1 input coroutine one frame; finished coroutines drop out. harness_begin_frame calls this automatically — demo code should not call it directly. Exposed for the rare host that doesn’t use the harness; in that case must run between ImGui_ImplGlfw_NewFrame() and NewFrame() so synthetic events aren’t clobbered by the GLFW backend.

spawn_coroutine(c: Coroutine )

Schedule an L1 input coroutine (one event per frame); advance_coroutines drains it.

Arguments:

1.2.6. Window control

imgui_dock(input: JsonValue? ): JsonValue?

Live-command: re-dock the dock_window named by input["target"] into the dock node id supplied in input["value"] (a uint from a DockBuilder* call). Routes through SetNextWindowDockID on the next frame.

Arguments:
imgui_dock_reset(input: JsonValue? ): JsonValue?

Live-command: reset the dockspace named by input["target"] — clears the DockBuilder node tree and flips state.has_initial_layout back to false so the user’s setup function re-runs on the next frame.

Arguments:
imgui_raise(input: JsonValue? ): JsonValue?

Live-command: bring the dock_window named by input["target"] to the front of its dock node — when several windows are docked into one node (so they render as a tab strip), this selects that window’s tab. Routes through SetNextWindowFocus() on the next frame. The automation counterpart of clicking a tab; imgui_click can’t reach a dock node’s internal tab bar.

Arguments:
imgui_set_window_pos(input: JsonValue? ): JsonValue?

Live-command: reposition the dock_window named by input["target"]. value is a JSON object with required x/y and optional w/h. Routes through SetNextWindowPos / SetNextWindowSize on the next frame; useful for placing a window after imgui_undock (the floating-window default location is implementation-defined) or for scripted layouts.

Arguments:
imgui_set_window_size(input: JsonValue? ): JsonValue?

Live-command: resize the window named by input["target"] to (w, h). Routes through SetNextWindowSize on the next frame via the regular-window dispatcher’s pending_size rail. Used by the window_size_constraints recording driver to demonstrate the SetNextWindowSizeConstraints callbacks (snap/aspect/quantize) without needing a real border-drag synth.

Arguments:
imgui_undock(input: JsonValue? ): JsonValue?

Live-command: undock the dock_window named by input["target"]; the window becomes free-floating. Routes through SetNextWindowDockID(0) on the next frame.

Arguments:

1.2.7. Live commands

imgui_await(input: JsonValue? ): JsonValue?

Live-command: poll quiescence. Returns {quiescent, frame, pending_coroutines, active_id} — clients drive the wait loop client-side (the server runs on the GLFW thread).

Arguments:
imgui_click(input: JsonValue? ): JsonValue?

Live-command: a real synthetic mouse click on the widget named by input["target"] (resolves path or 0x... hex_id; optional input["button"] — 0 left / 1 right / 2 middle). Faithful — warps to the widget center and presses/releases through ImGui’s own input path, so the widget behaves exactly as under a real click (errors if the target isn’t rendered). To set an exact value a click can’t reach, use imgui_force_set.

Arguments:
imgui_close(input: JsonValue? ): JsonValue?

Live-command: close the container named by input["target"] (window/popup/tree_node/collapsing_header/tab_item).

Arguments:
imgui_focus(input: JsonValue? ): JsonValue?

Live-command: force keyboard focus onto input["target"]; the widget’s path must have been tagged via register_focusable.

Arguments:
imgui_force_set(input: JsonValue? ): JsonValue?

Live-command: force the next-frame value of input["target"] to input["value"]. A BYPASS — writes state directly, doing what a user couldn’t (exact value, off-screen or inactive widget). For a faithful interaction prefer imgui_click / imgui_mouse_play / imgui_key_type; reach for this only when the value itself is the goal.

Arguments:
imgui_key_char(input: JsonValue? ): JsonValue?

Live-command: push a synthetic Unicode character to the focused widget.

Arguments:
imgui_key_press(input: JsonValue? ): JsonValue?

Live-command: press a synth key (ImGuiKey int); auto-pairs the mod alias.

Arguments:
imgui_key_release(input: JsonValue? ): JsonValue?

Live-command: release a synth key (ImGuiKey int).

Arguments:
imgui_mouse_click(input: JsonValue? ): JsonValue?

Live-command: press/release a synth mouse button at the current synth cursor.

Arguments:
imgui_mouse_pos(input: JsonValue? ): JsonValue?

Live-command: teleport the synth cursor to (x, y) and take ownership.

Arguments:
imgui_mouse_scroll(input: JsonValue? ): JsonValue?

Live-command: synthetic mouse wheel.

Arguments:
imgui_open(input: JsonValue? ): JsonValue?

Live-command: open the container named by input["target"] (window/popup/tree_node/collapsing_header/tab_item).

Arguments:
imgui_select(input: JsonValue? ): JsonValue?

Live-command: activate the tab_item named by input["target"] — the automation counterpart of clicking a tab in a tab_bar (ImGui owns active-tab selection, so an inactive tab can’t be reached by imgui_click). Routes through ImGuiTabItemFlags.SetSelected on the next frame. The selected tab records state.selected = true for snapshot consumers.

Arguments:
imgui_snapshot(_input: JsonValue? ): JsonValue?

Live-command: return {frame, globals, io} — every registered widget joined with its per-frame entry when rendered.

Arguments:

1.2.8. Synthetic input

get_synth_cursor(): tuple<bool;float;float>

Current synth cursor as (owned, x, y); owned means overlays should render against this instead of GetMousePos().

get_synth_keys(): tuple<bool;uint;float>

Synth-key state as (any_held, last_codepoint, last_codepoint_t).

imgui_synth_tick()

Per-frame synth entry point — call between the backend’s *_NewFrame() and ImGui::NewFrame(). Detaches/reattaches real input on a user-control transition, advances registered timelines, and re-asserts held synth state (idempotent — ImGui dedupes same-state events). No-op when nothing is armed. harness_new_frame calls this for you.

register_real_input_toggle(hook: function<(enabled:bool):void> )

Register the backend’s real-input enable/disable callback. imgui_synth_tick invokes it on a set_user_control transition (the GLFW backend wires imgui_set_real_input_callbacks).

Arguments:
  • hook : function<(enabled:bool):void>

register_synth_frame_hook(name: string; hook: function<():void> )

Register a per-frame synth callback invoked by imgui_synth_tick (imgui_live_core’s timeline playback uses this).

Arguments:
  • name : string

  • hook : function<void>

register_synth_stop_hook(name: string; hook: function<():void> )

Register a callback invoked when control is handed back to the user (set_user_control(true)) — stop in-flight playback.

Arguments:
  • name : string

  • hook : function<void>

release_held_buttons()

Release every currently-held synth mouse button.

release_held_keys()

Release every currently-held synth key.

set_user_control(input: JsonValue? ): JsonValue?

Live-command: enable/disable real mouse+keyboard reaching the UI.

Arguments:
set_user_control_enabled(enabled: bool )

Toggle whether real mouse/keyboard reach the UI. The GLFW-callback detach/reattach happens on the next imgui_synth_tick (must run on the render thread), so this is safe to call off-thread.

Arguments:
  • enabled : bool

synth_char(codepoint: uint )

Push a synthetic Unicode character to the focused widget.

Arguments:
  • codepoint : uint

synth_key_press(key: int )

Press synth key (ImGuiKey int); auto-pairs the ImGuiMod_* alias for side-mod keys so Shortcut() routing sees the chord.

Arguments:
  • key : int

synth_key_release(key: int )

Release synth key (and its mod alias).

Arguments:
  • key : int

synth_mouse_button(button: int; action: int )

Press (action==1) / release synth mouse button. Emits the cursor pos immediately before the button so ImGui records MouseClickedPos at the synth coords and IsItemHovered() passes.

Arguments:
  • button : int

  • action : int

synth_mouse_move(x: float; y: float )

Teleport the synth cursor to (x, y) and take ownership (the real cursor can’t move it until set_user_control(true) hands back).

Arguments:
  • x : float

  • y : float

synth_mouse_wheel(dx: float; dy: float )

Synthetic mouse wheel; takes cursor ownership.

Arguments:
  • dx : float

  • dy : float

user_control_enabled(): bool

False when set_user_control has disabled real input — imgui_synth_tick drops the backend input queue each frame so synth fully owns IO.

1.2.9. Debug logging

imgui_log_drain(_input: JsonValue? ): JsonValue?

Live-command: return the host-side instrumentation lines accumulated since the last drain (a JSON array of strings) and clear the buffer. The transport-readable companion to the stdout emit — a test or MCP client reads the dispatch/coroutine trace from the response.

Arguments:
imgui_log_section(input: JsonValue? ): JsonValue?

Live-command: enable/disable host-side debug log section input["name"] per input["on"]. Gated emit sites (e.g. “synth.click”) cost one set-lookup when off, so heavy per-frame logging is free until a section is turned on. Runs in the host context, so it reaches the dispatch/coroutine instrumentation a test’s own context can’t.

Arguments:
log_section(name: string; on: bool )

Enable/disable a debug log section. Sections are checked by log_section_on; gated emit sites cost one set-lookup when off, so heavy per-frame logging is free until a section is turned on.

Arguments:
  • name : string

  • on : bool

log_section_on(name: string ): bool

True if debug log section name is enabled. Guard heavy per-frame emit: if (log_section_on("x")) clk_log(...).

Arguments:
  • name : string

1.2.10. Memory debugging

imgui_debug_memory(enabled: bool )

Toggle per-frame heap / string-heap GC-pressure logging. Enabling (re)sets the baseline, so a steadily-growing heap surfaces as the per-frame +delta climbing instead of flat.

Arguments:
  • enabled : bool

imgui_memory_report()

Emit a final heap / string-heap delta-vs-baseline line, plus a WARNING if either grew past 64kb (a steady per-frame leak / GC-pressure smell). Called at teardown by with_imgui_app / with_recording_app; safe to call by hand. No-op until a baseline has been taken.

imgui_reset_memory_baseline()

Snapshot current heap + string-heap usage as the leak-tracking baseline. Call after one-time setup (window/fonts/first frame) so only steady-state growth is measured.

1.2.11. Snapshot transform

capture_current_item_rect(): float4

The just-submitted ImGui item’s screen-space rect [x0,y0,x1,y1], mapped through the active snapshot item-transform (identity in the common case). For popup/list widgets whose items are NOT separately registered (combo popup items): the body captures one rect per item right after its Selectable, building the sub_bboxes a driver then targets via widget_component_point.

capture_item_bbox(entry: WidgetEntry; local: float4 )

Record a captured imgui item rect into entry: bbox mapped to screen via the active snapshot item-transform, plus canvas_bbox carrying the original local rect when the transform actually moved it. The single capture chokepoint for both leaf widgets (widget_finalize) and container headers, so every entry stays consistent under a transformed canvas. The (0,0,0,0) sentinel (a headerless container) is left untouched — never mapped to a phantom rect at the transform offset.

Arguments:
pop_snapshot_item_transform()

Pop the innermost item-transform pushed by push_snapshot_item_transform, restoring the enclosing canvas’s mapping (or identity at the outermost level).

push_snapshot_item_transform(scale: float2; offset: float2 )

Push a screen = local * scale + offset mapping applied to every widget’s captured item rect until the matching pop_snapshot_item_transform(). A custom canvas (e.g. node_editor) pushes this so interior widgets report screen-space bbox plus a canvas_bbox carrying the original local rect. Nestable.

Arguments:
  • scale : float2

  • offset : float2

1.2.12. JSON serialization

1.2.12.1. JV

JV(r: $Result(type<auto(TT)>,type<auto(EE)>) ): JsonValue?

Wire format for Result<T, E>: {"ok": bool, "value"|"error": ...}; the transport contract every imgui_* handler returns.

Arguments:
  • r : typemacro

JV(value: TextFilterState ): JsonValue?

serialize(arch: Archive; value: TextFilterState )

def serialize (var arch: Archive; var value: TextFilterState)

Arguments:
state_jv(path: string; t: type<auto(T) const> ): JsonValue?

Safe serializer-side helper for [widget] finalize lambdas — resolves the live state-struct address at path and returns JV(state). Returns null when the slot is gone (erased indexed key) so the snapshot reports the widget without a payload instead of dereferencing a stale capture. Pair with with_state on the dispatcher side; together they let custom-widget finalize helpers stay free of unsafe / reinterpret.

Arguments:
  • path : string

  • t : auto(T)