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 capture mouse wheel in custom widget? #2704

Closed
bear24rw opened this issue Jul 29, 2019 · 4 comments
Closed

How to capture mouse wheel in custom widget? #2704

bear24rw opened this issue Jul 29, 2019 · 4 comments
Labels

Comments

@bear24rw
Copy link
Contributor

Version/Branch of Dear ImGui:

Version: v1.72
Branch: master

My Issue/Question:

I am trying to implement a custom widget which captures the mouse scroll wheel when hovered. When hovered the window should not scroll (similar to when hovered over a listbox or textarea widget). Is this easily achievable?

Screenshots/Video
Video showing undesired behavior of window scrolling while mouse wheeling over the custom widget:
imgui_scroll_wheel

Standalone, minimal, complete and verifiable example: (see #2261)

struct MyWidget {
    float wheel_counter = 0.0f;

    void Draw(const char *label, ImVec2 frame_size = ImVec2(0,0)) {
        ImGuiWindow* window = ImGui::GetCurrentWindow();
        if (window->SkipItems)
            return;

        ImGuiContext& g = *GImGui;
        const ImGuiStyle& style = g.Style;
        const ImGuiID id = window->GetID(label);

        const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
        if (frame_size.x == 0.0f) frame_size.x = ImGui::CalcItemWidth();
        if (frame_size.y == 0.0f) frame_size.y = frame_size.x;

        const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
        const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
        const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));

        ImGui::RenderFrame(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);

        ImGui::InvisibleButton(label, frame_size);

        char buffer[256];
        snprintf(buffer, sizeof(buffer), "Counter: %.3f", wheel_counter);
        window->DrawList->PushClipRect(inner_bb.Min, inner_bb.Max, true);
        window->DrawList->AddText(inner_bb.GetCenter(), ImGui::GetColorU32(ImGuiCol_Text), buffer);
        window->DrawList->PopClipRect();

        if (ImGui::IsItemHovered()) {
            wheel_counter += ImGui::GetIO().MouseWheel;
        }
    }
};
        ImGui::Begin("Issue");
        static MyWidget w;
        w.Draw("FooBar");
        ImGui::End();
@ocornut
Copy link
Owner

ocornut commented Jul 29, 2019

It's not easily achievable at the moment, other than using the window flags to make the parent window itself not react to mouse wheel.

@ocornut ocornut added the inputs label Jul 29, 2019
@bear24rw
Copy link
Contributor Author

Okay, thanks for the quick response. I came up with two workarounds:

  1. Require the shift key to be held (arguably better anyway since it wont interrupt you scrolling down a window)
  2. Use a child region and set its scrollmax to non-zero:
struct MyWidget {
    float wheel_counter = 0.0f;

    void Draw(const char *label, ImVec2 frame_size = ImVec2(0,0)) {
        ImGuiWindow* window = ImGui::GetCurrentWindow();
        if (window->SkipItems)
            return;

        const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
        if (frame_size.x == 0.0f)
            frame_size.x = ImGui::CalcItemWidth();
        if (frame_size.y == 0.0f)
            frame_size.y = frame_size.x;

        ImGui::BeginChild(label, frame_size);
        window = ImGui::GetCurrentWindow();
        window->ScrollMax.y = 1.0f;

        ImGuiContext& g = *GImGui;
        const ImGuiStyle& style = g.Style;
        const ImGuiID id = window->GetID(label);

        const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
        const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
        const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));

        ImGui::RenderFrame(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);

        ImGui::InvisibleButton(label, frame_size);

        char buffer[256];
        snprintf(buffer, sizeof(buffer), "Counter: %.3f", wheel_counter);
        window->DrawList->PushClipRect(inner_bb.Min, inner_bb.Max, true);
        window->DrawList->AddText(inner_bb.GetCenter(), ImGui::GetColorU32(ImGuiCol_Text), buffer);
        window->DrawList->PopClipRect();

        if (ImGui::IsItemHovered()) {
            wheel_counter += ImGui::GetIO().MouseWheel;
        }

        ImGui::EndChild();
    }
};

@ocornut
Copy link
Owner

ocornut commented Jul 9, 2021

Amend to this thread: in Dec 2020 we added ImGui::SetItemUsingMouseWheel() to claim the mouse wheel.

See #2891 for old discussion, and #4207 #3281 for some uses.

@ocornut
Copy link
Owner

ocornut commented Nov 8, 2022

Update: I have now pushed a more generic system for input ownership, and as I result obsoleted SetItemUsingMouseWheel(). There is still a redirection function but I expect to delete it sooner than the typical 2-3 years obsolescence schedule.

You can now use:

SetItemKeyOwner(ImGuiKey_MouseWheelY);

Note that SetItemUsingMouseWheel() used to lock both wheel axises.
If you need both you can call the function with ImGuiKey_MouseWheelX and ImGuiKey_MouseWheelY.

Also note that using SetKeyOwner() and SetItemKeyOwner() flags allow for variety of other behaviors outside the scope of this thread.

(cross posted to #2704, #2891, #5108)

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

No branches or pull requests

2 participants