8.19.17. Tree node

tree_node is the block-arg [container] form of ImGui’s TreeNodeEx: branch headers that fold-and-expand with an auto-paired TreePop. The wrapper’s signature:

tree_node(IDENT, (text = "...",
                  flags = ImGuiTreeNodeFlags....)) {
    // body — runs only while the chevron is expanded (TreeNodeEx == true)
}

Body invocation gated on ImGui returning true for TreeNodeEx; TreePop only fires when the body was invoked. TreeNodeState mirrors the per-frame open status (opened) and exposes the pending_open / pending_close flags so imgui_open / imgui_close can drive the chevron from outside.

Source: modules/dasImgui/examples/tutorial/tree_node.das.

8.19.17.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: tree_node — expandable branch with auto-paired TreePop.
 22//
 23//   tree_node(IDENT, (text = "..", flags = ImGuiTreeNodeFlags....)) { body }
 24//
 25// The [container] form. Body runs only while ImGui reports the node open
 26// (chevron expanded). The wrapper handles TreePop automatically when the
 27// body's been invoked. Three distinct knobs:
 28//
 29//   1. flags — ImGuiTreeNodeFlags bitfield. DefaultOpen, OpenOnArrow,
 30//      OpenOnDoubleClick, Leaf, Bullet, Framed, NoTreePushOnOpen.
 31//
 32//   2. state.pending_open / pending_close — flips SetNextItemOpen(true/false,
 33//      Always) on the NEXT frame's TreeNodeEx call. The live commands
 34//      imgui_open / imgui_close ride this channel.
 35//
 36//   3. state.opened — per-frame read-only mirror of TreeNodeEx's bool
 37//      return. A driver checks "did the user actually expand this?" by
 38//      reading state.opened on the snapshot.
 39//
 40// tree_node vs tree_node_ex:
 41//   - tree_node (this tutorial) — [container] block-arg, auto-pairs TreePop.
 42//   - tree_node_ex — [widget] leaf form, caller drives TreePop manually
 43//     (see examples/features/tree_node_ex.das). Use it when the body needs
 44//     to render OUTSIDE the open conditional, e.g. same_line() with a
 45//     sibling button on the header row.
 46//
 47// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/tree_node.das
 48// LIVE:       daslang-live modules/dasImgui/examples/tutorial/tree_node.das
 49//
 50// DRIVE (when running live):
 51//   curl -X POST -d '{"name":"imgui_open","args":{"target":"TN_WIN/PHYSICS_TREE"}}' \
 52//        localhost:9090/command
 53//   curl -X POST -d '{"name":"imgui_close","args":{"target":"TN_WIN/PHYSICS_TREE"}}' \
 54//        localhost:9090/command
 55// =============================================================================
 56
 57[export]
 58def init() {
 59    live_create_window("dasImgui tree_node tutorial", 720, 540)
 60    live_imgui_init(live_window)
 61    let io & = unsafe(GetIO())
 62    GetStyle().FontScaleMain = 1.4
 63}
 64
 65[export]
 66def update() {
 67    if (!live_begin_frame()) return
 68    begin_frame()
 69
 70    ImGui_ImplGlfw_NewFrame()
 71    apply_synth_io_override()
 72    NewFrame()
 73
 74    PHYSICS_GRAVITY.bounds = (0.0f, 50.0f)
 75    PHYSICS_DENSITY.bounds = (0.0f, 10.0f)
 76    RENDER_FOV.bounds = (30.0f, 120.0f)
 77
 78    SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
 79    SetNextWindowSize(ImVec2(680.0f, 500.0f), ImGuiCond.Always)
 80    window(TN_WIN, (text = "tree_node tutorial", closable = false,
 81                    flags = ImGuiWindowFlags.None)) {
 82
 83        text("Three patterns - DefaultOpen, nested tree_nodes, and OpenOnArrow.")
 84        separator()
 85
 86        // ---- A: DefaultOpen (chevron down from frame 1) ----
 87        tree_node(PHYSICS_TREE, (text = "Physics",
 88                                 flags = ImGuiTreeNodeFlags.DefaultOpen)) {
 89            slider_float(PHYSICS_GRAVITY, (text = "gravity"))
 90            slider_float(PHYSICS_DENSITY, (text = "density"))
 91            text("PHYSICS_TREE.opened = {PHYSICS_TREE.opened}")
 92        }
 93
 94        // ---- B: nested tree_nodes — Render > [Camera | Mesh] ----
 95        tree_node(RENDER_TREE, (text = "Render",
 96                                flags = ImGuiTreeNodeFlags.None)) {
 97            tree_node(CAMERA_SUBTREE, (text = "Camera",
 98                                       flags = ImGuiTreeNodeFlags.DefaultOpen)) {
 99                slider_float(RENDER_FOV, (text = "fov"))
100                checkbox(CAMERA_ORTHO, (text = "Orthographic"))
101            }
102            tree_node(MESH_SUBTREE, (text = "Mesh",
103                                     flags = ImGuiTreeNodeFlags.None)) {
104                checkbox(MESH_WIRE, (text = "Wireframe"))
105                checkbox(MESH_NORMALS, (text = "Show normals"))
106            }
107        }
108
109        // ---- C: OpenOnArrow — clicking the label doesn't expand ----
110
111        // DefaultOpen keeps ASSETS_HINT in the registry from frame 1. OpenOnArrow
112        // is still shown: collapse via chevron, then click the label — it stays
113        // collapsed; only the arrow toggles.
114        tree_node(ASSETS_TREE, (text = "Assets (OpenOnArrow + DefaultOpen)",
115                                flags = ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.DefaultOpen)) {
116            text(ASSETS_HINT, (text = "OpenOnArrow flag - clicking the LABEL doesn't toggle."))
117            text("Click the arrow chevron specifically. Useful for")
118            text("selectable-row trees where the label is the selection.")
119        }
120    }
121
122    end_of_frame()
123    Render()
124    var w, h : int
125    live_get_framebuffer_size(w, h)
126    glViewport(0, 0, w, h)
127    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
128    glClear(GL_COLOR_BUFFER_BIT)
129    live_imgui_render()
130
131    live_end_frame()
132}
133
134[export]
135def shutdown() {
136    live_imgui_shutdown()
137    live_destroy_window()
138}
139
140[export]
141def main() {
142    init()
143    while (!exit_requested()) {
144        update()
145        maybe_collect_gc()
146    }
147    shutdown()
148}

