Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
michaeltheprogrammer1 opened this issue Oct 10, 2020 · 14 comments
Labels
menus menu bars, menu items

Comments

@michaeltheprogrammer1
Copy link

michaeltheprogrammer1 commented Oct 10, 2020

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.

@Josue-Herrera
Copy link

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.

@ocornut ocornut added the menus menu bars, menu items label Oct 11, 2020
@alexandru-cazacu
Copy link

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

@Nightmare82
Copy link

@alexandru-cazacu Could you please post your complete code ? For some reason the second menu bar always continues in the first row for me.

@alexandru-cazacu
Copy link

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

ocornut added a commit that referenced this issue Mar 25, 2021
…ginMainMenuBar(). (#3966, #3518)

Complement ca34c81 in docking branch which removed assumption that we can't tell size ahead of Begin().
@ocornut
Copy link
Owner

ocornut commented Mar 25, 2021

I have refactored some code in a58271c 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).

@Nightmare82
Copy link

Thanks a lot @alexandru-cazacu !

@ocornut sounds great, thanks!

@alexandru-cazacu
Copy link

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

@mnesarco
Copy link

mnesarco commented Jul 3, 2021

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

@ocornut
Copy link
Owner

ocornut commented Jul 7, 2021

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

@mnesarco
Copy link

mnesarco commented Jul 7, 2021

@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
Copy link

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

@GasimGasimzada
Copy link

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?

@Torphedo
Copy link

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

ocornut added a commit that referenced this issue Dec 6, 2022
…. (toward #5143, #4868, #3692, #3518)

This is not strictly required presently, but will be consistent with adding inner decoration sizes in next commit, as well as generally being sane.
Locking TitleBarHeight() / MenuBarHeight() values per-window probably have side-effects in ill-defined situation related to changing font size per window.
kjblanchard pushed a commit to kjblanchard/imgui that referenced this issue May 5, 2023
…. (toward ocornut#5143, ocornut#4868, ocornut#3692, ocornut#3518)

This is not strictly required presently, but will be consistent with adding inner decoration sizes in next commit, as well as generally being sane.
Locking TitleBarHeight() / MenuBarHeight() values per-window probably have side-effects in ill-defined situation related to changing font size per window.
@alexandru-cazacu
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
menus menu bars, menu items
Projects
None yet
Development

No branches or pull requests

8 participants