8.19.49. Right-click context popups
Two [container] rails that attach a right-click context menu to a
target. popup_context_item attaches to the previously submitted
item; popup_context_window attaches to the enclosing window.
Both are stateless_finalize — ImGui owns open/close internally,
the body runs only while the popup is visible.
popup_context_item(IDENT, (str_id = "..",
flags = ImGuiPopupFlags.MouseButtonRight)) {
<menu body>
}
popup_context_window(IDENT, (str_id = "..",
flags = ImGuiPopupFlags.MouseButtonRight)) {
<menu body>
}
Source: modules/dasImgui/examples/tutorial/popups.das.
8.19.49.1. Walkthrough
The recording drives both menus with real synthetic right-clicks and
clicks a menu item from each, self-verifying that the action fired
(PO_RENAME then PO_REFRESH flip to value = true). After each
right-click it waits for the menu to actually render before
travelling onto the item — never a fixed sleep guessing the popup is
open. To explore live, run the tutorial under daslang-live and
right-click the button / empty space yourself:
bin/Release/daslang-live modules/dasImgui/examples/tutorial/popups.das
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_visual_aids
19
20// =============================================================================
21// TUTORIAL: popups — two right-click context popup [container]s.
22//
23// popup_context_item(IDENT, (str_id = "..", — attaches to the
24// flags = ...)) { PREVIOUSLY submitted
25// <menu_item or any body> item; right-click
26// } THAT item to open.
27//
28// popup_context_window(IDENT, (str_id = "..", — attaches to the
29// flags = ...)) { enclosing window;
30// <menu_item or any body> right-click ANYWHERE
31// } inside opens.
32//
33// ImGui drives open/close internally — no pending_open / pending_close
34// fields on the state; the body runs only on the frames the popup is
35// visible. `str_id` is the popup's stack id; pass a unique non-empty
36// string per consumer (the C-default `"##ContextWindow"` fires only on
37// a NULL const char* which daslang's string binding doesn't expose).
38//
39// flags = ImGuiPopupFlags.MouseButtonRight is the conventional opener;
40// MouseButtonLeft / MouseButtonMiddle work too (rarely useful).
41//
42// The window popup ORs in NoOpenOverItems so a right-click ON the button
43// opens the ITEM menu, not the window menu. Without it the window popup
44// swallows every right-click inside the window, items included, and the
45// item menu can never open.
46//
47// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/popups.das
48// LIVE: daslang-live modules/dasImgui/examples/tutorial/popups.das
49// =============================================================================
50
51var private g_item_action : string = "(right-click the button)"
52var private g_win_action : string = "(right-click empty area)"
53
54[export]
55def init() {
56 live_create_window("dasImgui popups tutorial", 760, 560)
57 live_imgui_init(live_window)
58 let io & = unsafe(GetIO())
59 GetStyle().FontScaleMain = 1.4
60}
61
62[export]
63def update() {
64 if (!live_begin_frame()) return
65 begin_frame()
66
67 ImGui_ImplGlfw_NewFrame()
68 apply_synth_io_override()
69 NewFrame()
70
71 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
72 SetNextWindowSize(ImVec2(720.0f, 520.0f), ImGuiCond.Always)
73 window(PO_WIN, (text = "popups tutorial", closable = false,
74 flags = ImGuiWindowFlags.None)) {
75
76 text("Right-click context popups - two flavors.")
77 text(PO_HINT, (text = "popup_context_item attaches to the previous item; popup_context_window attaches to the enclosing window."))
78 separator()
79
80 // ---- Stage 1: popup_context_item — attached to a specific item ----
81 text("Stage 1 - right-click the target button below:")
82 button(PO_TARGET, (text = "Right-click me"))
83 popup_context_item(PO_CTX_ITEM, (str_id = "ctx_item",
84 flags = ImGuiPopupFlags.MouseButtonRight)) {
85 if (menu_item(PO_RENAME, (text = "Rename", shortcut = "F2"))) {
86 g_item_action = "Rename"
87 }
88 if (menu_item(PO_DELETE, (text = "Delete", shortcut = "Del"))) {
89 g_item_action = "Delete"
90 }
91 separator()
92 if (menu_item(PO_INFO, (text = "Info", shortcut = ""))) {
93 g_item_action = "Info"
94 }
95 }
96 text("last item action: {g_item_action}")
97 spacing()
98 separator()
99
100 // ---- Stage 2: popup_context_window — anywhere in the window ----
101 text("Stage 2 - right-click empty space (not over an item):")
102 text("NoOpenOverItems cedes item right-clicks to the item menu above.")
103 popup_context_window(PO_CTX_WIN, (str_id = "ctx_win",
104 flags = ImGuiPopupFlags.MouseButtonRight |
105 ImGuiPopupFlags.NoOpenOverItems)) {
106 if (menu_item(PO_REFRESH, (text = "Refresh", shortcut = "F5"))) {
107 g_win_action = "Refresh"
108 }
109 if (menu_item(PO_RESET, (text = "Reset", shortcut = ""))) {
110 g_win_action = "Reset"
111 }
112 separator()
113 if (menu_item(PO_ABOUT, (text = "About", shortcut = ""))) {
114 g_win_action = "About"
115 }
116 }
117 text("last window action: {g_win_action}")
118 }
119
120 end_of_frame()
121 Render()
122 var w, h : int
123 live_get_framebuffer_size(w, h)
124 glViewport(0, 0, w, h)
125 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
126 glClear(GL_COLOR_BUFFER_BIT)
127 live_imgui_render()
128
129 live_end_frame()
130}
131
132[export]
133def shutdown() {
134 live_imgui_shutdown()
135 live_destroy_window()
136}
137
138[export]
139def main() {
140 init()
141 while (!exit_requested()) {
142 update()
143 maybe_collect_gc()
144 }
145 shutdown()
146}
8.19.49.1.1. Requires
imgui/imgui_containers_builtin— both popup containers.imgui/imgui_widgets_builtin—button+menu_itemfor the target + menu rows.imgui/imgui_boost_runtime—PopupContextItemState/PopupContextWindowState(stateless markers).
8.19.49.1.2. popup_context_item — attached to an item
BeginPopupContextItem(str_id, flags) attaches the menu to whatever
item was most recently submitted. Submit the target first, then the
popup:
button(TARGET, (text = "Right-click me"))
popup_context_item(CTX, (str_id = "target_ctx",
flags = ImGuiPopupFlags.MouseButtonRight)) {
if (menu_item(RENAME, (text = "Rename", shortcut = "F2"))) {
g_item_action = "Rename"
}
if (menu_item(DELETE, (text = "Delete", shortcut = "Del"))) {
g_item_action = "Delete"
}
}
The order matters — popup_context_item calls IsItemHovered /
IsItemClicked internally against the previous item, so the
popup must come after the target.
str_id is the popup’s stack id, and must be non-empty. The
ImGui C-level default "##ContextItem" fires only on a NULL
const char*, which daslang’s string binding doesn’t expose. Pass
a unique non-empty string per consumer.
8.19.49.1.3. popup_context_window — attached to the window
Same shape; attaches to the enclosing window instead of a specific
item. When a window has both an item menu and a window menu, OR in
NoOpenOverItems so a right-click on an item opens that item’s menu
and only empty space opens the window menu:
window(MAIN_WIN, (text = "popups", closable = false,
flags = ImGuiWindowFlags.None)) {
button(TARGET, (text = "Right-click me"))
popup_context_item(ITEM_CTX, (str_id = "item_ctx",
flags = ImGuiPopupFlags.MouseButtonRight)) {
if (menu_item(RENAME, (text = "Rename", shortcut = "F2"))) {
g_item_action = "Rename"
}
}
popup_context_window(WIN_CTX, (str_id = "win_ctx",
flags = ImGuiPopupFlags.MouseButtonRight |
ImGuiPopupFlags.NoOpenOverItems)) {
if (menu_item(REFRESH, (text = "Refresh", shortcut = "F5"))) {
g_win_action = "Refresh"
}
}
}
Without NoOpenOverItems the window popup opens on every
right-click inside the window — the button’s right-click included — so
the item menu can never open. The flag tells the window popup to ignore
right-clicks that land on an item, ceding them to that item’s own
context menu. This is the idiomatic pairing whenever a window carries
both an item menu and a window-wide menu.
8.19.49.1.4. Flags
ImGuiPopupFlags carries the trigger options:
MouseButtonLeft/MouseButtonRight/MouseButtonMiddle— which button opens.Rightis the conventional default.MouseButtonMask_— any of the three.NoOpenOverItems— forpopup_context_window: don’t open when the right-click lands on an item, so item context menus win (see above).NoOpenOverExistingPopup— don’t open if a popup is already open.
8.19.49.1.5. State model — stateless_finalize
The states (PopupContextItemState / PopupContextWindowState)
carry no pending_open / pending_close fields. ImGui drives
open/close internally on actual right-click events; there’s no
imgui_open / imgui_close channel. The widget body runs only
on the frames the popup is visible, so any per-frame logic inside the
body is naturally gated.
The asymmetry vs the popup_modal family (which DOES have
pending_open) reflects the fact that context popups are
reactive to mouse events, while modals are proactive — you
imgui_open a modal from logic, you don’t imgui_open a context
menu.
See also
Full source: modules/dasImgui/examples/tutorial/popups.das
Modal sibling: Modal popups — proactive popup
driven by pending_open.
Manual-trigger sibling: Popup window — stateless
BeginPopup + caller-driven OpenPopup.
Features-side demo: modules/dasImgui/examples/features/popup_context_item.das.
Boost macros — the macro layer.