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: 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_builtincollapsing_header.

  • imgui/imgui_widgets_builtintext, 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 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.

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.