8.19.18. Collapsing header
collapsing_header is the section-fold sibling of tree_node: same
expand/collapse chevron, but no TreePop pair — ImGui owns the
close lifecycle. Two distinct gates govern visibility:
Expanded — the chevron click toggles the body.
state.openedis the per-frameCollapsingHeaderreturn value.Closable — with
closable=true, ImGui adds an X-button that setsstate.open=false. Whilestate.openis false, the whole header strip is hidden (not just its body).
The boost wrapper exposes one channel — pending_open /
pending_close — that drives the chevron (expanded gate). The
live commands imgui_open / imgui_close ride that channel.
imgui_open additionally re-sets state.open=true so a previously
X-hidden strip becomes visible again. imgui_close does NOT touch
state.open — to hide the strip, click the X-button or write
state.open=false from app code.
Source: modules/dasImgui/examples/tutorial/collapsing_header.das.
8.19.18.1. Walkthrough
1options gen2
2options _comment_hygiene = true
3options gc
4
5require imgui
6require imgui_app
7require opengl/opengl_boost
8require live/glfw_live
9require live/live_api
10require live/live_commands
11require live/live_vars
12require live_host
13require imgui/imgui_live
14require imgui/imgui_boost_runtime
15require imgui/imgui_boost_v2
16require imgui/imgui_widgets_builtin
17require imgui/imgui_containers_builtin
18require imgui/imgui_visual_aids
19
20// =============================================================================
21// TUTORIAL: collapsing_header — expandable section header with optional X-button.
22//
23// collapsing_header(IDENT, (text = "..", closable = bool,
24// flags = ImGuiTreeNodeFlags....)) { body }
25//
26// Same shape as tree_node BUT no TreePop pair — ImGui owns the close lifecycle.
27// Two distinct gates control visibility, each on its own field:
28//
29// 1. EXPANDED (chevron) — controls whether the BODY runs. state.opened
30// mirrors CollapsingHeader's per-frame return. pending_open /
31// pending_close (driven by imgui_open / imgui_close) override the
32// chevron via SetNextItemOpen on the next frame.
33//
34// 2. CLOSABLE (X-button) — controls whether the entire HEADER STRIP
35// renders. Only active when closable=true. ImGui flips state.open=false
36// on X-click; the wrapper then skips BeginCollapsingHeader entirely so
37// the strip vanishes. imgui_open re-sets state.open=true; imgui_close
38// does NOT — it only collapses the chevron via the expanded channel.
39// To programmatically hide the strip, write state.open=false from app
40// code.
41//
42// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/collapsing_header.das
43// LIVE: daslang-live modules/dasImgui/examples/tutorial/collapsing_header.das
44//
45// DRIVE (when running live):
46// # Collapse the chevron (body stops rendering; header strip stays):
47// curl -X POST -d '{"name":"imgui_close","args":{"target":"CH_WIN/CLOSABLE_CH"}}' \
48// localhost:9090/command
49// # Re-expand the chevron AND re-show the strip if X-button hid it:
50// curl -X POST -d '{"name":"imgui_open","args":{"target":"CH_WIN/CLOSABLE_CH"}}' \
51// localhost:9090/command
52// # To HIDE the strip, click the X-button or write CLOSABLE_CH.open=false
53// # from app code — there's no imgui_close path that does this.
54// =============================================================================
55
56[export]
57def init() {
58 live_create_window("dasImgui collapsing_header tutorial", 760, 520)
59 live_imgui_init(live_window)
60 let io & = unsafe(GetIO())
61 GetStyle().FontScaleMain = 1.4
62}
63
64[export]
65def update() {
66 if (!live_begin_frame()) return
67 begin_frame()
68
69 ImGui_ImplGlfw_NewFrame()
70 apply_synth_io_override()
71 NewFrame()
72
73 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
74 SetNextWindowSize(ImVec2(720.0f, 480.0f), ImGuiCond.Always)
75 window(CH_WIN, (text = "collapsing_header tutorial", closable = false,
76 flags = ImGuiWindowFlags.None)) {
77
78 text("Three collapsing_header variants - basic, closable, default-open.")
79 separator()
80
81 // ---- A: basic (non-closable) ----
82 collapsing_header(BASIC_CH, (text = "Basic header", closable = false,
83 flags = ImGuiTreeNodeFlags.None)) {
84 text("Click the chevron to fold this section.")
85 checkbox(BASIC_LIVE, (text = "Receive updates"))
86 text("opened = {BASIC_CH.opened}")
87 }
88
89 // ---- B: closable (renders an X-button) ----
90 collapsing_header(CLOSABLE_CH, (text = "Closable header",
91 closable = true,
92 flags = ImGuiTreeNodeFlags.DefaultOpen)) {
93 text("X-button on the right of the header strip closes this section.")
94 text("Once closed, the whole row disappears - flip back via")
95 text("imgui_open against CH_WIN/CLOSABLE_CH.")
96 VOLUME.bounds = (0, 100)
97 slider_int(VOLUME, (text = "volume"))
98 }
99 text("CLOSABLE_CH.open = {CLOSABLE_CH.open}, opened = {CLOSABLE_CH.opened}")
100 separator()
101
102 // ---- C: DefaultOpen flag (chevron already down on first frame) ----
103 collapsing_header(STARTUP_CH, (text = "Starts open (DefaultOpen flag)",
104 closable = false,
105 flags = ImGuiTreeNodeFlags.DefaultOpen)) {
106 text(STARTUP_HINT, (text = "ImGuiTreeNodeFlags.DefaultOpen makes the chevron expanded"))
107 text("on first render; later frames respect user input.")
108 }
109 }
110
111 end_of_frame()
112 Render()
113 var w, h : int
114 live_get_framebuffer_size(w, h)
115 glViewport(0, 0, w, h)
116 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
117 glClear(GL_COLOR_BUFFER_BIT)
118 live_imgui_render()
119
120 live_end_frame()
121}
122
123[export]
124def shutdown() {
125 live_imgui_shutdown()
126 live_destroy_window()
127}
128
129[export]
130def main() {
131 init()
132 while (!exit_requested()) {
133 update()
134 maybe_collect_gc()
135 }
136 shutdown()
137}
8.19.18.1.1. Requires
Already in the baseline boost layer:
imgui/imgui_containers_builtin—collapsing_header.imgui/imgui_widgets_builtin—text,checkbox,slider_int.
8.19.18.1.2. Two visibility dimensions
CollapsingHeaderState carries both gates:
var CLOSABLE_CH : CollapsingHeaderState
// After the frame renders:
// CLOSABLE_CH.open : bool // X-button gate (visible at all?)
// CLOSABLE_CH.opened : bool // chevron gate (body expanded?)
// CLOSABLE_CH.flags : ImGuiTreeNodeFlags // sticky
open is the X-button channel. opened is the chevron channel.
Both surface in the snapshot. Tests that want to verify “the user
expanded the section but didn’t close it” check opened==true &&
open==true.
8.19.18.1.3. The closable form
When closable=true, ImGui::CollapsingHeader takes a
bool* p_visible and renders an X-button on the right of the header
strip. The boost wrapper feeds &state.open to that pointer:
collapsing_header(CLOSABLE_CH, (text = "Closable header",
closable = true,
flags = ImGuiTreeNodeFlags.DefaultOpen)) {
text("X-button on the right of the header strip closes this section.")
// body
}
Click the X — ImGui sets state.open=false. Next frame, the
wrapper sees open==false and skips the ImGui call entirely.
The header row is gone until something flips open back to true.
That something is either app code (CLOSABLE_CH.open = true) or a
live driver (imgui_open against the path).
8.19.18.1.4. The pending-flag channel
Two channels feed open/close from outside:
state.pending_open = true— set anywhere in app code, then the next frame’s call to the container clears it and appliesSetNextItemOpen(true, Always).imgui_open— the live command. The dispatcher walks theImguiPathRegistryand setspending_openon the matching state.
Same shape for pending_close / imgui_close. The closable form
also clears state.open=false from the X-button — three control
surfaces flow through one struct.
8.19.18.1.5. DefaultOpen flag
ImGuiTreeNodeFlags.DefaultOpen makes the chevron expanded on first
render only. Subsequent frames respect whatever the user clicked.
Useful for “show me the important section by default” without
remembering open-state across runs.
collapsing_header(STARTUP_CH, (text = "Starts open",
closable = false,
flags = ImGuiTreeNodeFlags.DefaultOpen)) {
text("Body visible on first render.")
}
Other ImGuiTreeNodeFlags compose: OpenOnArrow, OpenOnDoubleClick,
Bullet, Framed, Leaf — same flag enum as tree_node (the
two rails share semantics intentionally).
8.19.18.1.6. Standalone vs live
Same convention as the other tutorials.
8.19.18.1.7. Driving from outside
The header strip is a real click target — its bbox is captured into the snapshot, so a human (or a playwright driver) clicks the chevron to fold the body and the X-button to hide the whole strip, exactly as the walkthrough above does (every gesture there is a real click, self-verified by the body’s rendered state). The live commands below drive the same gates without a click, for remote or scripted control.
To collapse the chevron (body stops rendering; the header strip stays visible):
curl -X POST -d '{"name":"imgui_close","args":{"target":"CH_WIN/CLOSABLE_CH"}}' \
localhost:9090/command
To re-expand the chevron — and, for a closable header that had been X-hidden, re-show the whole strip:
curl -X POST -d '{"name":"imgui_open","args":{"target":"CH_WIN/CLOSABLE_CH"}}' \
localhost:9090/command
imgui_open writes both state.pending_open AND state.open=true
so a previously X-hidden header re-appears. imgui_close, by
contrast, only writes state.pending_close — there’s no path that
hides the strip via live commands. To hide the strip programmatically,
write state.open=false from app code (the X-button is the only
user-driven path).
See also
Full source: modules/dasImgui/examples/tutorial/collapsing_header.das
Features-side demo: modules/dasImgui/examples/features/collapsing_header_closable.das —
minimal closable-X-button repro with integration test.
Sibling: Containers — umbrella container tour.
Boost macros — the macro layer.