8.18.1.20. Modal popups
popup_modal is the blocking sibling of popup: ImGui dims the
parent window, absorbs every click outside the modal, and routes ESC to
the close path. The wrapper’s signature:
popup_modal(IDENT, (text = "title",
closable = bool,
flags = ImGuiWindowFlags....)) {
// body — runs only while ImGui has the modal open
}
Lifecycle is identical to popup — OpenPopup + BeginPopupModal
+ EndPopup, driven by state.pending_open / pending_close.
The differences are entirely visual + input-blocking, both handled by
ImGui.
closable=true adds an X-button on the title bar wired to
state.open. The wrapper feeds &state.open to
BeginPopupModal’s p_open parameter, so an X-click sets
state.open=false and ImGui closes the modal on the next frame.
Source: modules/dasImgui/examples/tutorial/popup_modal.das.
8.18.1.20.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: popup_modal — blocking modal dialog with optional X-button.
21//
22// popup_modal(IDENT, (text = "title", closable = bool,
23// flags = ImGuiWindowFlags....)) { body }
24//
25// Lifecycle is identical to popup() — OpenPopup + BeginPopupModal/EndPopup,
26// driven by state.pending_open / pending_close — but ImGui ALSO:
27//
28// 1. Dims the parent window — visual backdrop says "deal with this first".
29// 2. Blocks input outside the modal — clicks elsewhere are absorbed, not
30// passed through. ESC closes the modal (vanilla ImGui behavior).
31// 3. With closable=true, renders an X-button wired to state.open.
32//
33// The boost wrapper threads three control surfaces through state.pending_*:
34// - app code — `MODAL_X.pending_open = true`
35// - live commands — imgui_open / imgui_close
36// - close-button — ImGui flips state.open when X clicked or ESC pressed
37//
38// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/popup_modal.das
39// LIVE: daslang-live modules/dasImgui/examples/tutorial/popup_modal.das
40//
41// DRIVE (when running live):
42// curl -X POST -d '{"name":"imgui_open","args":{"target":"PM_WIN/CONFIRM_MODAL"}}' \
43// localhost:9090/command
44// =============================================================================
45
46var private CONFIRM_RESULT : string = "(no answer yet)"
47
48[export]
49def init() {
50 live_create_window("dasImgui popup_modal tutorial", 720, 480)
51 live_imgui_init(live_window)
52 let io & = unsafe(GetIO())
53 GetStyle().FontScaleMain = 1.4
54}
55
56[export]
57def update() {
58 if (!live_begin_frame()) return
59 begin_frame()
60
61 ImGui_ImplGlfw_NewFrame()
62 apply_synth_io_override()
63 NewFrame()
64
65 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
66 SetNextWindowSize(ImVec2(680.0f, 440.0f), ImGuiCond.Always)
67 window(PM_WIN, (text = "popup_modal tutorial", closable = false,
68 flags = ImGuiWindowFlags.None)) {
69
70 text("Two modals - a Yes/No confirm and a closable settings dialog.")
71 separator()
72
73 // ---- A: trigger the confirm modal ----
74 if (button(OPEN_CONFIRM, (text = "Delete file..."))) {
75 CONFIRM_MODAL.pending_open = true
76 }
77 same_line((spacing = 12.0f))
78 if (button(OPEN_SETTINGS, (text = "Open settings (X-button)"))) {
79 SETTINGS_MODAL.pending_open = true
80 }
81
82 separator()
83 text(STATUS_LINE, (text = "Last confirm answer: {CONFIRM_RESULT}"))
84 text("SETTINGS_MODAL.open (closable) = {SETTINGS_MODAL.open}")
85
86 // ---- Modal A: Yes/No confirm ----
87
88 // closable=false → no X-button; Yes/No buttons + ESC close it (ESC is an
89 // ImGui built-in — to disable it the app must intercept the key before
90 // NewFrame()).
91 popup_modal(CONFIRM_MODAL, (text = "Confirm delete",
92 closable = false,
93 flags = ImGuiWindowFlags.AlwaysAutoResize)) {
94 text("Are you sure you want to delete the selected file?")
95 separator()
96 if (button(CONFIRM_YES, (text = "Yes"))) {
97 CONFIRM_RESULT = "Yes"
98 CONFIRM_MODAL.pending_close = true
99 }
100 same_line((spacing = 12.0f))
101 if (button(CONFIRM_NO, (text = "No"))) {
102 CONFIRM_RESULT = "No"
103 CONFIRM_MODAL.pending_close = true
104 }
105 }
106
107 // ---- Modal B: closable settings ----
108 // closable=true → X-button on the title bar wired to state.open.
109 popup_modal(SETTINGS_MODAL, (text = "Settings",
110 closable = true,
111 flags = ImGuiWindowFlags.AlwaysAutoResize)) {
112 text("Background tasks")
113 separator()
114 checkbox(S_AUTOSAVE, (text = "Auto-save every 5 minutes"))
115 checkbox(S_TELEMETRY, (text = "Send anonymous telemetry"))
116 S_MAX_FPS.bounds = (30, 240)
117 slider_int(S_MAX_FPS, (text = "Max FPS cap"))
118 separator()
119 if (button(S_APPLY, (text = "Apply"))) {
120 SETTINGS_MODAL.pending_close = true
121 }
122 }
123 }
124
125 end_of_frame()
126 Render()
127 var w, h : int
128 live_get_framebuffer_size(w, h)
129 glViewport(0, 0, w, h)
130 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
131 glClear(GL_COLOR_BUFFER_BIT)
132 live_imgui_render()
133
134 live_end_frame()
135}
136
137[export]
138def shutdown() {
139 live_imgui_shutdown()
140 live_destroy_window()
141}
142
143[export]
144def main() {
145 init()
146 while (!exit_requested()) {
147 update()
148 }
149 shutdown()
150}
8.18.1.20.1.1. Requires
Already in the baseline boost layer:
imgui/imgui_containers_builtin—popup_modalpluswindow.imgui/imgui_widgets_builtin—button,checkbox,slider_int,text.
8.18.1.20.1.2. Confirm dialog (non-closable)
The classic “Yes/No before destructive action”:
popup_modal(CONFIRM_MODAL, (text = "Confirm delete",
closable = false,
flags = ImGuiWindowFlags.AlwaysAutoResize)) {
text("Are you sure you want to delete the selected file?")
separator()
if (button(CONFIRM_YES, (text = "Yes"))) {
CONFIRM_RESULT = "Yes"
CONFIRM_MODAL.pending_close = true
}
same_line()
if (button(CONFIRM_NO, (text = "No"))) {
CONFIRM_RESULT = "No"
CONFIRM_MODAL.pending_close = true
}
}
closable=false means there’s no X-button — but ESC still closes the
modal (ImGui’s built-in popup keybinding, unaffected by closable or
any ImGuiWindowFlags bit). The body’s Yes/No buttons are the
explicit answer paths: both write to CONFIRM_RESULT and set
pending_close. To make the modal truly non-dismissable, the app must
intercept the ESC key before NewFrame() and avoid issuing any
pending_close from app code — outside the scope of this tutorial.
The AlwaysAutoResize flag (passed via flags) sizes the modal to
its content — saves the caller from picking width/height by hand.
8.18.1.20.1.3. Closable settings modal
When the user might want to close without “saving”:
popup_modal(SETTINGS_MODAL, (text = "Settings",
closable = true,
flags = ImGuiWindowFlags.AlwaysAutoResize)) {
checkbox(S_AUTOSAVE, (text = "Auto-save every 5 minutes"))
checkbox(S_TELEMETRY, (text = "Send anonymous telemetry"))
slider_int(S_MAX_FPS, (text = "Max FPS cap"))
separator()
if (button(S_APPLY, (text = "Apply"))) {
SETTINGS_MODAL.pending_close = true
}
}
closable=true activates the X-button. The wrapper passes
&state.open to ImGui as p_open; ImGui flips it false on
X-click or ESC, the next frame’s BeginPopupModal returns false,
and the wrapper finalizes the close. SETTINGS_MODAL.open mirrors
ImGui’s view so the snapshot reports whether the modal is currently up.
8.18.1.20.1.4. popup vs popup_modal
Same machinery, different UX:
popup— clicks outside the popup close it (auto-close behavior). Useful for dropdowns, context menus, transient widgets.popup_modal— clicks outside are absorbed. The user MUST resolve the modal before doing anything else. Use for destructive confirms, multi-step wizards, blocking error dialogs.
The two share PopupState exactly — same open / flags /
pending_open / pending_close fields. Switching between forms
is a one-word edit.
8.18.1.20.1.5. Pending-flag channels
Three control surfaces converge on the same state:
App code —
MODAL.pending_open = truefrom a button handler or any event.Live commands —
imgui_open/imgui_closeagainst the registered path.Close button / ESC (closable only) — ImGui flips
state.open, the next frame’s wrapper applies the close.
The wrapper bridges them: pending_open=true triggers OpenPopup,
pending_close=true triggers CloseCurrentPopup inside the active
modal body, X-click flips open=false and the wrapper closes on
next frame.
8.18.1.20.1.6. Standalone vs live
Same convention as the other tutorials.
8.18.1.20.1.7. Driving from outside
The walkthrough above opens each modal by clicking its trigger button and dismisses it by clicking Yes / No / Apply — every gesture is a real click, self-verified by the modal body appearing or vanishing. The live commands below drive the same lifecycle directly, for remote or scripted control:
curl -X POST -d '{"name":"imgui_open","args":{"target":"PM_WIN/CONFIRM_MODAL"}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_click","args":{"target":"PM_WIN/CONFIRM_MODAL/CONFIRM_YES"}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_close","args":{"target":"PM_WIN/CONFIRM_MODAL"}}' \
localhost:9090/command
The path for body widgets composes off the modal IDENT — clicks against
PM_WIN/CONFIRM_MODAL/CONFIRM_YES resolve correctly only while the
modal is open (the body’s widgets aren’t in the registry otherwise).
See also
Full source: modules/dasImgui/examples/tutorial/popup_modal.das
Features-side demo: modules/dasImgui/examples/features/containers_overlay.das —
popup + popup_modal + tooltip overlay family showcase.
Sibling: Popup window — manual-trigger popup pattern for shared-str_id under PushID scopes.
Boost macros — the macro layer.