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

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.

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.

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.

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.

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

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 examples/features/tree_node_ex.das for the rail-side demo and 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.

Standalone vs live

Same convention as the other tutorials.

See also

Full source: examples/tutorial/tree_node.das

Features-side demos: examples/features/containers_layout.das — tree_node alongside tab_bar + collapsing_header; examples/features/tree_node_open_manual.das — leaf-form manual tree_pop pairing with a sibling button on the header row; 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.