8.19.17.1.1. Requires

Already in the baseline boost layer:

  • imgui/imgui_containers_builtintree_node container.

  • imgui/imgui_widgets_builtinslider_float, checkbox, text, plus the leaf tree_node_ex.

8.19.17.1.2. Nesting and path composition

Tree nodes nest naturally. Each level’s IDENT pushes onto the path hash, so a slider three levels deep registers at TN_WIN/RENDER_TREE/CAMERA_SUBTREE/RENDER_FOV:

tree_node(RENDER_TREE, (text = "Render", flags = ImGuiTreeNodeFlags.None)) {
    tree_node(CAMERA_SUBTREE, (text = "Camera",
                               flags = ImGuiTreeNodeFlags.DefaultOpen)) {
        slider_float(RENDER_FOV, (text = "fov"))
    }
    tree_node(MESH_SUBTREE, (text = "Mesh",
                             flags = ImGuiTreeNodeFlags.None)) {
        checkbox(MESH_WIRE, (text = "Wireframe"))
    }
}

When RENDER_TREE is collapsed, neither subtree’s body runs — the sliders/checkboxes aren’t in the snapshot until the user expands RENDER_TREE. The state structs themselves (CAMERA_SUBTREE, MESH_SUBTREE) still exist; their opened field is false.

8.19.17.1.3. Open / close from outside

