From d31fe97f741ae57600cc0762804eb5fcf79cbc97 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2020 16:50:28 +0200 Subject: [PATCH] Popups: Fix an edge case where programatically closing a popup while clicking on its empty space would attempt to focus it and close other popups. (#2880) --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 36 ++++++++++++++++++++++++++---------- imgui_internal.h | 3 ++- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 2d70bc8db56f..ad078258414f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -52,6 +52,8 @@ Other Changes: Set to 0.0f (default) to always make a close button appear on hover (same as Chrome, VS). Set to FLT_MAX to only display a close button when selected (merely hovering is not enough). Set to an intermediary value to toggle behavior based on width (same as Firefox). +- Popups: Fix an edge case where programatically closing a popup while clicking on its empty space + would attempt to focus it and close other popups. (#2880) - Fix GetGlyphRangesKorean() end-range to end at 0xD7A3 (instead of 0xD79D). (#348, #3217) [@marukrap] - Metrics: Added a "Settings" section with some details about persistent ini settings. - Nav, Menus: Fix vertical wrap-around in menus or popups created with multiple appending calls to diff --git a/imgui.cpp b/imgui.cpp index 8c0afd067b09..07c2ea10cff4 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3449,17 +3449,22 @@ void ImGui::UpdateMouseMovingWindowEndFrame() if (g.NavWindow && g.NavWindow->Appearing) return; - // Click to focus window and start moving (after we're done with all our widgets) + // Click on empty space to focus window and start moving (after we're done with all our widgets) if (g.IO.MouseClicked[0]) { - if (g.HoveredRootWindow != NULL) + // Handle the edge case of a popup being closed while clicking in its empty space. + // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more. + ImGuiWindow* root_window = g.HoveredRootWindow; + const bool is_closed_popup = root_window && (root_window->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpenAtAnyLevel(root_window->PopupId); + + if (root_window != NULL && !is_closed_popup) { StartMouseMovingWindow(g.HoveredWindow); - if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(g.HoveredRootWindow->Flags & ImGuiWindowFlags_NoTitleBar)) - if (!g.HoveredRootWindow->TitleBarRect().Contains(g.IO.MouseClickedPos[0])) + if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(root_window->Flags & ImGuiWindowFlags_NoTitleBar)) + if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0])) g.MovingWindow = NULL; } - else if (g.NavWindow != NULL && GetTopMostPopupModal() == NULL) + else if (root_window != NULL && g.NavWindow != NULL && GetTopMostPopupModal() == NULL) { // Clicking on void disable focus FocusWindow(NULL); @@ -3700,7 +3705,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) { if (g.IO.MouseClicked[i]) - g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty()); + g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (g.OpenPopupStack.Size > 0); mouse_any_down |= g.IO.MouseDown[i]; if (g.IO.MouseDown[i]) if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down]) @@ -3718,7 +3723,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() if (g.WantCaptureMouseNextFrame != -1) g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0); else - g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty()); + g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (g.OpenPopupStack.Size > 0); // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to imgui, false = dispatch keyboard info to Dear ImGui + app) if (g.WantCaptureKeyboardNextFrame != -1) @@ -7610,6 +7615,15 @@ bool ImGui::IsPopupOpen(const char* str_id) return g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id); } +bool ImGui::IsPopupOpenAtAnyLevel(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + for (int n = 0; n < g.OpenPopupStack.Size; n++) + if (g.OpenPopupStack[n].PopupId == id) + return true; + return false; +} + ImGuiWindow* ImGui::GetTopMostPopupModal() { ImGuiContext& g = *GImGui; @@ -7661,8 +7675,8 @@ void ImGui::OpenPopupEx(ImGuiID id) else { // Close child popups if any, then flag popup for open/reopen - g.OpenPopupStack.resize(current_stack_size + 1); - g.OpenPopupStack[current_stack_size] = popup_ref; + ClosePopupToLevel(current_stack_size, false); + g.OpenPopupStack.push_back(popup_ref); } // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow(). @@ -7675,7 +7689,7 @@ void ImGui::OpenPopupEx(ImGuiID id) void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup) { ImGuiContext& g = *GImGui; - if (g.OpenPopupStack.empty()) + if (g.OpenPopupStack.Size == 0) return; // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. @@ -7714,6 +7728,8 @@ void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_ { ImGuiContext& g = *GImGui; IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size); + + // Trim open popup stack ImGuiWindow* focus_window = g.OpenPopupStack[remaining].SourceWindow; ImGuiWindow* popup_window = g.OpenPopupStack[remaining].Window; g.OpenPopupStack.resize(remaining); diff --git a/imgui_internal.h b/imgui_internal.h index f8ec2fdff413..3e88b0546675 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1581,7 +1581,7 @@ struct IMGUI_API ImGuiWindow bool WantCollapseToggle; bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) bool Appearing; // Set during the frame where the window is appearing (or re-appearing) - bool Hidden; // Do not display (== (HiddenFrames*** > 0)) + bool Hidden; // Do not display (== HiddenFrames*** > 0) bool IsFallbackWindow; // Set on the "Debug##Default" window. bool HasCloseButton; // Set when the window has a close button (p_open != NULL) signed char ResizeBorderHeld; // Current border being held for resize (-1: none, otherwise 0-3) @@ -1853,6 +1853,7 @@ namespace ImGui IMGUI_API void ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup); IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup); IMGUI_API bool IsPopupOpen(ImGuiID id); // Test for id at current popup stack level (currently begin-ed into); this doesn't scan the whole popup stack! + IMGUI_API bool IsPopupOpenAtAnyLevel(ImGuiID id); IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, ImGuiTooltipFlags tooltip_flags); IMGUI_API ImGuiWindow* GetTopMostPopupModal();