8.18.1.22. Popup window
ImGui exposes three popup-opening patterns: auto-trigger from a preceding
item (BeginPopupContextItem), auto-trigger from anywhere in the window
(BeginPopupContextWindow), and manual-trigger — the caller decides
when to open. The boost layer wraps each with a dedicated [container]:
popup_context_item(IDENT, (str_id, flags))— right-click on the previously-submitted item opens the popup.popup_context_window(IDENT, (str_id, flags))— right-click anywhere inside the enclosing window opens it.popup_window(IDENT, (str_id, flags))— no auto-trigger; the caller drivesopen_popup(str_id, flags)from any custom predicate.
The manual form is what the cpp demo’s per-column table-context section
needs: one shared str_id across multiple PushID scopes, each
deciding for itself when to OpenPopup based on hover state, key
combinations, or any predicate that has no preceding “item” to attach to.
Source: modules/dasImgui/examples/tutorial/popup_window.das.
8.18.1.22.1. Walkthrough
The recording drives both trigger scopes for real: clicking Open via
button fires open_popup and the popup body renders; picking a fruit
calls close_current_popup and the Last pick line updates;
right-clicking anywhere in the hover region opens the same popup through
the second scope. Each open is verified against the popup body’s
rendered-this-frame state and each pick against the status line, so a
trigger that failed to open or close the popup would abort the recording.
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_id_builtin
18require imgui/imgui_visual_aids
19
20// =============================================================================
21// TUTORIAL: popup_window — stateless BeginPopup/EndPopup with manual trigger.
22//
23// popup_window(IDENT, (str_id = "..", flags = ..)) { body }
24//
25// Distinct from the auto-triggered relatives:
26//
27// popup_context_item — right-click on the preceding item opens the popup.
28// popup_context_window — right-click anywhere in the window opens it.
29// popup_window — NO auto-trigger; caller drives open_popup(str_id).
30//
31// The manual form maps the cpp pattern from `imgui_demo.cpp`'s table-context
32// section: one shared str_id, multiple PushID-scoped instances, custom
33// hover/key/release predicates feeding the same OpenPopup call.
34//
35// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/popup_window.das
36// LIVE: daslang-live modules/dasImgui/examples/tutorial/popup_window.das
37// =============================================================================
38
39var private LAST_PICK : string = "(none yet)"
40
41[export]
42def init() {
43 live_create_window("dasImgui popup_window tutorial", 760, 480)
44 live_imgui_init(live_window)
45 let io & = unsafe(GetIO())
46 GetStyle().FontScaleMain = 1.4
47}
48
49[export]
50def update() {
51 if (!live_begin_frame()) return
52 begin_frame()
53
54 ImGui_ImplGlfw_NewFrame()
55 apply_synth_io_override()
56 NewFrame()
57
58 SetNextWindowPos(ImVec2(40.0f, 40.0f), ImGuiCond.Always)
59 SetNextWindowSize(ImVec2(560.0f, 340.0f), ImGuiCond.Always)
60 window(PW_WIN, (text = "popup_window", closable = false,
61 flags = ImGuiWindowFlags.None)) {
62 text("Two scopes share str_id \"fruit_picker\":")
63
64 // Scope A: explicit click trigger.
65 with_id("via_button") {
66 if (button(PW_BTN, (text = "Open via button"))) {
67 open_popup("fruit_picker")
68 }
69 popup_window(PW_BTN_POPUP, (str_id = "fruit_picker",
70 flags = ImGuiWindowFlags.None)) {
71 draw_picker_body()
72 }
73 }
74
75 separator()
76
77 // Scope B: hover-region + right-click — no preceding item involved.
78 with_id("via_region") {
79 text(PW_HOVER, (text = "(hover this region, right-click)"))
80 let region_min = GetItemRectMin()
81 let region_max = GetItemRectMax()
82 let in_region = (IsMouseHoveringRect(region_min, region_max, true) &&
83 !IsAnyItemHovered())
84 if (in_region && IsMouseReleased(ImGuiMouseButton.Right)) {
85 open_popup("fruit_picker")
86 }
87 popup_window(PW_REG_POPUP, (str_id = "fruit_picker",
88 flags = ImGuiWindowFlags.None)) {
89 draw_picker_body()
90 }
91 }
92
93 separator()
94 text(PW_STATUS, (text = "Last pick: {LAST_PICK}"))
95 }
96
97 end_of_frame()
98 Render()
99 var w, h : int
100 live_get_framebuffer_size(w, h)
101 glViewport(0, 0, w, h)
102 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
103 glClear(GL_COLOR_BUFFER_BIT)
104 live_imgui_render()
105
106 live_end_frame()
107}
108
109def private draw_picker_body() {
110 text("Pick a fruit:")
111 if (menu_label(PW_APPLE, (text = "Apple"))) {
112 LAST_PICK = "Apple"
113 close_current_popup()
114 }
115 if (menu_label(PW_BANANA, (text = "Banana"))) {
116 LAST_PICK = "Banana"
117 close_current_popup()
118 }
119 if (menu_label(PW_CHERRY, (text = "Cherry"))) {
120 LAST_PICK = "Cherry"
121 close_current_popup()
122 }
123 separator()
124 if (button(PW_CLOSE, (text = "Close"))) {
125 close_current_popup()
126 }
127}
128
129[export]
130def shutdown() {
131 live_imgui_shutdown()
132 live_destroy_window()
133}
134
135[export]
136def main() {
137 init()
138 while (!exit_requested()) {
139 update()
140 }
141 shutdown()
142}
8.18.1.22.1.1. Requires
Already in the baseline boost layer:
imgui/imgui_containers_builtin—popup_window,open_popup,close_current_popup.imgui/imgui_id_builtin—with_idfor the per-scope ID-stack push.
8.18.1.22.1.2. Two scopes, one str_id
ImGui keys a popup’s open state by str_id under the current ID stack.
When two render sites want the same logical popup (“pick a fruit”) but
need independent open state, push a distinct with_id("scope") { ... }
around each:
with_id("via_button") {
if (button(OPEN_BTN, (text = "Open via button"))) {
open_popup("fruit_picker")
}
popup_window(BTN_POPUP, (str_id = "fruit_picker", flags = ImGuiWindowFlags.None)) {
// body
}
}
with_id("via_region") {
// ...custom hover/release trigger...
if (in_region && IsMouseReleased(ImGuiMouseButton.Right)) {
open_popup("fruit_picker")
}
popup_window(REG_POPUP, (str_id = "fruit_picker", flags = ImGuiWindowFlags.None)) {
// body
}
}
The two scopes see the same str_id, but ImGui’s ID-stack push from
with_id mangles the open state per scope. The shared body factor lets
both scopes call the same private draw_picker_body() helper.
8.18.1.22.1.3. Custom predicates
Because there’s no auto-trigger, the caller is free to compose any predicate that should open the popup. The features demo combines a hover-region check with a right-release:
text(HINT, (text = "(hover this region, right-click)"))
let region_min = GetItemRectMin()
let region_max = GetItemRectMax()
let in_region = (IsMouseHoveringRect(region_min, region_max, true) &&
!IsAnyItemHovered())
if (in_region && IsMouseReleased(ImGuiMouseButton.Right)) {
open_popup("fruit_picker")
}
This shape is what the cpp imgui_demo’s "Tables > Context menus"
section uses to open one "MyPopup" per column. The
examples/imgui_demo/tables.das port reuses popup_window for
exactly that loop.
8.18.1.22.1.4. State
PopupWindowState is empty — ImGui owns the open lifecycle by
str_id and the boost wrapper just brackets the body. The state
global is what the registry walks to find the widget by IDENT; the
snapshot reports kind="popup_window" for every registered instance
regardless of whether the popup is currently visible.
8.18.1.22.1.5. Standalone vs live
Same convention as the other tutorials.
See also
Full source: modules/dasImgui/examples/tutorial/popup_window.das
Features-side demo: modules/dasImgui/examples/features/popup_window.das.
Integration test: modules/dasImgui/tests/test_popup_window.das.
Boost macros — the macro layer.