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: examples/tutorial/popup_modal.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: popup_modal — blocking modal dialog with optional X-button.
20//
21// popup_modal(IDENT, (text = "title", closable = bool,
22// flags = ImGuiWindowFlags....)) { body }
23//
24// Lifecycle is identical to popup() — OpenPopup + BeginPopupModal/EndPopup,
25// driven by state.pending_open / pending_close — but ImGui ALSO:
26//
27// 1. Dims the parent window — visual backdrop says "deal with this first".
28// 2. Blocks input outside the modal — clicks elsewhere are absorbed, not
29// passed through. ESC closes the modal (vanilla ImGui behavior).
30// 3. With closable=true, renders an X-button wired to state.open.
31//
32// The boost wrapper threads three control surfaces through state.pending_*:
33// - app code — `MODAL_X.pending_open = true`
34// - live commands — imgui_open / imgui_close
35// - close-button — ImGui flips state.open when X clicked or ESC pressed
36//
37// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/popup_modal.das
38// LIVE: daslang-live modules/dasImgui/examples/tutorial/popup_modal.das
39//
40// DRIVE (when running live):
41// curl -X POST -d '{"name":"imgui_open","args":{"target":"PM_WIN/CONFIRM_MODAL"}}' \
42// localhost:9090/command
43// =============================================================================
44
45var private CONFIRM_RESULT : string = "(no answer yet)"
46
47[export]
48def init() {
49 live_create_window("dasImgui popup_modal tutorial", 720, 480)
50 live_imgui_init(live_window)
51 let io & = unsafe(GetIO())
52 GetStyle().FontScaleMain = 1.4
53}
54
55[export]
56def update() {
57 if (!live_begin_frame()) return
58 begin_frame()
59
60 ImGui_ImplGlfw_NewFrame()
61 apply_synth_io_override()
62 NewFrame()
63
64 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
65 SetNextWindowSize(ImVec2(680.0f, 440.0f), ImGuiCond.Always)
66 window(PM_WIN, (text = "popup_modal tutorial", closable = false,
67 flags = ImGuiWindowFlags.None)) {
68
69 text("Two modals - a Yes/No confirm and a closable settings dialog.")
70 separator()
71
72 // ---- A: trigger the confirm modal ----
73 if (button(OPEN_CONFIRM, (text = "Delete file..."))) {
74 CONFIRM_MODAL.pending_open = true
75 }
76 same_line((spacing = 12.0f))
77 if (button(OPEN_SETTINGS, (text = "Open settings (X-button)"))) {
78 SETTINGS_MODAL.pending_open = true
79 }
80
81 separator()
82 text(STATUS_LINE, (text = "Last confirm answer: {CONFIRM_RESULT}"))
83 text("SETTINGS_MODAL.open (closable) = {SETTINGS_MODAL.open}")
84
85 // ---- Modal A: Yes/No confirm ----
86 // closable=false → no X-button. Yes/No buttons + ESC are the close
87 // paths (ESC is an ImGui built-in for popups; to disable it the app
88 // would need to intercept the keystroke before NewFrame()).
89 popup_modal(CONFIRM_MODAL, (text = "Confirm delete",
90 closable = false,
91 flags = ImGuiWindowFlags.AlwaysAutoResize)) {
92 text("Are you sure you want to delete the selected file?")
93 separator()
94 if (button(CONFIRM_YES, (text = "Yes"))) {
95 CONFIRM_RESULT = "Yes"
96 CONFIRM_MODAL.pending_close = true
97 }
98 same_line((spacing = 12.0f))
99 if (button(CONFIRM_NO, (text = "No"))) {
100 CONFIRM_RESULT = "No"
101 CONFIRM_MODAL.pending_close = true
102 }
103 }
104
105 // ---- Modal B: closable settings ----
106 // closable=true → X-button on the title bar wired to state.open.
107 popup_modal(SETTINGS_MODAL, (text = "Settings",
108 closable = true,
109 flags = ImGuiWindowFlags.AlwaysAutoResize)) {
110 text("Background tasks")
111 separator()
112 checkbox(S_AUTOSAVE, (text = "Auto-save every 5 minutes"))
113 checkbox(S_TELEMETRY, (text = "Send anonymous telemetry"))
114 S_MAX_FPS.bounds = (30, 240)
115 slider_int(S_MAX_FPS, (text = "Max FPS cap"))
116 separator()
117 if (button(S_APPLY, (text = "Apply"))) {
118 SETTINGS_MODAL.pending_close = true
119 }
120 }
121 }
122
123 end_of_frame()
124 Render()
125 var w, h : int
126 live_get_framebuffer_size(w, h)
127 glViewport(0, 0, w, h)
128 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
129 glClear(GL_COLOR_BUFFER_BIT)
130 live_imgui_render()
131
132 live_end_frame()
133}
134
135[export]
136def shutdown() {
137 live_imgui_shutdown()
138 live_destroy_window()
139}
140
141[export]
142def main() {
143 init()
144 while (!exit_requested()) {
145 update()
146 }
147 shutdown()
148}
Requires
Already in the baseline boost layer:
imgui/imgui_containers_builtin—popup_modalpluswindow.imgui/imgui_widgets_builtin—button,checkbox,slider_int,text.
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.
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.
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.
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.
Standalone vs live
Same convention as the other tutorials.
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: examples/tutorial/popup_modal.das
Features-side demo: 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.