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_builtin—tree_nodecontainer.imgui/imgui_widgets_builtin—slider_float,checkbox,text, plus the leaftree_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 appliesSetNextItemOpen(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); usetree_node_ex(the [widget] leaf form) for nodes that should be selectable rows without children. Seeexamples/features/tree_node_ex.das.Bullet— bullet point instead of chevron. Pair withLeaf.Framed— solid background under the header strip.NoTreePushOnOpen— open the node WITHOUT indenting the body. Used bytree_node_exrails 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 handlesTreePop.tree_node_ex(IDENT, (...))—[widget]leaf. Returns a bool; caller pairstree_pop()(the rail) manually, outside anif (open)if needed. The escape hatch for “header has a siblingsame_line()widget” or “Leaf node with selectable behavior” — seeexamples/features/tree_node_ex.dasfor the rail-side demo andexamples/features/tree_node_open_manual.dasfor 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.