8.18.1.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.opened is the per-frame CollapsingHeader return value.

  • Closable — with closable=true, ImGui adds an X-button that sets state.open=false. While state.open is 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.18.1.18.1. Walkthrough

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

8.18.1.18.1.1. Requires

Already in the baseline boost layer:

  • imgui/imgui_containers_builtincollapsing_header.

  • imgui/imgui_widgets_builtintext, checkbox, slider_int.

8.18.1.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.18.1.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.18.1.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 applies SetNextItemOpen(true, Always).

  • imgui_open — the live command. The dispatcher walks the ImguiPathRegistry and sets pending_open on 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.18.1.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.18.1.18.1.6. Standalone vs live

Same convention as the other tutorials.

8.18.1.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.