From 30eceaf95fe227a466bc5cd78afde1ac8e1b07ca Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 21 Apr 2023 17:30:05 +0200 Subject: [PATCH] Focus: start moving modal check into FocusWindow(), add ImGuiFocusRequestFlags_UnlessBelowModal (currently opt-in, should try to make opt-out). (#6357, #4317) --- imgui.cpp | 33 ++++++++++++++++++++++----------- imgui_internal.h | 15 +++++++++++++-- imgui_widgets.cpp | 2 +- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index c892a00c5..844932913 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1056,7 +1056,6 @@ static void RenderWindowDecorations(ImGuiWindow* window, const ImRec static void RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open); static void RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col); static void RenderDimmedBackgrounds(); -static ImGuiWindow* FindBlockingModal(ImGuiWindow* window); // Viewports static void UpdateViewportsNewFrame(); @@ -3911,7 +3910,7 @@ bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flag // Inhibit hover unless the window is within the stack of our modal/popup if (want_inhibit) - if (!ImGui::IsWindowWithinBeginStackOf(window->RootWindow, focused_root_window)) + if (!IsWindowWithinBeginStackOf(window->RootWindow, focused_root_window)) return false; } return true; @@ -4296,10 +4295,10 @@ void ImGui::UpdateMouseMovingWindowEndFrame() if (g.HoveredIdDisabled) g.MovingWindow = NULL; } - else if (root_window == NULL && g.NavWindow != NULL && GetTopMostPopupModal() == NULL) + else if (root_window == NULL && g.NavWindow != NULL) { // Clicking on void disable focus - FocusWindow(NULL); + FocusWindow(NULL, ImGuiFocusRequestFlags_UnlessBelowModal); } } @@ -4624,7 +4623,7 @@ void ImGui::NewFrame() // Closing the focused window restore focus to the first active root window in descending z-order if (g.NavWindow && !g.NavWindow->WasActive) - FocusTopMostWindowUnderOne(NULL, NULL, NULL); + FocusTopMostWindowUnderOne(NULL, NULL, NULL, ImGuiFocusRequestFlags_None); // No window should be open at the beginning of the frame. // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. @@ -6077,7 +6076,7 @@ void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags // - Window // .. returns Modal2 // - Window // .. returns Modal2 // - Modal2 // .. returns Modal2 -static ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) +ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) { ImGuiContext& g = *GImGui; if (g.OpenPopupStack.Size <= 0) @@ -6091,6 +6090,8 @@ static ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) continue; if (!popup_window->Active && !popup_window->WasActive) // Check WasActive, because this code may run before popup renders on current frame, also check Active to handle newly created windows. continue; + if (window == NULL) // FindBlockingModal(NULL) test for if FocusWindow(NULL) is naturally possible via a mouse click. + return popup_window; if (IsWindowWithinBeginStackOf(window, popup_window)) // Window is rendered over last modal, no render order change needed. break; for (ImGuiWindow* parent = popup_window->ParentWindowInBeginStack->RootWindow; parent != NULL; parent = parent->ParentWindowInBeginStack->RootWindow) @@ -6928,10 +6929,19 @@ int ImGui::FindWindowDisplayIndex(ImGuiWindow* window) } // Moving window to front of display and set focus (which happens to be back of our sorted list) -void ImGui::FocusWindow(ImGuiWindow* window) +void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) { ImGuiContext& g = *GImGui; + // Modal check? + if (flags & ImGuiFocusRequestFlags_UnlessBelowModal) + if (ImGuiWindow* blocking_modal = FindBlockingModal(window)) + { + IMGUI_DEBUG_LOG_FOCUS("[focus] FocusWindow(\"%s\", UnlessBelowModal): prevented by \"%s\".\n", window ? window->Name : "", blocking_modal->Name); + return; + } + + // Apply focus if (g.NavWindow != window) { SetNavWindow(window); @@ -6968,7 +6978,7 @@ void ImGui::FocusWindow(ImGuiWindow* window) BringWindowToDisplayFront(display_front_window); } -void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport) +void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags) { ImGuiContext& g = *GImGui; IM_UNUSED(filter_viewport); // Unused in master branch. @@ -6994,11 +7004,11 @@ void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWind if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) { ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(window); - FocusWindow(focus_window); + FocusWindow(focus_window, flags); return; } } - FocusWindow(NULL); + FocusWindow(NULL, flags); } // Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only. @@ -10101,6 +10111,7 @@ bool ImGui::IsPopupOpen(const char* str_id, ImGuiPopupFlags popup_flags) return IsPopupOpen(id, popup_flags); } +// FIXME: In principle we should converge toward replacing calls to GetTopMostPopupModal() + IsWindowWithinBeginStackOf() with calls to FindBlockingModal() ImGuiWindow* ImGui::GetTopMostPopupModal() { ImGuiContext& g = *GImGui; @@ -10263,7 +10274,7 @@ void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_ if (focus_window && !focus_window->WasActive && popup_window) { // Fallback - FocusTopMostWindowUnderOne(popup_window, NULL, NULL); + FocusTopMostWindowUnderOne(popup_window, NULL, NULL, ImGuiFocusRequestFlags_None); } else { diff --git a/imgui_internal.h b/imgui_internal.h index a74572be7..5e71419b1 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -164,6 +164,7 @@ typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // E // Flags typedef int ImGuiActivateFlags; // -> enum ImGuiActivateFlags_ // Flags: for navigation/focus function (will be for ActivateItem() later) typedef int ImGuiDebugLogFlags; // -> enum ImGuiDebugLogFlags_ // Flags: for ShowDebugLogWindow(), g.DebugLogFlags +typedef int ImGuiFocusRequestFlags; // -> enum ImGuiFocusRequestFlags_ // Flags: for FocusWindow(); typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for IsKeyPressed(), IsMouseClicked(), SetKeyOwner(), SetItemKeyOwner() etc. typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), g.LastItemData.InFlags typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for g.LastItemData.StatusFlags @@ -906,6 +907,15 @@ enum ImGuiSeparatorFlags_ ImGuiSeparatorFlags_SpanAllColumns = 1 << 2, }; +// Flags for FocusWindow(). This is not called ImGuiFocusFlags to avoid confusion with public-facing ImGuiFocusedFlags. +// FIXME: Once we finishing replacing more uses of GetTopMostPopupModal()+IsWindowWithinBeginStackOf() +// and FindBlockingModal() with this, we may want to change the flag to be opt-out instead of opt-in. +enum ImGuiFocusRequestFlags_ +{ + ImGuiFocusRequestFlags_None = 0, + ImGuiFocusRequestFlags_UnlessBelowModal = 1 << 0, // Do not set focus if the window is below a modal. +}; + enum ImGuiTextFlags_ { ImGuiTextFlags_None = 0, @@ -2751,8 +2761,8 @@ namespace ImGui inline ImRect WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); } // Windows: Display Order and Focus Order - IMGUI_API void FocusWindow(ImGuiWindow* window); - IMGUI_API void FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport); + IMGUI_API void FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags = 0); + IMGUI_API void FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags); IMGUI_API void BringWindowToFocusFront(ImGuiWindow* window); IMGUI_API void BringWindowToDisplayFront(ImGuiWindow* window); IMGUI_API void BringWindowToDisplayBack(ImGuiWindow* window); @@ -2872,6 +2882,7 @@ namespace ImGui IMGUI_API ImRect GetPopupAllowedExtentRect(ImGuiWindow* window); IMGUI_API ImGuiWindow* GetTopMostPopupModal(); IMGUI_API ImGuiWindow* GetTopMostAndVisiblePopupModal(); + IMGUI_API ImGuiWindow* FindBlockingModal(ImGuiWindow* window); IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window); IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 337e01580..fb445b99b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7084,7 +7084,7 @@ void ImGui::EndMainMenuBar() // FIXME: With this strategy we won't be able to restore a NULL focus. ImGuiContext& g = *GImGui; if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest) - FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL); + FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal); End(); }