imgui
imgui copied to clipboard
How to achieve a multiline menu bar or a second menu bar under the main one?
Version/Branch of Dear ImGui:
Version: 1.79 Branch: docking
Back-end/Renderer/Compiler/OS
Back-ends: imgui_impl_opengl3.cpp + imgui_impl_sdl.cpp Compiler: msvc Operating System: Windows 10
My Issue/Question:
How to create a multiline menu bar or many menu bars?
I'd like to achieve something like this:
or like this:
where the first menu bar has file
etc.
and the second menu bar right under it (or the same menu bar, but with widgets next lined) with options like unity or visual studio has (unity: hand button, rotation button, play scene, pause scene, etc.)
I also want to have full docking and viewport functionality.
I tried to use ImGui::NextLine()
, it does not work, and
ImGui::BeginMainMenuBar();
ImGui::EndMainMenuBar();
ImGui::BeginMenuBar();
ImGui::EndMenuBar();
but it does not work as a regular widget I guess so a new menu bar hasn't been created under the main one.
I think you just want to use Begin() and set the position and size to fit the boundaries of the buttons or menu items you places on that window.
you can using ImGui::SetNextWindowPos() and ImGui::SetNextWindowSize() to make custom menu bar like seen in Visual Studio.
I was able to achieve this effect by duplicating ImGui::BeginMainMenuBar
and ImGui::EndMainMenuBar
and changing the window name to ##SecondaryMenuBar
.
The pixel gap is caused by the fact that i changed the border color to match that of the background. If you want it to be like Visual Studio you have set the border to match the window background and then you obtain:
@alexandru-cazacu Could you please post your complete code ? For some reason the second menu bar always continues in the first row for me.
@Nightmare82 Sure here it is. I just duplicated BeginMainMenuBar()
and EndMainMenuBar()
.
It might not be the optimal solution, but it gets the job done.
bool HY_ImGui_BeginMainStatusBar()
{
ImGuiContext& g = *GImGui;
ImGuiViewportP* viewport = g.Viewports[0];
ImGuiWindow* menu_bar_window = ImGui::FindWindowByName("##MainStatusBar");
// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
// Get our rectangle at the top of the work area
//__debugbreak();
if (menu_bar_window == NULL || menu_bar_window->BeginCount == 0)
{
// Set window position
// We don't attempt to calculate our height ahead, as it depends on the per-viewport font size. However menu-bar will affect the minimum window size so we'll get the right height.
ImVec2 menu_bar_pos = viewport->Pos + viewport->CurrWorkOffsetMin;
ImVec2 menu_bar_size = ImVec2(viewport->Size.x - viewport->CurrWorkOffsetMin.x + viewport->CurrWorkOffsetMax.x, 1.0f);
ImGui::SetNextWindowPos(menu_bar_pos);
ImGui::SetNextWindowSize(menu_bar_size);
}
// Create window
ImGui::SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set.
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint, however the presence of a menu-bar will give us the minimum height we want.
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
bool is_open = ImGui::Begin("##MainStatusBar", NULL, window_flags) && ImGui::BeginMenuBar();
ImGui::PopStyleVar(2);
// Report our size into work area (for next frame) using actual window size
menu_bar_window = ImGui::GetCurrentWindow();
if (menu_bar_window->BeginCount == 1)
viewport->CurrWorkOffsetMin.y += menu_bar_window->Size.y;
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
if (!is_open)
{
ImGui::End();
return false;
}
return true; //-V1020
}
void HY_ImGui_EndMainStatusBar()
{
ImGui::EndMenuBar();
// When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
// 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)
ImGui::FocusTopMostWindowUnderOne(g.NavWindow, NULL);
ImGui::End();
}
Then to use it:
if (HY_ImGui_BeginMainStatusBar()) {
ImGui::Text("Happy status bar");
HY_ImGui_EndMainStatusBar();
}
Watch out for the window name, in my case it's ##MainStatusBar
instead of ##MainMenuBar
.
I have refactored some code in a58271c07 to make this easier, you can now use BeginViewportSideBar()
(in imgui_internal.h so technically not documented/guaranteed, but simpler/better than snippet above and more likely to move us toward a public solution).
Thanks a lot @alexandru-cazacu !
@ocornut sounds great, thanks!
I tried a58271c and it worked perfecly:
Here is the snippet i used:
ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)ImGui::GetMainViewport();
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
float height = ImGui::GetFrameHeight();
if (ImGui::BeginViewportSideBar("##SecondaryMenuBar", viewport, ImGuiDir_Up, height, window_flags)) {
if (ImGui::BeginMenuBar()) {
ImGui::Text("Happy secondary menu bar");
ImGui::EndMenuBar();
}
ImGui::End();
}
if (ImGui::BeginViewportSideBar("##MainStatusBar", viewport, ImGuiDir_Down, height, window_flags)) {
if (ImGui::BeginMenuBar()) {
ImGui::Text("Happy status bar");
ImGui::EndMenuBar();
}
ImGui::End();
}
And here is a screenshot (using BeginMainMenuBar()
, a secondary menu bar and a status bar with BeginViewportSideBar()
):
Hi Friends,
BeginViewportSideBar
is pretty useful. Are there any plans to promote it to the public API?
@mnesarco It'll need more feedback until we can promote BeginViewportSideBar()
there.
I'll also be reworking API to claim space in the "menu layer" of individual window, so the ImGuiWindowFlags_MenuBar
flag will becoming unnecessary since BeginMenuBar()
will be able to claim the space, and same tech will make it easier to create status bar or sidebar within windows.
@ocornut Thank you, I will love to see the new API. Thank you for all your hard work. The more I use ImGui the more I love it.
@alexandru-cazacu
Thank you for the example.
Some notes:
// [1] BeginViewportSideBar signature receives ImGuiViewport* viewport
//
// You can change this:
// ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)ImGui::GetMainViewport();
// To this:
// ImGuiViewport* viewport = ImGui::GetMainViewport();
//
// But you can pass just NULL because main viewport is the default.
//
// [2] ImGui::BeginViewportSideBar uses ImGui::Begin so the call to ImGui::End should be out of the {if scope}
//
// Updated version:
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
float height = ImGui::GetFrameHeight();
if (ImGui::BeginViewportSideBar("##SecondaryMenuBar", NULL, ImGuiDir_Up, height, window_flags)) {
if (ImGui::BeginMenuBar()) {
ImGui::Text("Happy secondary menu bar");
ImGui::EndMenuBar();
}
}
ImGui::End();
if (ImGui::BeginViewportSideBar("##MainStatusBar", NULL, ImGuiDir_Down, height, window_flags)) {
if (ImGui::BeginMenuBar()) {
ImGui::Text("Happy status bar");
ImGui::EndMenuBar();
}
}
ImGui::End();
Cheers.
I am currently in the stable branch and doing this:
const ImGuiViewport *viewport = ImGui::GetMainViewport();
// Set position to the bottom of the viewport
ImGui::SetNextWindowPos(
ImVec2(viewport->Pos.x,
viewport->Pos.y + viewport->Size.y - ImGui::GetFrameHeight()));
// Extend width to viewport width
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, ImGui::GetFrameHeight()));
// Add menu bar flag and disable everything else
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollWithMouse |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_MenuBar;
if (ImGui::Begin("StatusBar", nullptr, flags)) {
if (ImGui::BeginMenuBar()) {
ImGui::Text("%s", state.c_str());
ImGui::EndMenuBar();
}
ImGui::End();
}
May I ask what problem does BeginViewportSideBar
solve?
Thanks for this, had been struggling with trying to implement this myself the past few days.
Nechroposting a bit because I received a notification from user @rlrq97 asking how to implement the floating toolbar in this screenshot (I can't find the original comment anymore, maybe it was deleted). Maybe it's a bit off topic, but I don't know where to post this, and someone might find it useful.
This is the final result using an embedded icon font.
//
// Viewport panel
//
ImGuiWindowFlags viewportWindowFlags = ImGuiWindowFlags_MenuBar;
ImVec4 windowBg = style.Colors[ImGuiCol_WindowBg];
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
ImGui::PushStyleColor(ImGuiCol_WindowBg, {0, 0, 0, 0});
if (ImGui::Begin("Viewport", nullptr, viewportWindowFlags)) {
// Menu bar
if (ImGui::BeginMenuBar()) {
// ...
ImGui::EndMenuBar();
}
// Toolbar
float textHeight = ImGui::CalcTextSize("A").y;
int toolbarItems = 10;
// style.FramePadding can also be used here
ImVec2 toolbarItemSize = ImVec2{textHeight, textHeight} * 2.0f;
ImVec2 toolbarPos = ImGui::GetWindowPos() + ImGui::GetCursorPos();
ImVec2 toolbarSize = {toolbarItemSize.x + style.WindowPadding.x * 2.0f, //
toolbarItemSize.y * toolbarItems + style.WindowPadding.y * 2.0f};
ImGui::SetNextWindowPos(toolbarPos);
ImGui::SetNextWindowSize(toolbarSize);
ImGuiWindowFlags toolbarFlags = ImGuiWindowFlags_NoDecoration | //
ImGuiWindowFlags_NoMove | //
ImGuiWindowFlags_NoScrollWithMouse | //
ImGuiWindowFlags_NoSavedSettings | //
ImGuiWindowFlags_NoBringToFrontOnFocus;
ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_NoPadWithHalfSpacing;
// You shouldn't need this. My parent window has a transparent bg so I set the original window bg color here.
ImGui::PushStyleColor(ImGuiCol_WindowBg, windowBg);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {0.0f, 0.0f});
if (ImGui::Begin("##ViewportToolbar", nullptr, toolbarFlags)) {
// Bring the toolbar window always on top.
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f));
if (ImGui::Selectable(ICON_FA_MOUSE_POINTER, gCurrentBrush == Brush::Select, selectableFlags, toolbarItemSize)) {
gCurrentBrush = Brush::Select;
}
if (ImGui::Selectable(ICON_FA_PLUS, gCurrentBrush == Brush::Add, selectableFlags, toolbarItemSize)) {
gCurrentBrush = Brush::Add;
}
// More buttons ...
ImGui::Separator();
if (ImGui::Selectable(ICON_FA_PLUS_SQUARE, gCurrentBrush == Brush::AddChunk, selectableFlags, toolbarItemSize)) {
gCurrentBrush = Brush::AddChunk;
}
if (ImGui::Selectable(ICON_FA_MINUS_SQUARE, gCurrentBrush == Brush::RemoveChunk, selectableFlags, toolbarItemSize)) {
gCurrentBrush = Brush::RemoveChunk;
}
ImGui::PopStyleVar(); // ImGuiStyleVar_SelectableTextAlign
}
ImGui::End();
ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing
ImGui::PopStyleColor(); // ImGuiCol_WindowBg
}
ImGui::End();
ImGui::PopStyleColor(); // ImGuiCol_WindowBg
ImGui::PopStyleVar(); // ImGuiStyleVar_FrameRounding
One last thing, I think this issue can be closed. The original request, having the possibility to have a secondary menu bar under the main one, can be achieved with ImGui::BeginViewportSideBar
.