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 label — label 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
Backs the menu_bar container. Stateless wrapper.
- Fields:
_placeholder : bool - unused; keeps the struct non-empty for [container] auto-emit
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:
flags : ImGuiTabBarFlags - per-call ImGuiTabBarFlags
- 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
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 passnullhere and re-resolve per-k throughaddr_getter_int/addr_getter_str. Namedstate_addr(notaddr) because daslang reservesaddras the builtin address-of operator.ti : TypeInfo? -
TypeInfoof the state value ataddr(or the indexed slot’s element). Drivessprint_json_atat snapshot time andsscan_json_aton 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 — useaddrinstead.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 whenhas_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 => usestate_addr(non-indexed) oraddr_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
register_clickable_bbox (alias_ident: string; id_tag: string; kind: string)
register_end_of_frame_hook (name: string; hook: function<():void>)
register_post_command_hook (name: string; hook: function<(command:string;target:string):void>)
register_widget_dispatch_for_type (ti: TypeInfo const?; dispatcher: KindDispatcher)
with_state (path: string; t: type<auto(T) const>; blk: block<(var s:T&):void>) : auto
- 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:
ti : TypeInfo?
dispatcher : KindDispatcher
- 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:
c : Coroutine
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:
input : JsonValue?
- 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:
input : JsonValue?
- 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:
input : JsonValue?
- 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:
input : JsonValue?
- 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:
input : JsonValue?
- 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:
input : JsonValue?
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:
input : JsonValue?
- 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:
input : JsonValue?
- imgui_close(input: JsonValue? ): JsonValue?
Live-command: close the container named by input["target"] (window/popup/tree_node/collapsing_header/tab_item).
- Arguments:
input : JsonValue?
- 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:
input : JsonValue?
- 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:
input : JsonValue?
- imgui_key_char(input: JsonValue? ): JsonValue?
Live-command: push a synthetic Unicode character to the focused widget.
- Arguments:
input : JsonValue?
- imgui_key_press(input: JsonValue? ): JsonValue?
Live-command: press a synth key (ImGuiKey int); auto-pairs the mod alias.
- Arguments:
input : JsonValue?
- imgui_key_release(input: JsonValue? ): JsonValue?
Live-command: release a synth key (ImGuiKey int).
- Arguments:
input : JsonValue?
- imgui_mouse_click(input: JsonValue? ): JsonValue?
Live-command: press/release a synth mouse button at the current synth cursor.
- Arguments:
input : JsonValue?
- imgui_mouse_pos(input: JsonValue? ): JsonValue?
Live-command: teleport the synth cursor to (x, y) and take ownership.
- Arguments:
input : JsonValue?
- imgui_mouse_scroll(input: JsonValue? ): JsonValue?
Live-command: synthetic mouse wheel.
- Arguments:
input : JsonValue?
- imgui_open(input: JsonValue? ): JsonValue?
Live-command: open the container named by input["target"] (window/popup/tree_node/collapsing_header/tab_item).
- Arguments:
input : JsonValue?
- 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:
input : JsonValue?
- imgui_snapshot(_input: JsonValue? ): JsonValue?
Live-command: return {frame, globals, io} — every registered widget joined with its per-frame entry when rendered.
- Arguments:
_input : JsonValue?
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:
input : JsonValue?
- 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:
_input : JsonValue?
- 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:
input : JsonValue?
- 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:
entry : WidgetEntry
local : float4
- 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:
arch : Archive
value : TextFilterState
- 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)