8.18.1.19. Tab bar
tab_bar hosts one or more tab_item blocks under a strip of
clickable headers. ImGui owns active-tab selection internally — the
boost wrapper just observes — and routes click events to the right
body. Three things make tabs subtle:
Only the active tab’s body runs each frame. An inactive tab’s leaves aren’t submitted to ImGui, so a snapshot shows them
rendered:falseand their container-path key (e.g.AUDIO_TAB/A_VOLUME) only resolves once that tab has been active — switch the tab before reading or writing leaves under it. The container’s state (TabBarState / TabItemState) lives outside the body and persists.There is no ``pending_select`` channel. ImGui owns selection;
imgui_clickagainst a tab_item path is how external drivers switch tabs.Closable tabs add an X-button. The wrapper feeds
&state.opento BeginTabItem’sp_openparameter; ImGui flips it false on X-click and the wrapper skips the tab next frame.
Source: modules/dasImgui/examples/tutorial/tab_bar.das.
8.18.1.19.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: tab_bar — BeginTabBar / EndTabBar with nested tab_item blocks.
21//
22// tab_bar(IDENT, (text = "...", flags = ImGuiTabBarFlags....)) {
23// tab_item(TAB_A, (text = "A", closable = false, flags = ...)) { ... }
24// tab_item(TAB_B, (text = "B", closable = true, flags = ...)) { ... }
25// }
26//
27// Key behaviors:
28//
29// 1. Only the ACTIVE tab's body block runs each frame — widgets inside
30// inactive tabs are NOT in the registry that frame. A driver doing
31// imgui_force_set against an inactive tab's child path gets a 404.
32//
33// 2. Closable tabs (closable=true) render an X-button. ImGui flips
34// state.open=false; the wrapper then skips BeginTabItem entirely so
35// the tab disappears from the strip until pending_open is set true
36// again.
37//
38// 3. ImGui owns active-tab selection — there is NO state.pending_select.
39// Drive it from outside by imgui_click against the tab_item path
40// (the click hits the tab strip header).
41//
42// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/tab_bar.das
43// LIVE: daslang-live modules/dasImgui/examples/tutorial/tab_bar.das
44//
45// DRIVE (when running live):
46// curl -X POST -d '{"name":"imgui_click","args":{"target":"TB_WIN/MAIN_TABS/AUDIO_TAB"}}' \
47// localhost:9090/command
48// curl -X POST -d '{"name":"imgui_close","args":{"target":"TB_WIN/MAIN_TABS/DRAFT_TAB"}}' \
49// localhost:9090/command
50// =============================================================================
51
52[export]
53def init() {
54 live_create_window("dasImgui tab_bar tutorial", 760, 540)
55 live_imgui_init(live_window)
56 let io & = unsafe(GetIO())
57 GetStyle().FontScaleMain = 1.4
58}
59
60[export]
61def update() {
62 if (!live_begin_frame()) return
63 begin_frame()
64
65 ImGui_ImplGlfw_NewFrame()
66 apply_synth_io_override()
67 NewFrame()
68
69 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
70 SetNextWindowSize(ImVec2(720.0f, 500.0f), ImGuiCond.Always)
71 window(TB_WIN, (text = "tab_bar tutorial", closable = false,
72 flags = ImGuiWindowFlags.None)) {
73
74 text("Three tabs plus one closable Draft tab. Click a header to switch.")
75 separator()
76
77 // Reorderable + TabListPopupButton (the ▼ menu on the right).
78 tab_bar(MAIN_TABS, (text = "MainTabs",
79 flags = ImGuiTabBarFlags.Reorderable |
80 ImGuiTabBarFlags.TabListPopupButton)) {
81
82 // ---- General tab (non-closable, default selection) ----
83 tab_item(GENERAL_TAB, (text = "General", closable = false,
84 flags = ImGuiTabItemFlags.None)) {
85 text("Only the active tab's body block runs each frame.")
86 text("Switch tabs - checkbox state below survives because the")
87 text("[container] state lives outside the body.")
88 checkbox(G_WIRE, (text = "Wireframe"))
89 checkbox(G_LIVE, (text = "Live preview"))
90 }
91
92 // ---- Audio tab ----
93 tab_item(AUDIO_TAB, (text = "Audio", closable = false,
94 flags = ImGuiTabItemFlags.None)) {
95 slider_int(A_VOLUME, (text = "Volume"))
96 checkbox(A_MUTE, (text = "Mute"))
97 text("VOLUME = {A_VOLUME.value} MUTE = {A_MUTE.value}")
98 }
99
100 // ---- Info tab ----
101 tab_item(INFO_TAB, (text = "Info", closable = false,
102 flags = ImGuiTabItemFlags.None)) {
103 text(INFO_LEAD, (text = "Tab metadata - read-only."))
104 text("MAIN_TABS.flags = Reorderable | TabListPopupButton")
105 text("Inactive tab bodies do NOT register child widgets.")
106 }
107
108 // ---- Draft tab (closable, has X-button) ----
109 tab_item(DRAFT_TAB, (text = "Draft", closable = true,
110 flags = ImGuiTabItemFlags.None)) {
111 text("Draft. Click the x on the tab header to close it.")
112 text("DRAFT_TAB.open = {DRAFT_TAB.open}")
113 slider_int(D_LINE_LEN, (text = "Line length"))
114 }
115 }
116
117 separator()
118 text("Outside the tab_bar - runs every frame regardless of active tab.")
119 text("DRAFT_TAB.open is observable from out here: {DRAFT_TAB.open}")
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 }
146 shutdown()
147}
8.18.1.19.1.1. Requires
Already in the baseline boost layer:
imgui/imgui_containers_builtin—tab_bar,tab_item,edit_tab_item.imgui/imgui_widgets_builtin— leaves used inside each tab body.
8.18.1.19.1.2. Active tab + body gating
ImGui drives active-tab selection internally. Each tab_item’s body
only runs while that tab is active:
tab_bar(MAIN_TABS, (text = "MainTabs",
flags = ImGuiTabBarFlags.Reorderable)) {
tab_item(GENERAL_TAB, (text = "General", closable = false,
flags = ImGuiTabItemFlags.None)) {
checkbox(G_WIRE, (text = "Wireframe")) // only registered while active
}
tab_item(AUDIO_TAB, ...) { ... }
tab_item(INFO_TAB, ...) { ... }
}
A snapshot taken while AUDIO_TAB is active shows G_WIRE under
GENERAL_TAB as rendered:false — the leaf wasn’t submitted to
ImGui that frame. Drivers must switch the active tab first before
reading or writing leaves underneath.
8.18.1.19.1.3. Switching tabs from outside
imgui_click against a tab_item path hits the tab strip header (the
boost wrapper captures the header bbox separately so click resolves to
the strip, not the body):
curl -X POST -d '{"name":"imgui_click","args":{"target":"TB_WIN/MAIN_TABS/AUDIO_TAB"}}' \
localhost:9090/command
This is the only programmatic way to switch tabs — there’s no
imgui_set_active because ImGui’s internal selection isn’t exposed
as a struct field. imgui_click is enough: it produces a real ImGui
mouse event against the header, which is exactly what manual user
interaction does.
8.18.1.19.1.4. Closable tabs
closable=true makes the X-button appear:
tab_item(DRAFT_TAB, (text = "Draft", closable = true,
flags = ImGuiTabItemFlags.None)) {
text("DRAFT_TAB.open = {DRAFT_TAB.open}")
}
The wrapper threads &state.open into BeginTabItem’s p_open.
When the user clicks X, ImGui flips state.open=false. Next frame
the wrapper sees !state.open and skips BeginTabItem entirely —
the tab disappears from the strip. Bring it back with
DRAFT_TAB.pending_open = true from app code or imgui_open
from a driver.
state.open defaults to true so closable tabs appear from frame 1.
@live open : bool = true in TabItemState carries the value across
live reload — the user’s “I closed this tab” state survives a recompile.
8.18.1.19.1.6. ImGuiTabBarFlags
Bar-level chrome:
Reorderable— drag tab headers to reorder them.AutoSelectNewTabs— newly-submitted tabs become the active selection.TabListPopupButton— adds a ▼ menu on the right with all tabs as a list (useful when there are many tabs).NoCloseWithMiddleMouseButton— disable the middle-mouse shortcut for closable tabs.NoTooltip— suppress hover tooltips on tab headers.FittingPolicyResizeDown/FittingPolicyScroll— what happens when the strip doesn’t fit horizontally.
Per-tab flags via ImGuiTabItemFlags: UnsavedDocument (marks the
header with a dot), SetSelected (a one-shot select-this-tab nudge),
NoCloseButton (closable=true but no X), Leading/Trailing
(pin to bar edges), NoReorder.
8.18.1.19.1.7. Standalone vs live
Same convention as the other tutorials.
See also
Full source: modules/dasImgui/examples/tutorial/tab_bar.das
Features-side demos: modules/dasImgui/examples/features/containers_layout.das —
tab_bar + tree_node + collapsing_header composition;
modules/dasImgui/examples/features/tab_item_button.das — the tab_item_button
widget for an action-button styled as a tab.
Sibling: Containers — umbrella container tour.
Boost macros — the macro layer.