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: examples/tutorial/popups.das.

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
  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: popups — two right-click context popup [container]s.
 20//
 21//   popup_context_item(IDENT, (str_id = "..",          — attaches to the
 22//                              flags = ...)) {           PREVIOUSLY submitted
 23//       <menu_item or any body>                          item; right-click
 24//   }                                                    THAT item to open.
 25//
 26//   popup_context_window(IDENT, (str_id = "..",        — attaches to the
 27//                                flags = ...)) {         enclosing window;
 28//       <menu_item or any body>                          right-click ANYWHERE
 29//   }                                                    inside opens.
 30//
 31// ImGui drives open/close internally — no pending_open / pending_close
 32// fields on the state; the body runs only on the frames the popup is
 33// visible. `str_id` is the popup's stack id; pass a unique non-empty
 34// string per consumer (the C-default `"##ContextWindow"` fires only on
 35// a NULL const char* which daslang's string binding doesn't expose).
 36//
 37// flags = ImGuiPopupFlags.MouseButtonRight is the conventional opener;
 38// MouseButtonLeft / MouseButtonMiddle work too (rarely useful).
 39//
 40// The window popup ORs in NoOpenOverItems so a right-click ON the button
 41// opens the ITEM menu, not the window menu. Without it the window popup
 42// swallows every right-click inside the window, items included, and the
 43// item menu can never open.
 44//
 45// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/popups.das
 46// LIVE:       daslang-live modules/dasImgui/examples/tutorial/popups.das
 47// =============================================================================
 48
 49var private g_item_action : string = "(right-click the button)"
 50var private g_win_action  : string = "(right-click empty area)"
 51
 52[export]
 53def init() {
 54    live_create_window("dasImgui popups tutorial", 760, 560)
 55    live_imgui_init(live_window)
 56    let io & = unsafe(GetIO())
 57    GetStyle().FontScaleMain = 1.4
 58}
 59
 60[export]
 61def update() {
 62    if (!live_begin_frame()) return
 63    begin_frame()
 64
 65    ImGui_ImplGlfw_NewFrame()
 66    apply_synth_io_override()
 67    NewFrame()
 68
 69    SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
 70    SetNextWindowSize(ImVec2(720.0f, 520.0f), ImGuiCond.Always)
 71    window(PO_WIN, (text = "popups tutorial", closable = false,
 72                    flags = ImGuiWindowFlags.None)) {
 73
 74        text("Right-click context popups - two flavors.")
 75        text(PO_HINT, (text = "popup_context_item attaches to the previous item; popup_context_window attaches to the enclosing window."))
 76        separator()
 77
 78        // ---- Stage 1: popup_context_item — attached to a specific item ----
 79        text("Stage 1 - right-click the target button below:")
 80        button(PO_TARGET, (text = "Right-click me"))
 81        popup_context_item(PO_CTX_ITEM, (str_id = "ctx_item",
 82                                          flags = ImGuiPopupFlags.MouseButtonRight)) {
 83            if (menu_item(PO_RENAME, (text = "Rename", shortcut = "F2"))) {
 84                g_item_action = "Rename"
 85            }
 86            if (menu_item(PO_DELETE, (text = "Delete", shortcut = "Del"))) {
 87                g_item_action = "Delete"
 88            }
 89            separator()
 90            if (menu_item(PO_INFO, (text = "Info", shortcut = ""))) {
 91                g_item_action = "Info"
 92            }
 93        }
 94        text("last item action: {g_item_action}")
 95        spacing()
 96        separator()
 97
 98        // ---- Stage 2: popup_context_window — anywhere in the window ----
 99        text("Stage 2 - right-click empty space (not over an item):")
100        text("NoOpenOverItems cedes item right-clicks to the item menu above.")
101        popup_context_window(PO_CTX_WIN, (str_id = "ctx_win",
102                                           flags = ImGuiPopupFlags.MouseButtonRight |
103                                                   ImGuiPopupFlags.NoOpenOverItems)) {
104            if (menu_item(PO_REFRESH, (text = "Refresh", shortcut = "F5"))) {
105                g_win_action = "Refresh"
106            }
107            if (menu_item(PO_RESET, (text = "Reset", shortcut = ""))) {
108                g_win_action = "Reset"
109            }
110            separator()
111            if (menu_item(PO_ABOUT, (text = "About", shortcut = ""))) {
112                g_win_action = "About"
113            }
114        }
115        text("last window action: {g_win_action}")
116    }
117
118    end_of_frame()
119    Render()
120    var w, h : int
121    live_get_framebuffer_size(w, h)
122    glViewport(0, 0, w, h)
123    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
124    glClear(GL_COLOR_BUFFER_BIT)
125    live_imgui_render()
126
127    live_end_frame()
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    }
142    shutdown()
143}

Requires

  • imgui/imgui_containers_builtin — both popup containers.

  • imgui/imgui_widgets_builtinbutton + menu_item for the target + menu rows.

  • imgui/imgui_boost_runtimePopupContextItemState / PopupContextWindowState (stateless markers).

Flags

ImGuiPopupFlags carries the trigger options:

  • MouseButtonLeft / MouseButtonRight / MouseButtonMiddle — which button opens. Right is the conventional default.

  • MouseButtonMask_ — any of the three.

  • NoOpenOverItems — for popup_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.

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: 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: examples/features/popup_context_item.das.

Boost macros — the macro layer.