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

8.18.1.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.18.1.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.18.1.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.18.1.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.18.1.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. The body is required to not exist (or be empty); use tree_node_ex (the [widget] leaf form) for nodes that should be selectable rows without children. 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.18.1.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.18.1.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.