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: examples/tutorial/collapsing_header.das.
Walkthrough
1options gen2
2
3require imgui
4require imgui_app
5require opengl/opengl_boost
6require live/glfw_live
7require live/live_api
8require live/live_commands
9require live/live_vars
10require live_host
11require imgui/imgui_live
12require imgui/imgui_boost_runtime
13require imgui/imgui_boost_v2
14require imgui/imgui_widgets_builtin
15require imgui/imgui_containers_builtin
16require imgui/imgui_visual_aids
17
18// =============================================================================
19// TUTORIAL: collapsing_header — expandable section header with optional X-button.
20//
21// collapsing_header(IDENT, (text = "..", closable = bool,
22// flags = ImGuiTreeNodeFlags....)) { body }
23//
24// Same shape as tree_node BUT no TreePop pair — ImGui owns the close lifecycle.
25// Two distinct gates control visibility, each on its own field:
26//
27// 1. EXPANDED (chevron) — controls whether the BODY runs. state.opened
28// mirrors CollapsingHeader's per-frame return. pending_open /
29// pending_close (driven by imgui_open / imgui_close) override the
30// chevron via SetNextItemOpen on the next frame.
31//
32// 2. CLOSABLE (X-button) — controls whether the entire HEADER STRIP
33// renders. Only active when closable=true. ImGui flips state.open=false
34// on X-click; the wrapper then skips BeginCollapsingHeader entirely so
35// the strip vanishes. imgui_open re-sets state.open=true; imgui_close
36// does NOT — it only collapses the chevron via the expanded channel.
37// To programmatically hide the strip, write state.open=false from app
38// code.
39//
40// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/collapsing_header.das
41// LIVE: daslang-live modules/dasImgui/examples/tutorial/collapsing_header.das
42//
43// DRIVE (when running live):
44// # Collapse the chevron (body stops rendering; header strip stays):
45// curl -X POST -d '{"name":"imgui_close","args":{"target":"CH_WIN/CLOSABLE_CH"}}' \
46// localhost:9090/command
47// # Re-expand the chevron AND re-show the strip if X-button hid it:
48// curl -X POST -d '{"name":"imgui_open","args":{"target":"CH_WIN/CLOSABLE_CH"}}' \
49// localhost:9090/command
50// # To HIDE the strip, click the X-button or write CLOSABLE_CH.open=false
51// # from app code — there's no imgui_close path that does this.
52// =============================================================================
53
54[export]
55def init() {
56 live_create_window("dasImgui collapsing_header tutorial", 760, 520)
57 live_imgui_init(live_window)
58 let io & = unsafe(GetIO())
59 GetStyle().FontScaleMain = 1.4
60}
61
62[export]
63def update() {
64 if (!live_begin_frame()) return
65 begin_frame()
66
67 ImGui_ImplGlfw_NewFrame()
68 apply_synth_io_override()
69 NewFrame()
70
71 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
72 SetNextWindowSize(ImVec2(720.0f, 480.0f), ImGuiCond.Always)
73 window(CH_WIN, (text = "collapsing_header tutorial", closable = false,
74 flags = ImGuiWindowFlags.None)) {
75
76 text("Three collapsing_header variants - basic, closable, default-open.")
77 separator()
78
79 // ---- A: basic (non-closable) ----
80 collapsing_header(BASIC_CH, (text = "Basic header", closable = false,
81 flags = ImGuiTreeNodeFlags.None)) {
82 text("Click the chevron to fold this section.")
83 checkbox(BASIC_LIVE, (text = "Receive updates"))
84 text("opened = {BASIC_CH.opened}")
85 }
86
87 // ---- B: closable (renders an X-button) ----
88 collapsing_header(CLOSABLE_CH, (text = "Closable header",
89 closable = true,
90 flags = ImGuiTreeNodeFlags.DefaultOpen)) {
91 text("X-button on the right of the header strip closes this section.")
92 text("Once closed, the whole row disappears - flip back via")
93 text("imgui_open against CH_WIN/CLOSABLE_CH.")
94 VOLUME.bounds = (0, 100)
95 slider_int(VOLUME, (text = "volume"))
96 }
97 text("CLOSABLE_CH.open = {CLOSABLE_CH.open}, opened = {CLOSABLE_CH.opened}")
98 separator()
99
100 // ---- C: DefaultOpen flag (chevron already down on first frame) ----
101 collapsing_header(STARTUP_CH, (text = "Starts open (DefaultOpen flag)",
102 closable = false,
103 flags = ImGuiTreeNodeFlags.DefaultOpen)) {
104 text(STARTUP_HINT, (text = "ImGuiTreeNodeFlags.DefaultOpen makes the chevron expanded"))
105 text("on first render; later frames respect user input.")
106 }
107 }
108
109 end_of_frame()
110 Render()
111 var w, h : int
112 live_get_framebuffer_size(w, h)
113 glViewport(0, 0, w, h)
114 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
115 glClear(GL_COLOR_BUFFER_BIT)
116 live_imgui_render()
117
118 live_end_frame()
119}
120
121[export]
122def shutdown() {
123 live_imgui_shutdown()
124 live_destroy_window()
125}
126
127[export]
128def main() {
129 init()
130 while (!exit_requested()) {
131 update()
132 }
133 shutdown()
134}
Requires
Already in the baseline boost layer:
imgui/imgui_containers_builtin—collapsing_header.imgui/imgui_widgets_builtin—text,checkbox,slider_int.
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.
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).
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.
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).
Standalone vs live
Same convention as the other tutorials.
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: examples/tutorial/collapsing_header.das
Features-side demo: examples/features/collapsing_header_closable.das —
minimal closable-X-button repro with integration test.
Sibling: Containers — umbrella container tour.
Boost macros — the macro layer.