ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
59.73k stars 10.17k forks source link

IsItemHovered() returns true, but IsAnyItemHovered() returns false for text #4205

Open englercj opened 3 years ago

englercj commented 3 years ago

Version/Branch of Dear ImGui:

Version: 1.84 WIP Branch: docking

Back-end/Renderer/Compiler/OS

Back-ends: custom Compiler: msvc Operating System: windows

My Issue/Question:

It seems like IsItemHovered() and IsAnyItemHovered() don't agree on how to handle text. The former returns true when the text is hovered, but the later returns false.

Standalone, minimal, complete and verifiable example:

My use-case is in a menu bar so I'm including that if it is relevant:

if (ImGui::BeginMainMenuBar())
{
    ImGui::TextUnformatted("Something");
    if (ImGui::IsItemHovered())
        ImGui::TextUnformatted("ITEM"); // is drawn, IsItemHovered() returns TRUE
    if (ImGui::IsAnyItemHovered())
        ImGui::TextUnformatted("ANY"); // is not drawn, IsAnyItemHovered() returns FALSE

    ImGui::EndMainMenuBar();
}
ocornut commented 3 years ago

You are correct... maybe IsAnyItemHovered() should be reformulated.

I however need to ask why you are interested in IsAnyItemHovered() ? 95%+ of people who claim they want to use IsAnyItemHovered() would be better using io.WantCaptureMouse.

englercj commented 3 years ago

I'm spawning a borderless window and implementing a custom title bar with ImGui. To do so I need to implement WM_NCHITTEST and tell Windows what part of the title bar is currently being hovered.

Basically what I do is check if any item is being hovered in the menu, and if not return HTCAPTION which lets the user drag the window around. It ends up looking something like this (simplified) example:

m_menuHitArea = HTCLIENT;

if (ImGui::BeginMainMenuBar())
{
    ImGui::TextUnformatted(ICON_FA_HOUSE);
    if (ImGui::IsItemHovered())
        m_menuHitArea = HTSYSMENU;

    // All the menu items and stuff
    if (ImGui::BeginMenu("File")) { /* ... */ }
    if (ImGui::BeginMenu("Edit")) { /* ... */ }

    // Setup right alignment of system buttons
    const float targetSysButtonWidth = 30.0f * ImGui::GetWindowDpiScale();
    const float sysButtonPadding = targetSysButtonWidth - ImGui::GetFontSize();

    ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, { 0, 0 });
    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { sysButtonPadding, menubarPadding * 2.0f });
    const float sysButtonWidth = ImGui::CalcTextSize(ICON_FA_SQUARE).x + (sysButtonPadding * 2.0f);
    const float windowWidth = ImGui::GetWindowWidth() + ((ImGui::GetStyle().FramePadding.x / 2.0f) * ImGui::GetWindowDpiScale());
    ImGui::SameLine(windowWidth - (sysButtonWidth * 3));

    // Minimize
    ImGui::MenuItem(ICON_FA_WINDOW_MINIMIZE, nullptr, nullptr, true);
    if (ImGui::IsItemHovered())
        m_menuHitArea = HTMINBUTTON;

    // Maximize & Restore
    ImGui::MenuItem(ICON_FA_SQUARE_FULL, nullptr, nullptr, true);
    if (ImGui::IsItemHovered())
        m_menuHitArea = HTMAXBUTTON;

    // Close
    ImGui::PushStyleColor(ImGuiCol_HeaderHovered, { 1.0f, 0.0f, 0.0f, 0.9f });
    ImGui::MenuItem("\xe2\x9c\x96", nullptr, nullptr, true);
    if (ImGui::IsItemHovered())
        m_menuHitArea = HTCLOSE;
    ImGui::PopStyleColor();

    ImGui::PopStyleVar(2);

    // If we're in the main menu bar, not over anything more specific, let the user drag the window
    if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemHovered())
        m_menuHitArea = HTCAPTION;

    ImGui::EndMainMenuBar();
}

Elsewhere I may return the cached m_menuHitArea value to Windows when it asks for a hit test.

ocornut commented 3 years ago

Well you should use io.WantCaptureMouse then.

englercj commented 3 years ago

Can you elaborate? I'm not sure how WantCaptureMouse helps the situation?

englercj commented 3 years ago

Looks like ImGui::GetIO().WantCaptureMouse is always true when my mouse is over anywhere in the main menu bar and the window has focus. But I want to know specifically when the menu bar is hovered, and nothing in the menu bar (like a button, menu item, or text) is.

ocornut commented 3 years ago

Right, sorry I misread the code snippet. This is equivalent to the test done to enable moving a window on dear imgui side.

In that specific snippet the only "offender" is the ImGui::TextUnformatted(ICON_FA_HOUSE); call, as you mention raw Text() elements are not claiming HoveredId.

For that specific purpose, since your menu bar is likely going to be fairly contained, would suggest using either:

Down the line it should be possible to provide some kind of state/flags to allow elements like Text() - which normally don't call ItemHoverable() and not have an ID to do the equivalent to prevent moving window.

Also note that your call to IsWindowHovered() might want to use the ImGuiHoveredFlags_AllowWhenBlockedByPopup to support moving via that native title bar when a popup or modal are open.

englercj commented 3 years ago

I ended up using a custom styled Button() because I wanted an easier way to control the sizing of the widget, which has the side effect of skipping the text hover issue. Basically, your first recommendation.

I still wanted to open this issue because IsItemHovered() not matching IsAnyItemHovered() was unexpected behavior when I hit it.

Also note that your call to IsWindowHovered() might want to use the ImGuiHoveredFlags_AllowWhenBlockedByPopup to support moving via that native title bar when a popup or modal are open.

Good call, I'll make that change. Thanks!