BeginMenu()/MenuItem(): fixed accidental triggering of child menu items when opening a menu inside a small host window forcing the child menu window to be repositioned under the mouse cursor. (#8233, #9394)

nb: ImGuiSelectableFlags_NoHoldingActiveID is not used anymore. Would remove remove once we remove the unnecessary call to Selectable() from MenuItem().
This commit is contained in:
ocornut
2026-05-11 15:32:39 +02:00
parent eb453f2be6
commit bca5a69928
2 changed files with 31 additions and 3 deletions
+7
View File
@@ -152,6 +152,13 @@ Other Changes:
items straying out of columns boundaries. (#7994, #2221) items straying out of columns boundaries. (#7994, #2221)
- Box-Select + Tables: fixed an issue when calling `BeginMultiSelect()` in a table - Box-Select + Tables: fixed an issue when calling `BeginMultiSelect()` in a table
before layout has been locked (first row or headers row submitted). (#8250) before layout has been locked (first row or headers row submitted). (#8250)
- Menus:
- BeginMenu()/MenuItem(): fixed accidental triggering of child menu items when
opening a menu inside a small host window forcing the child menu window to be
repositioned under the mouse cursor. (#8233, #9394)
Done by reworking BeginMenu()/MenuItem(): they previously avoiding taking
ActiveID + key/click ownership (in order to allow releasing button on another item).
Now they take them and release them once the mouse is moved outside item boundaries.
- Inputs: - Inputs:
- SetItemKeyOwner(): return true if ownership has been requested, which typically - SetItemKeyOwner(): return true if ownership has been requested, which typically
needs to to checked for gating further tests. This is important as the function needs to to checked for gating further tests. This is important as the function
+24 -3
View File
@@ -9391,8 +9391,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
bool pressed; bool pressed;
// We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups;
const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups;
ImGuiMenuColumns* offsets = &window->DC.MenuColumns; ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
{ {
@@ -9430,6 +9429,14 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
if (!enabled) if (!enabled)
EndDisabled(); EndDisabled();
// Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394)
// Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here.
if (g.ActiveId == id && g.HoveredId != id && g.ActiveIdSource == ImGuiInputSource_Mouse && IsMouseDragging(0))
{
ClearActiveID();
SetKeyOwner(ImGuiKey_MouseLeft, ImGuiKeyOwner_NoOwner);
}
const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav; const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav;
if (menuset_is_open) if (menuset_is_open)
PopItemFlag(); PopItemFlag();
@@ -9510,6 +9517,9 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
PopID(); PopID();
if (g.ActiveId == id && want_open)
g.ActiveIdNoClearOnFocusLoss = true;
if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
{ {
// Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame. // Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame.
@@ -9602,7 +9612,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
BeginDisabled(); BeginDisabled();
// We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover; const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover;
ImGuiMenuColumns* offsets = &window->DC.MenuColumns; ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
{ {
@@ -9645,6 +9655,17 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
RenderCheckMark(window->DrawList, text_pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f); RenderCheckMark(window->DrawList, text_pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);
} }
} }
// Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394)
// Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here.
const ImGuiID id = g.LastItemData.ID;
if (g.ActiveId == id && g.HoveredId != id && g.ActiveIdSource == ImGuiInputSource_Mouse && IsMouseDragging(0))
{
ClearActiveID();
SetKeyOwner(ImGuiKey_MouseLeft, ImGuiKeyOwner_NoOwner);
}
IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
if (!enabled) if (!enabled)
EndDisabled(); EndDisabled();