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.
This commit is contained in:
ocornut 2024-01-15 16:56:29 +01:00
parent e0c8c80ada
commit 46e5f44ec8
2 changed files with 57 additions and 22 deletions

View File

@ -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();

View File

@ -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<ImGuiFocusScopeData> 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