imgui icon indicating copy to clipboard operation
imgui copied to clipboard

How to achieve a multiline menu bar or a second menu bar under the main one?

Open michaeltheprogrammer1 opened this issue 4 years ago • 16 comments

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:

image

or like this:

image

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.

michaeltheprogrammer1 avatar Oct 10 '20 13:10 michaeltheprogrammer1

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.

Josue-Herrera avatar Oct 10 '20 21:10 Josue-Herrera

I was able to achieve this effect by duplicating ImGui::BeginMainMenuBar and ImGui::EndMainMenuBar and changing the window name to ##SecondaryMenuBar.

image

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:

image

alexandru-cazacu avatar Mar 20 '21 18:03 alexandru-cazacu

@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 avatar Mar 22 '21 17:03 Nightmare82

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

alexandru-cazacu avatar Mar 24 '21 19:03 alexandru-cazacu

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).

ocornut avatar Mar 25 '21 16:03 ocornut

Thanks a lot @alexandru-cazacu !

@ocornut sounds great, thanks!

Nightmare82 avatar Mar 25 '21 19:03 Nightmare82

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()):

image

alexandru-cazacu avatar Mar 25 '21 20:03 alexandru-cazacu

Hi Friends, BeginViewportSideBar is pretty useful. Are there any plans to promote it to the public API?

mnesarco avatar Jul 03 '21 20:07 mnesarco

@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 avatar Jul 07 '21 11:07 ocornut

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

mnesarco avatar Jul 07 '21 14:07 mnesarco

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

mnesarco avatar Sep 13 '21 13:09 mnesarco

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?

GasimGasimzada avatar Jan 08 '22 18:01 GasimGasimzada

Thanks for this, had been struggling with trying to implement this myself the past few days.

Torphedo avatar Jul 28 '22 18:07 Torphedo

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.

image

//
// 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.

alexandru-cazacu avatar Nov 04 '23 17:11 alexandru-cazacu