8.19.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.19.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
3options gc
4
5require imgui
6require imgui_app
7require opengl/opengl_boost
8require live/glfw_live
9require live/live_api
10require live/live_commands
11require live/live_vars
12require live_host
13require imgui/imgui_live
14require imgui/imgui_boost_runtime
15require imgui/imgui_boost_v2
16require imgui/imgui_widgets_builtin
17require imgui/imgui_containers_builtin
18require imgui/imgui_id_builtin
19require imgui/imgui_visual_aids
20
21// =============================================================================
22// TUTORIAL: popup_window — stateless BeginPopup/EndPopup with manual trigger.
23//
24// popup_window(IDENT, (str_id = "..", flags = ..)) { body }
25//
26// Distinct from the auto-triggered relatives:
27//
28// popup_context_item — right-click on the preceding item opens the popup.
29// popup_context_window — right-click anywhere in the window opens it.
30// popup_window — NO auto-trigger; caller drives open_popup(str_id).
31//
32// The manual form maps the cpp pattern from `imgui_demo.cpp`'s table-context
33// section: one shared str_id, multiple PushID-scoped instances, custom
34// hover/key/release predicates feeding the same OpenPopup call.
35//
36// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/popup_window.das
37// LIVE: daslang-live modules/dasImgui/examples/tutorial/popup_window.das
38// =============================================================================
39
40var private LAST_PICK : string = "(none yet)"
41
42[export]
43def init() {
44 live_create_window("dasImgui popup_window tutorial", 760, 480)
45 live_imgui_init(live_window)
46 let io & = unsafe(GetIO())
47 GetStyle().FontScaleMain = 1.4
48}
49
50[export]
51def update() {
52 if (!live_begin_frame()) return
53 begin_frame()
54
55 ImGui_ImplGlfw_NewFrame()
56 apply_synth_io_override()
57 NewFrame()
58
59 SetNextWindowPos(ImVec2(40.0f, 40.0f), ImGuiCond.Always)
60 SetNextWindowSize(ImVec2(560.0f, 340.0f), ImGuiCond.Always)
61 window(PW_WIN, (text = "popup_window", closable = false,
62 flags = ImGuiWindowFlags.None)) {
63 text("Two scopes share str_id \"fruit_picker\":")
64
65 // Scope A: explicit click trigger.
66 with_id("via_button") {
67 if (button(PW_BTN, (text = "Open via button"))) {
68 open_popup("fruit_picker")
69 }
70 popup_window(PW_BTN_POPUP, (str_id = "fruit_picker",
71 flags = ImGuiWindowFlags.None)) {
72 draw_picker_body()
73 }
74 }
75
76 separator()
77
78 // Scope B: hover-region + right-click — no preceding item involved.
79 with_id("via_region") {
80 text(PW_HOVER, (text = "(hover this region, right-click)"))
81 let region_min = GetItemRectMin()
82 let region_max = GetItemRectMax()
83 let in_region = (IsMouseHoveringRect(region_min, region_max, true) &&
84 !IsAnyItemHovered())
85 if (in_region && IsMouseReleased(ImGuiMouseButton.Right)) {
86 open_popup("fruit_picker")
87 }
88 popup_window(PW_REG_POPUP, (str_id = "fruit_picker",
89 flags = ImGuiWindowFlags.None)) {
90 draw_picker_body()
91 }
92 }
93
94 separator()
95 text(PW_STATUS, (text = "Last pick: {LAST_PICK}"))
96 }
97
98 end_of_frame()
99 Render()
100 var w, h : int
101 live_get_framebuffer_size(w, h)
102 glViewport(0, 0, w, h)
103 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
104 glClear(GL_COLOR_BUFFER_BIT)
105 live_imgui_render()
106
107 live_end_frame()
108}
109
110def private draw_picker_body() {
111 text("Pick a fruit:")
112 if (menu_label(PW_APPLE, (text = "Apple"))) {
113 LAST_PICK = "Apple"
114 close_current_popup()
115 }
116 if (menu_label(PW_BANANA, (text = "Banana"))) {
117 LAST_PICK = "Banana"
118 close_current_popup()
119 }
120 if (menu_label(PW_CHERRY, (text = "Cherry"))) {
121 LAST_PICK = "Cherry"
122 close_current_popup()
123 }
124 separator()
125 if (button(PW_CLOSE, (text = "Close"))) {
126 close_current_popup()
127 }
128}
129
130[export]
131def shutdown() {
132 live_imgui_shutdown()
133 live_destroy_window()
134}
135
136[export]
137def main() {
138 init()
139 while (!exit_requested()) {
140 update()
141 maybe_collect_gc()
142 }
143 shutdown()
144}
8.19.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.19.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.19.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.19.22.1.4. State
PopupWindowState carries no live fields — just an unused placeholder
bool that keeps the struct non-empty for [container] auto-emit. 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.19.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.