From 46e5f44ec8c2535415e6bc713e16d9baac9de78e Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 15 Jan 2024 16:56:29 +0100 Subject: [PATCH] Shortcut()/SetShortcutRouting(): use mixed current window focus scope + ParentWindowForFocusRoute. (#6798, #2637, #456) Amend d474836 Begin: tweak clearing of CurrentWindow as FocusWindow() relies on it now. Addded SetWindowParentWindowForFocusRoute() helper. --- imgui.cpp | 76 ++++++++++++++++++++++++++++++++++-------------- imgui_internal.h | 3 ++ 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index a6b92d44..e75b7d95 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6439,13 +6439,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } // Add to focus scope stack - if ((flags & ImGuiWindowFlags_NavFlattened) == 0) - PushFocusScope(window->ID); + PushFocusScope((flags & ImGuiWindowFlags_NavFlattened) ? g.CurrentFocusScopeId : window->ID); window->NavRootFocusScopeId = g.CurrentFocusScopeId; - // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() - g.CurrentWindow = NULL; - // Add to popup stack if (flags & ImGuiWindowFlags_Popup) { @@ -6510,6 +6506,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->Appearing) SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() + g.CurrentWindow = NULL; + // When reusing window again multiple times a frame, just append content (don't need to setup again) if (first_begin_of_the_frame) { @@ -7083,8 +7082,7 @@ void ImGui::End() if (window->DC.CurrentColumns) EndColumns(); PopClipRect(); // Inner window clip rectangle - if ((window->Flags & ImGuiWindowFlags_NavFlattened) == 0) - PopFocusScope(); + PopFocusScope(); // Stop logging if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging @@ -7209,7 +7207,7 @@ void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) g.NavMousePosDirty = true; g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId g.NavLayer = ImGuiNavLayer_Main; - g.NavFocusScopeId = window ? window->NavRootFocusScopeId : 0; + SetNavFocusScope(window ? window->NavRootFocusScopeId : 0); g.NavIdIsAlive = false; g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; @@ -7822,6 +7820,33 @@ void ImGui::PopFocusScope() g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back().ID : 0; } +void ImGui::SetNavFocusScope(ImGuiID focus_scope_id) +{ + ImGuiContext& g = *GImGui; + g.NavFocusScopeId = focus_scope_id; + g.NavFocusScopePath.resize(0); // Invalidate + if (focus_scope_id == 0) + return; + IM_ASSERT(g.NavWindow != NULL); + + // Store current path (in reverse order) + if (focus_scope_id == g.CurrentFocusScopeId) + { + // Top of focus stack contains local focus scopes inside current window + for (int n = g.FocusScopeStack.Size - 1; n >= 0 && g.FocusScopeStack.Data[n].WindowID == g.CurrentWindow->ID; n--) + g.NavFocusScopePath.push_back(g.FocusScopeStack.Data[n]); + } + else if (focus_scope_id == g.NavWindow->NavRootFocusScopeId) + g.NavFocusScopePath.push_back({ focus_scope_id, g.NavWindow->ID }); + else + return; + + // Then follow on manually set ParentWindowForFocusRoute field (#6798) + for (ImGuiWindow* window = g.NavWindow->ParentWindowForFocusRoute; window != NULL; window = window->ParentWindowForFocusRoute) + g.NavFocusScopePath.push_back({ window->NavRootFocusScopeId, window->ID }); + IM_ASSERT(g.NavFocusScopePath.Size < 100); // Maximum depth is technically 251 as per CalcRoutingScore(): 254 - 3 +} + // Focus = move navigation cursor, set scrolling, set focus window. void ImGui::FocusItem() { @@ -8318,12 +8343,11 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) // - 254: ImGuiInputFlags_RouteGlobalLow // - 255: never route // 'flags' should include an explicit routing policy -static int CalcRoutingScore(ImGuiWindow* location, ImGuiID owner_id, ImGuiInputFlags flags) +static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInputFlags flags) { if (flags & ImGuiInputFlags_RouteFocused) { ImGuiContext& g = *GImGui; - ImGuiWindow* focused = g.NavWindow; // ActiveID gets top priority // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it) @@ -8336,13 +8360,12 @@ static int CalcRoutingScore(ImGuiWindow* location, ImGuiID owner_id, ImGuiInputF // - When Window/ChildB is focused -> Window scores 4, Window/ChildB scores 3 (best) // Assuming only WindowA is submitting a routing request, // - When Window/ChildB is focused -> Window scores 4 (best), Window/ChildB doesn't have a score. - // FIXME: This could be later abstracted as a focus path - for (int next_score = 3; focused != NULL; next_score++, focused = focused->ParentWindowForFocusRoute) - if (focused == location) - { - IM_ASSERT(next_score < 255); - return next_score; - } + if (focus_scope_id == 0) + return 255; + for (int index_in_focus_path = 0; index_in_focus_path < g.NavFocusScopePath.Size; index_in_focus_path++) + if (g.NavFocusScopePath.Data[index_in_focus_path].ID == focus_scope_id) + return 3 + index_in_focus_path; + return 255; } @@ -8383,7 +8406,7 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI return true; } - const int score = CalcRoutingScore(g.CurrentWindow, owner_id, flags); + const int score = CalcRoutingScore(g.CurrentFocusScopeId, owner_id, flags); IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> score %d\n", GetKeyChordName(key_chord), owner_id, flags, score); if (score == 255) return false; @@ -11095,7 +11118,7 @@ void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id IM_ASSERT(nav_layer == ImGuiNavLayer_Main || nav_layer == ImGuiNavLayer_Menu); g.NavId = id; g.NavLayer = nav_layer; - g.NavFocusScopeId = focus_scope_id; + SetNavFocusScope(focus_scope_id); g.NavWindow->NavLastIds[nav_layer] = id; g.NavWindow->NavRectRel[nav_layer] = rect_rel; @@ -11117,7 +11140,7 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent; g.NavId = id; g.NavLayer = nav_layer; - g.NavFocusScopeId = g.CurrentFocusScopeId; + SetNavFocusScope(g.CurrentFocusScopeId); window->NavLastIds[nav_layer] = id; if (g.LastItemData.ID == id) window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect); @@ -11375,6 +11398,7 @@ static void ImGui::NavProcessItem() if (g.NavWindow != window) SetNavWindow(window); // Always refresh g.NavWindow, because some operations such as FocusItem() may not have a window. g.NavLayer = window->DC.NavLayerCurrent; + SetNavFocusScope(g.CurrentFocusScopeId); // Will set g.NavFocusScopeId AND store g.NavFocusScopePath g.NavFocusScopeId = g.CurrentFocusScopeId; g.NavIdIsAlive = true; if (g.LastItemData.InFlags & ImGuiItemFlags_HasSelectionUserData) @@ -11604,7 +11628,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) if (window->Flags & ImGuiWindowFlags_NoNavInputs) { g.NavId = 0; - g.NavFocusScopeId = window->NavRootFocusScopeId; + SetNavFocusScope(window->NavRootFocusScopeId); return; } @@ -11623,7 +11647,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) else { g.NavId = window->NavLastIds[0]; - g.NavFocusScopeId = window->NavRootFocusScopeId; + SetNavFocusScope(window->NavRootFocusScopeId); } } @@ -14507,6 +14531,14 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("NavActivateFlags: %04X", g.NavActivateFlags); Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId); + Text("NavFocusScopePath[] = "); + for (int path_n = g.NavFocusScopePath.Size - 1; path_n >= 0; path_n--) + { + const ImGuiFocusScopeData& focus_scope = g.NavFocusScopePath[path_n]; + SameLine(0.0f, 0.0f); + Text("0x%08X/", focus_scope.ID); + SetItemTooltip("In window \"%s\"", FindWindowByID(focus_scope.WindowID)->Name); + } Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL"); Unindent(); diff --git a/imgui_internal.h b/imgui_internal.h index 50b3e3d6..2d4d4c63 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2003,6 +2003,7 @@ struct ImGuiContext ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' ImGuiID NavId; // Focused item for navigation ImGuiID NavFocusScopeId; // Focused focus scope (e.g. selection code often wants to "clear other items" when landing on an item of the same scope) + ImVector NavFocusScopePath; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId) ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem() ImGuiID NavActivateDownId; // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) @@ -2952,6 +2953,7 @@ namespace ImGui IMGUI_API void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond = 0); IMGUI_API void SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size); IMGUI_API void SetWindowHiddenAndSkipItemsForCurrentFrame(ImGuiWindow* window); + inline void SetWindowParentWindowForFocusRoute(ImGuiWindow* window, ImGuiWindow* parent_window) { window->ParentWindowForFocusRoute = parent_window; } inline ImRect WindowRectAbsToRel(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); } 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); } inline ImVec2 WindowPosRelToAbs(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x + off.x, p.y + off.y); } @@ -3110,6 +3112,7 @@ namespace ImGui IMGUI_API void NavUpdateCurrentWindowIsScrollPushableX(); IMGUI_API void SetNavWindow(ImGuiWindow* window); IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel); + IMGUI_API void SetNavFocusScope(ImGuiID focus_scope_id); // Focus/Activation // This should be part of a larger set of API: FocusItem(offset = -1), FocusItemByID(id), ActivateItem(offset = -1), ActivateItemByID(id) etc. which are