Refactor: Moved Menu functions from imgui.cpp to imgui_widgets.cpp (#2036)
diff --git a/imgui.cpp b/imgui.cpp
index 3b9251a..9750a82 100644
--- a/imgui.cpp
+++ b/imgui.cpp
@@ -1983,51 +1983,6 @@
}
//-----------------------------------------------------------------------------
-// ImGuiSimpleColumns (internal use only)
-//-----------------------------------------------------------------------------
-
-ImGuiMenuColumns::ImGuiMenuColumns()
-{
- Count = 0;
- Spacing = Width = NextWidth = 0.0f;
- memset(Pos, 0, sizeof(Pos));
- memset(NextWidths, 0, sizeof(NextWidths));
-}
-
-void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
-{
- IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
- Count = count;
- Width = NextWidth = 0.0f;
- Spacing = spacing;
- if (clear) memset(NextWidths, 0, sizeof(NextWidths));
- for (int i = 0; i < Count; i++)
- {
- if (i > 0 && NextWidths[i] > 0.0f)
- Width += Spacing;
- Pos[i] = (float)(int)Width;
- Width += NextWidths[i];
- NextWidths[i] = 0.0f;
- }
-}
-
-float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
-{
- NextWidth = 0.0f;
- NextWidths[0] = ImMax(NextWidths[0], w0);
- NextWidths[1] = ImMax(NextWidths[1], w1);
- NextWidths[2] = ImMax(NextWidths[2], w2);
- for (int i = 0; i < 3; i++)
- NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
- return ImMax(Width, NextWidth);
-}
-
-float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
-{
- return ImMax(0.0f, avail_w - Width);
-}
-
-//-----------------------------------------------------------------------------
// ImGuiListClipper
//-----------------------------------------------------------------------------
@@ -10835,310 +10790,6 @@
return false;
}
-bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- ImGuiContext& g = *GImGui;
- ImGuiStyle& style = g.Style;
- ImVec2 pos = window->DC.CursorPos;
- ImVec2 label_size = CalcTextSize(label, NULL, true);
-
- ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
- bool pressed;
- if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
- {
- // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
- // Note that in this situation we render neither the shortcut neither the selected tick mark
- float w = label_size.x;
- window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
- PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
- pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
- PopStyleVar();
- window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
- }
- else
- {
- ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
- float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
- float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
- pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
- if (shortcut_size.x > 0.0f)
- {
- PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
- RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
- PopStyleColor();
- }
- if (selected)
- RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f);
- }
- return pressed;
-}
-
-bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
-{
- if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
- {
- if (p_selected)
- *p_selected = !*p_selected;
- return true;
- }
- return false;
-}
-
-// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
-bool ImGui::BeginMainMenuBar()
-{
- ImGuiContext& g = *GImGui;
- g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
- SetNextWindowPos(ImVec2(0.0f, 0.0f));
- SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
- PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
- PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));
- ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
- bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
- PopStyleVar(2);
- g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
- if (!is_open)
- {
- End();
- return false;
- }
- return true;
-}
-
-void ImGui::EndMainMenuBar()
-{
- EndMenuBar();
-
- // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
- ImGuiContext& g = *GImGui;
- if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
- FocusFrontMostActiveWindowIgnoringOne(g.NavWindow);
-
- End();
-}
-
-bool ImGui::BeginMenuBar()
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
- if (!(window->Flags & ImGuiWindowFlags_MenuBar))
- return false;
-
- IM_ASSERT(!window->DC.MenuBarAppending);
- BeginGroup(); // Backup position on layer 0
- PushID("##menubar");
-
- // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
- // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
- ImRect bar_rect = window->MenuBarRect();
- ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f));
- clip_rect.ClipWith(window->OuterRectClipped);
- PushClipRect(clip_rect.Min, clip_rect.Max, false);
-
- window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
- window->DC.LayoutType = ImGuiLayoutType_Horizontal;
- window->DC.NavLayerCurrent++;
- window->DC.NavLayerCurrentMask <<= 1;
- window->DC.MenuBarAppending = true;
- AlignTextToFramePadding();
- return true;
-}
-
-void ImGui::EndMenuBar()
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
- ImGuiContext& g = *GImGui;
-
- // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
- if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
- {
- ImGuiWindow* nav_earliest_child = g.NavWindow;
- while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
- nav_earliest_child = nav_earliest_child->ParentWindow;
- if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
- {
- // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
- // 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)
- IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
- FocusWindow(window);
- SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);
- g.NavLayer = 1;
- g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
- g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
- NavMoveRequestCancel();
- }
- }
-
- IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
- IM_ASSERT(window->DC.MenuBarAppending);
- PopClipRect();
- PopID();
- window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
- window->DC.GroupStack.back().AdvanceCursor = false;
- EndGroup(); // Restore position on layer 0
- window->DC.LayoutType = ImGuiLayoutType_Vertical;
- window->DC.NavLayerCurrent--;
- window->DC.NavLayerCurrentMask >>= 1;
- window->DC.MenuBarAppending = false;
-}
-
-bool ImGui::BeginMenu(const char* label, bool enabled)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(label);
-
- ImVec2 label_size = CalcTextSize(label, NULL, true);
-
- bool pressed;
- bool menu_is_open = IsPopupOpen(id);
- bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back());
- ImGuiWindow* backed_nav_window = g.NavWindow;
- if (menuset_is_open)
- g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
-
- // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup).
- ImVec2 popup_pos, pos = window->DC.CursorPos;
- if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
- {
- // Menu inside an horizontal menu bar
- // Selectable extend their highlight by half ItemSpacing in each direction.
- // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
- popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight());
- window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
- PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
- float w = label_size.x;
- pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
- PopStyleVar();
- window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
- }
- else
- {
- // Menu inside a menu
- popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
- float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
- float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
- pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
- if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
- RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
- if (!enabled) PopStyleColor();
- }
-
- const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
- if (menuset_is_open)
- g.NavWindow = backed_nav_window;
-
- bool want_open = false, want_close = false;
- if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
- {
- // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
- bool moving_within_opened_triangle = false;
- if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
- {
- if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window)
- {
- ImRect next_window_rect = next_window->Rect();
- ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
- ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
- ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
- float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
- ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
- tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
- tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
- moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
- //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug
- }
- }
-
- want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
- want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
-
- if (g.NavActivateId == id)
- {
- want_close = menu_is_open;
- want_open = !menu_is_open;
- }
- if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
- {
- want_open = true;
- NavMoveRequestCancel();
- }
- }
- else
- {
- // Menu bar
- if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
- {
- want_close = true;
- want_open = menu_is_open = false;
- }
- else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
- {
- want_open = true;
- }
- else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
- {
- want_open = true;
- NavMoveRequestCancel();
- }
- }
-
- if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
- want_close = true;
- if (want_close && IsPopupOpen(id))
- ClosePopupToLevel(g.CurrentPopupStack.Size);
-
- if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size)
- {
- // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
- OpenPopup(label);
- return false;
- }
-
- menu_is_open |= want_open;
- if (want_open)
- OpenPopup(label);
-
- if (menu_is_open)
- {
- // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
- SetNextWindowPos(popup_pos, ImGuiCond_Always);
- ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
- if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
- flags |= ImGuiWindowFlags_ChildWindow;
- menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
- }
-
- return menu_is_open;
-}
-
-void ImGui::EndMenu()
-{
- // Nav: When a left move request _within our child menu_ failed, close the menu.
- // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
- // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
- if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
- {
- ClosePopupToLevel(g.OpenPopupStack.Size - 1);
- NavMoveRequestCancel();
- }
-
- EndPopup();
-}
-
// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
{
diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp
index 76ef649..99e5ebf 100644
--- a/imgui_widgets.cpp
+++ b/imgui_widgets.cpp
@@ -1372,6 +1372,7 @@
//-------------------------------------------------------------------------
// WIDGETS: Menus
+// - ImGuiMenuColumns
// - BeginMainMenuBar()
// - EndMainMenuBar()
// - BeginMenuBar()
@@ -1381,3 +1382,349 @@
// - MenuItem()
//-------------------------------------------------------------------------
+// Helpers for internal use
+ImGuiMenuColumns::ImGuiMenuColumns()
+{
+ Count = 0;
+ Spacing = Width = NextWidth = 0.0f;
+ memset(Pos, 0, sizeof(Pos));
+ memset(NextWidths, 0, sizeof(NextWidths));
+}
+
+void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
+{
+ IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
+ Count = count;
+ Width = NextWidth = 0.0f;
+ Spacing = spacing;
+ if (clear) memset(NextWidths, 0, sizeof(NextWidths));
+ for (int i = 0; i < Count; i++)
+ {
+ if (i > 0 && NextWidths[i] > 0.0f)
+ Width += Spacing;
+ Pos[i] = (float)(int)Width;
+ Width += NextWidths[i];
+ NextWidths[i] = 0.0f;
+ }
+}
+
+float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
+{
+ NextWidth = 0.0f;
+ NextWidths[0] = ImMax(NextWidths[0], w0);
+ NextWidths[1] = ImMax(NextWidths[1], w1);
+ NextWidths[2] = ImMax(NextWidths[2], w2);
+ for (int i = 0; i < 3; i++)
+ NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
+ return ImMax(Width, NextWidth);
+}
+
+float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
+{
+ return ImMax(0.0f, avail_w - Width);
+}
+
+// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
+bool ImGui::BeginMainMenuBar()
+{
+ ImGuiContext& g = *GImGui;
+ g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
+ SetNextWindowPos(ImVec2(0.0f, 0.0f));
+ SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
+ PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
+ PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));
+ ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
+ bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
+ PopStyleVar(2);
+ g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
+ if (!is_open)
+ {
+ End();
+ return false;
+ }
+ return true;
+}
+
+void ImGui::EndMainMenuBar()
+{
+ EndMenuBar();
+
+ // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
+ ImGuiContext& g = *GImGui;
+ if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
+ FocusFrontMostActiveWindowIgnoringOne(g.NavWindow);
+
+ End();
+}
+
+bool ImGui::BeginMenuBar()
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+ if (!(window->Flags & ImGuiWindowFlags_MenuBar))
+ return false;
+
+ IM_ASSERT(!window->DC.MenuBarAppending);
+ BeginGroup(); // Backup position on layer 0
+ PushID("##menubar");
+
+ // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
+ // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
+ ImRect bar_rect = window->MenuBarRect();
+ ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f));
+ clip_rect.ClipWith(window->OuterRectClipped);
+ PushClipRect(clip_rect.Min, clip_rect.Max, false);
+
+ window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
+ window->DC.LayoutType = ImGuiLayoutType_Horizontal;
+ window->DC.NavLayerCurrent++;
+ window->DC.NavLayerCurrentMask <<= 1;
+ window->DC.MenuBarAppending = true;
+ AlignTextToFramePadding();
+ return true;
+}
+
+void ImGui::EndMenuBar()
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return;
+ ImGuiContext& g = *GImGui;
+
+ // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
+ if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
+ {
+ ImGuiWindow* nav_earliest_child = g.NavWindow;
+ while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
+ nav_earliest_child = nav_earliest_child->ParentWindow;
+ if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
+ {
+ // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
+ // 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)
+ IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
+ FocusWindow(window);
+ SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);
+ g.NavLayer = 1;
+ g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
+ g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
+ NavMoveRequestCancel();
+ }
+ }
+
+ IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
+ IM_ASSERT(window->DC.MenuBarAppending);
+ PopClipRect();
+ PopID();
+ window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
+ window->DC.GroupStack.back().AdvanceCursor = false;
+ EndGroup(); // Restore position on layer 0
+ window->DC.LayoutType = ImGuiLayoutType_Vertical;
+ window->DC.NavLayerCurrent--;
+ window->DC.NavLayerCurrentMask >>= 1;
+ window->DC.MenuBarAppending = false;
+}
+
+bool ImGui::BeginMenu(const char* label, bool enabled)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(label);
+
+ ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+ bool pressed;
+ bool menu_is_open = IsPopupOpen(id);
+ bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back());
+ ImGuiWindow* backed_nav_window = g.NavWindow;
+ if (menuset_is_open)
+ g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
+
+ // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup).
+ ImVec2 popup_pos, pos = window->DC.CursorPos;
+ if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
+ {
+ // Menu inside an horizontal menu bar
+ // Selectable extend their highlight by half ItemSpacing in each direction.
+ // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
+ popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight());
+ window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
+ PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
+ float w = label_size.x;
+ pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
+ PopStyleVar();
+ window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
+ }
+ else
+ {
+ // Menu inside a menu
+ popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
+ float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
+ float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
+ pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
+ if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
+ RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
+ if (!enabled) PopStyleColor();
+ }
+
+ const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
+ if (menuset_is_open)
+ g.NavWindow = backed_nav_window;
+
+ bool want_open = false, want_close = false;
+ if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
+ {
+ // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
+ bool moving_within_opened_triangle = false;
+ if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
+ {
+ if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window)
+ {
+ ImRect next_window_rect = next_window->Rect();
+ ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
+ ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
+ ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
+ float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
+ ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
+ tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
+ tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
+ moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
+ //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug
+ }
+ }
+
+ want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
+ want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
+
+ if (g.NavActivateId == id)
+ {
+ want_close = menu_is_open;
+ want_open = !menu_is_open;
+ }
+ if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
+ {
+ want_open = true;
+ NavMoveRequestCancel();
+ }
+ }
+ else
+ {
+ // Menu bar
+ if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
+ {
+ want_close = true;
+ want_open = menu_is_open = false;
+ }
+ else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
+ {
+ want_open = true;
+ }
+ else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
+ {
+ want_open = true;
+ NavMoveRequestCancel();
+ }
+ }
+
+ if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
+ want_close = true;
+ if (want_close && IsPopupOpen(id))
+ ClosePopupToLevel(g.CurrentPopupStack.Size);
+
+ if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size)
+ {
+ // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
+ OpenPopup(label);
+ return false;
+ }
+
+ menu_is_open |= want_open;
+ if (want_open)
+ OpenPopup(label);
+
+ if (menu_is_open)
+ {
+ // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
+ SetNextWindowPos(popup_pos, ImGuiCond_Always);
+ ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
+ if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
+ flags |= ImGuiWindowFlags_ChildWindow;
+ menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
+ }
+
+ return menu_is_open;
+}
+
+void ImGui::EndMenu()
+{
+ // Nav: When a left move request _within our child menu_ failed, close the menu.
+ // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
+ // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
+ {
+ ClosePopupToLevel(g.OpenPopupStack.Size - 1);
+ NavMoveRequestCancel();
+ }
+
+ EndPopup();
+}
+
+bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ ImGuiStyle& style = g.Style;
+ ImVec2 pos = window->DC.CursorPos;
+ ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+ ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
+ bool pressed;
+ if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
+ {
+ // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
+ // Note that in this situation we render neither the shortcut neither the selected tick mark
+ float w = label_size.x;
+ window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
+ PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
+ pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
+ PopStyleVar();
+ window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
+ }
+ else
+ {
+ ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
+ float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
+ float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
+ pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
+ if (shortcut_size.x > 0.0f)
+ {
+ PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
+ RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
+ PopStyleColor();
+ }
+ if (selected)
+ RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f);
+ }
+ return pressed;
+}
+
+bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
+{
+ if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
+ {
+ if (p_selected)
+ *p_selected = !*p_selected;
+ return true;
+ }
+ return false;
+}
+