Skip to content

Commit 88a3545

Browse files
committed
Nav: It's now possible to navigate sibling of a menu-bar while navigating inside one of their child. If a Left<>Right navigation request fails to find a match we forward the request to the root menu. (#787, #126)
Currently the sibling menu is isn't automatically opened, that's still left to it (and even that can be anoying in Windows when the first menu-item is a child menu)
1 parent 587e637 commit 88a3545

File tree

2 files changed

+56
-11
lines changed

2 files changed

+56
-11
lines changed

imgui.cpp

+49-7
Original file line numberDiff line numberDiff line change
@@ -2634,6 +2634,15 @@ static void NavUpdate()
26342634
g.NavMoveFromClampedRefRect = false;
26352635
}
26362636

2637+
// When a forwarded move request failed, we restore the highlight that we disabled during the forward frame
2638+
if (g.NavMoveRequestForwardStep == 2)
2639+
{
2640+
IM_ASSERT(g.NavMoveRequest);
2641+
if (g.NavMoveResultId == 0)
2642+
g.NavDisableHighlight = false;
2643+
g.NavMoveRequestForwardStep = 0;
2644+
}
2645+
26372646
// Apply application mouse position movement, after we had a chance to process move request result.
26382647
if (g.NavMousePosDirty && g.NavIdIsAlive)
26392648
{
@@ -2781,14 +2790,24 @@ static void NavUpdate()
27812790

27822791
// Initiate directional inputs request
27832792
const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags;
2784-
g.NavMoveDir = ImGuiDir_None;
2785-
if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
2793+
if (g.NavMoveRequestForwardStep == 0)
27862794
{
2787-
if ((allowed_dir_flags & (1<<ImGuiDir_Left)) && IsNavInputPressed(ImGuiNavInput_PadLeft, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Left;
2788-
if ((allowed_dir_flags & (1<<ImGuiDir_Right)) && IsNavInputPressed(ImGuiNavInput_PadRight, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Right;
2789-
if ((allowed_dir_flags & (1<<ImGuiDir_Up)) && IsNavInputPressed(ImGuiNavInput_PadUp, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Up;
2790-
if ((allowed_dir_flags & (1<<ImGuiDir_Down)) && IsNavInputPressed(ImGuiNavInput_PadDown, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Down;
2795+
g.NavMoveDir = ImGuiDir_None;
2796+
if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
2797+
{
2798+
if ((allowed_dir_flags & (1<<ImGuiDir_Left)) && IsNavInputPressed(ImGuiNavInput_PadLeft, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Left;
2799+
if ((allowed_dir_flags & (1<<ImGuiDir_Right)) && IsNavInputPressed(ImGuiNavInput_PadRight, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Right;
2800+
if ((allowed_dir_flags & (1<<ImGuiDir_Up)) && IsNavInputPressed(ImGuiNavInput_PadUp, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Up;
2801+
if ((allowed_dir_flags & (1<<ImGuiDir_Down)) && IsNavInputPressed(ImGuiNavInput_PadDown, ImGuiNavReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Down;
2802+
}
27912803
}
2804+
else
2805+
{
2806+
IM_ASSERT(g.NavMoveDir != ImGuiDir_None);
2807+
IM_ASSERT(g.NavMoveRequestForwardStep == 1);
2808+
g.NavMoveRequestForwardStep = 2;
2809+
}
2810+
27922811
if (g.NavMoveDir != ImGuiDir_None)
27932812
{
27942813
g.NavMoveRequest = true;
@@ -2834,9 +2853,10 @@ static void NavUpdate()
28342853
// Reset search
28352854
g.NavMoveResultId = 0;
28362855
g.NavMoveResultDistAxial = g.NavMoveResultDistBox = g.NavMoveResultDistCenter = FLT_MAX;
2856+
2857+
// When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we clamp its bounding box (used for search) to the visible area to restart navigation within visible items
28372858
if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0)
28382859
{
2839-
// When we have manually scrolled and NavId is out of bounds, we clamp its bounding box (used for search) to the visible area to restart navigation within visible items
28402860
ImGuiWindow* window = g.NavWindow;
28412861
ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1,1), window->InnerRect.Max - window->Pos + ImVec2(1,1));
28422862
if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
@@ -5231,6 +5251,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us
52315251
window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
52325252
window->DC.ChildWindows.resize(0);
52335253
window->DC.LayoutType = ImGuiLayoutType_Vertical;
5254+
window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
52345255
window->DC.ItemFlags = ImGuiItemFlags_Default_;
52355256
window->DC.ItemWidth = window->ItemWidthDefault;
52365257
window->DC.TextWrapPos = -1.0f; // disabled
@@ -9966,6 +9987,27 @@ void ImGui::EndMenuBar()
99669987
ImGuiWindow* window = GetCurrentWindow();
99679988
if (window->SkipItems)
99689989
return;
9990+
ImGuiContext& g = *GImGui;
9991+
9992+
// When a move request within one of our child menu failed, capture the request to navigate among our siblings.
9993+
if (g.NavMoveRequest && g.NavMoveResultId == 0 && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right))
9994+
{
9995+
ImGuiWindow* nav_earliest_child = g.NavWindow;
9996+
while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
9997+
nav_earliest_child = nav_earliest_child->ParentWindow;
9998+
if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForwardStep == 0)
9999+
{
10000+
// To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
10001+
// This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
10002+
IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
10003+
FocusWindow(window);
10004+
SetNavIdAndMoveMouse(window->NavLastIds[1], 1, window->NavRectRel[1]);
10005+
g.NavLayer = 1;
10006+
g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
10007+
g.NavMoveRequest = false;
10008+
g.NavMoveRequestForwardStep = 1;
10009+
}
10010+
}
996910011

997010012
IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
997110013
IM_ASSERT(window->DC.MenuBarAppending);

imgui_internal.h

+7-4
Original file line numberDiff line numberDiff line change
@@ -473,8 +473,9 @@ struct ImGuiContext
473473
ImGuiID NavInitDefaultResultId;
474474
ImRect NavInitDefaultResultRectRel;
475475
bool NavInitDefaultResultExplicit; // Whether the result was explicitly requested with SetItemDefaultFocus()
476-
bool NavMoveRequest; // Move request for this frame
477476
bool NavMoveFromClampedRefRect; // Set by manual scrolling, if we scroll to a point where NavId isn't visible we reset navigation from visible items
477+
bool NavMoveRequest; // Move request for this frame
478+
int NavMoveRequestForwardStep; // 0: no forward, 1: forward request, 2: forward result
478479
ImGuiDir NavMoveDir; // West/East/North/South
479480
ImGuiDir NavMoveDirLast; //
480481
ImGuiID NavMoveResultId; // Best move request candidate
@@ -585,6 +586,7 @@ struct ImGuiContext
585586
NavInitDefaultResultExplicit = false;
586587
NavMoveFromClampedRefRect = false;
587588
NavMoveRequest = false;
589+
NavMoveRequestForwardStep = 0;
588590
NavMoveDir = NavMoveDirLast = ImGuiDir_None;
589591
NavMoveResultId = 0;
590592
NavMoveResultDistBox = NavMoveResultDistCenter = NavMoveResultDistAxial = 0.0f;
@@ -663,15 +665,16 @@ struct IMGUI_API ImGuiDrawContext
663665
ImGuiID LastItemId;
664666
ImRect LastItemRect;
665667
bool LastItemRectHoveredRect;
666-
bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f)
667-
int NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1)
668+
bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f)
669+
int NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1)
668670
int NavLayerActiveMask; // Which layer have been written to (result from previous frame)
669671
int NavLayerActiveMaskNext; // Which layer have been written to (buffer for current frame)
670672
bool MenuBarAppending; // FIXME: Remove this
671673
float MenuBarOffsetX;
672674
ImVector<ImGuiWindow*> ChildWindows;
673675
ImGuiStorage* StateStorage;
674676
ImGuiLayoutType LayoutType;
677+
ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin()
675678

676679
// We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings.
677680
ImGuiItemFlags ItemFlags; // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default]
@@ -714,7 +717,7 @@ struct IMGUI_API ImGuiDrawContext
714717
MenuBarAppending = false;
715718
MenuBarOffsetX = 0.0f;
716719
StateStorage = NULL;
717-
LayoutType = ImGuiLayoutType_Vertical;
720+
LayoutType = ParentLayoutType = ImGuiLayoutType_Vertical;
718721
ItemWidth = 0.0f;
719722
ItemFlags = ImGuiItemFlags_Default_;
720723
TextWrapPos = -1.0f;

0 commit comments

Comments
 (0)