Two channels feed open/close into the wrapper:

  • state.pending_open = true — app code anywhere. Next frame the wrapper applies SetNextItemOpen(true, Always) before TreeNodeEx runs. Cleared on consume.

  • imgui_open / imgui_close — live commands routed by path.

curl -X POST -d '{"name":"imgui_open","args":{"target":"TN_WIN/PHYSICS_TREE"}}' \
     localhost:9090/command
curl -X POST -d '{"name":"imgui_close","args":{"target":"TN_WIN/PHYSICS_TREE"}}' \
     localhost:9090/command

Both flow through state.pending_open / state.pending_close — the dispatcher just walks the registry and flips the flag. Always in the SetNextItemOpen call means the override wins over ImGui’s stored chevron state for that one frame.

8.19.17.1.4. Reading state.opened

state.opened is a per-frame mirror of TreeNodeEx’s bool return. A snapshot reports it under opened:

var PHYSICS_TREE : TreeNodeState
// After the frame:
//   PHYSICS_TREE.opened : bool   // chevron expanded this frame?
//   PHYSICS_TREE.flags  : ImGuiTreeNodeFlags   // sticky

Drivers verify “user expanded this tree” by reading opened==true. Note that opened is set to im_open BEFORE the body runs — the wrapper writes it as soon as TreeNodeEx returns, so even a body that panics still leaves a correct opened for the snapshot.

8.19.17.1.5. ImGuiTreeNodeFlags

The third arg’s bitfield. Most useful:

  • DefaultOpen — first-render expansion; subsequent frames respect user input. The Render-Camera example uses this so the sub-tree is open by default after a clean launch.

  • OpenOnArrow — only the chevron click toggles; clicking the label itself doesn’t expand. Pair with a Selectable-row pattern.

  • OpenOnDoubleClick — single click selects, double click toggles.

  • Leaf — render the row without a chevron; ImGui reports it as always-open, so a container body would run every frame. A leaf has no children, so leave the body empty — or use tree_node_ex (the [widget] leaf form) for nodes that should be selectable rows. See modules/dasImgui/examples/features/tree_node_ex.das.

  • Bullet — bullet point instead of chevron. Pair with Leaf.

  • Framed — solid background under the header strip.

  • NoTreePushOnOpen — open the node WITHOUT indenting the body. Used by tree_node_ex rails where the body lives outside the conditional.

  • SpanAllColumns / SpanAvailWidth / SpanFullWidth — hit-test region tweaks for trees inside tables.

8.19.17.1.6. tree_node vs tree_node_ex

Same backing struct, different ergonomics:

  • tree_node(IDENT, (...)) { body } (this tutorial) — [container] block-arg. Body runs gated on the open chevron; wrapper handles TreePop.

  • tree_node_ex(IDENT, (...))[widget] leaf. Returns a bool; caller pairs tree_pop() (the rail) manually, outside an if (open) if needed. The escape hatch for “header has a sibling same_line() widget” or “Leaf node with selectable behavior” — see modules/dasImgui/examples/features/tree_node_ex.das for the rail-side demo and modules/dasImgui/examples/features/tree_node_open_manual.das for the sibling same_line pattern.

When in doubt, use tree_node — block-arg auto-pairing is harder to get wrong.

8.19.17.1.7. Standalone vs live

Same convention as the other tutorials.

See also

Full source: modules/dasImgui/examples/tutorial/tree_node.das

Features-side demos: modules/dasImgui/examples/features/containers_layout.das — tree_node alongside tab_bar + collapsing_header; modules/dasImgui/examples/features/tree_node_open_manual.das — leaf-form manual tree_pop pairing with a sibling button on the header row; modules/dasImgui/examples/features/tree_node_ex.das — Leaf / Bullet / OpenOnArrow variations against the [widget] leaf form.

Sibling: Collapsing header — same flags-and-state shape without the chevron / TreePop dance.

Boost macros — the macro layer.