ocornut / imgui

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

Horizontal child view scrolling via MouseWheelH erratic if no room for vertical scrolling #4559

Open floooh opened 3 years ago

floooh commented 3 years ago

Excuse the weird ticket title, but that's what seems to happen :)

I just stumbled over this when working with the new table/column API, but it actually seems to be a general problem with child views (at least back to 1.82, I tested with 1.84.2 and that doesn't seem to make a difference).

In a child view which can scroll horizontally and vertically, the horizontal scrolling with MouseWheelH only seems to work properly if the view is big enough to also allow scrolling into the vertical direction. If vertical scrolling is disabled because the view is too small, horizontal scrolling doesn't work (but it doesn't seem to be completely disabled, there's some initial "jitter" on the horizontal direction, as if scrolling is attempted but then immediately reset to the initial position).

For now I only have this existing example to reproduce the problem (you'll need a laptop with touchpad, I have tested on a Mac 13"MBP).

  1. click this link to open the WASM demo: https://floooh.github.io/sokol-html5/droptest-sapp.html
  2. drag'n'drop a very small file (such as the one attached to this ticket)
  3. resize the window so that scrolling should work in both directions, and attempt to scroll horizontally - this should work fine
  4. now resize vertically so that there's no vertical scroll bar, and attempt to scroll horizontally - this doesn't work as expected

Example video for the working case (enough room to scroll in both directions, horizontal scrolling works):

https://user-images.githubusercontent.com/1699414/134004081-68e60e8f-b2f0-4103-8801-fbe6df9a9f4a.mov

...and if the window is resized so that no vertical scrolling is possible, horizontal mouse-wheel-scrolling doesn't work (you can see some tiny horizontal movement, but it actually should properly scroll):

https://user-images.githubusercontent.com/1699414/134004337-30bbeaf4-40c6-418d-9f24-2af316a88004.mov

Attached small file for drag'n'drop:

drop_me.txt

PS: there could be a tiny chance that this might be caused by my ImGui backend code, but I think it's unlikely.

PathogenDavid commented 3 years ago

I have tested on a Mac 13"MBP

This seems like it might have some sort of platform-specific element at play even though your demo is browser based.

I cannot reproduce this on Windows using Edge 95 (latest dev) or Firefox 92.0 (latest stable). I tried both my Steelseries mouse (which has a tilt wheel) and my Surface Book 2's trackpad.

floooh commented 3 years ago

Aha, thanks for checking @PathogenDavid, I'll also try do more testing on my Linux and Windows laptop in native and WASM. I also see the problem in native code on my MBP (that's where I noticed it first).

It'd be weird though if this should turn out to be a problem in my input code since this shouldn't be aware at all about the child view layout differences 🤔

floooh commented 3 years ago

Hmm, how weird, on my Asus Zenbook on Windows in Chrome, horizontal wheel scrolling always has this problem. Not just when vertical scrolling is off. Vertical scrolling works fine.

And on Ubuntu with Firefox there's no problem at all. Scrolling always works in all directions.

I guess it's best if you ignore this problem for now and I'll find some time to investigate with some proper printf-debugging ;) (starting with the scroll-wheel-values that go into Dear ImGui, and following that trail).

PathogenDavid commented 3 years ago

It's weird though if this should turn out to be a problem in my input code since this shouldn't be aware at all about the child view layout differences 🤔

I would agree, it's definitely a weird issue. I'd be interested in knowing what the magnitude of MouseWheelH is on the MacBook, especially if it's super tiny or super big. (It'd still be super weird for that to only matter when the scrollbar is missing.)

With the GLFW backend it's always either -1, 0, or 1 with my mouse. With my trackpad the magnitude seems to always be at least 0.1 and rarely exceeds 5.

(starting with the scroll-wheel-values that go into Dear ImGui, and following that trail).

Edit: lol, you beat me to it. Good luck!

ocornut commented 3 years ago

Related to #3795, while working on that issue I identified more problem but didn't post details about it in the thread.

3795 discuss how scrolling on different axis are forwarded to nested windows. To be honest I don't exactly remember why I put that issue on standbye (right after throwing "very shortly"), I think at the time I was thinking that on systems where "wheeling" inputs are fed to both coordinates simultaneously there would likely be a conflict and it might have looked like a bigger problem than initially stated.

Code is in ImGui::UpdateMouseWheel(), I suggest to add a IMGUI_DEBUG_LOG("wheel %.3f %.3f\n", wheel_x, wheel_y); call before // Vertical Mouse Wheel scrolling as I believe the value of both axises are useful in debugging this, so we see values over time on your system @floooh.

What I imagine happens if both wheel values are non-zero, when wheel_y != 0 we pass the scrolling up to the parent window and then scrolling is locked there. So it only happens when:

Actually between this and other issues with horizontal scrolling at the time I purchased a small touch device exactly for the purpose of testing this, and said device is sitting unopened on my desk right now...

3795 is also a separate bug by itself and I believe both can be fixed with a single commit.

ocornut commented 3 years ago

Extracts of the related code

    // Reset the locked window if we move the mouse or after the timer elapses
    if (g.WheelingWindow != NULL)
    {
        g.WheelingWindowTimer -= g.IO.DeltaTime;
        if (IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold)
            g.WheelingWindowTimer = 0.0f;
        if (g.WheelingWindowTimer <= 0.0f)
        {
            g.WheelingWindow = NULL;
            g.WheelingWindowTimer = 0.0f;
        }
    }

Note how the locked wheeling window is selected there:

    ImGuiWindow* window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow;
    if (!window || window->Collapsed)
        return;

Then scroll processed:

    const bool swap_axis = g.IO.KeyShift && !g.IO.ConfigMacOSXBehaviors;
    const float wheel_y = swap_axis ? 0.0f : g.IO.MouseWheel;
    const float wheel_x = swap_axis ? g.IO.MouseWheel : g.IO.MouseWheelH;

    IMGUI_DEBUG_LOG("wheel %.3f %.3f\n", wheel_x, wheel_y);

    // Vertical Mouse Wheel scrolling
    if (wheel_y != 0.0f)
    {
        StartLockWheelingWindow(window);
        while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.y == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))))
            window = window->ParentWindow;
        if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
        {
            float max_step = window->InnerRect.GetHeight() * 0.67f;
            float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step));
            SetScrollY(window, window->Scroll.y - wheel_y * scroll_step);
        }
    }

    // Horizontal Mouse Wheel scrolling, or Vertical Mouse Wheel w/ Shift held
    if (wheel_x != 0.0f)
    {
        StartLockWheelingWindow(window);
        while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.x == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))))
            window = window->ParentWindow;
        if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
        {
            float max_step = window->InnerRect.GetWidth() * 0.67f;
            float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step));
            SetScrollX(window, window->Scroll.x - wheel_x * scroll_step);
        }
    }

The wheel lock is designed so that when scrolling a parent our scroll doesn't get interrupted by a child window passing under (see #2604) and it is likely also the culprit of this issue here triggered by dual-axis wheeling which is likely to happen on "unfiltered" 2D trackpad drivers (vs actual mouse wheel which are two separate 1D wheels).

floooh commented 3 years ago

One interesting observation is that "Shift + Vertical Mouse Wheel" works in situations where "Horizontal Mouse Wheel" does not.

Also I just confirmed that the macOS touchpad (with two-finger-dragging) provides wheel information in both directions (not mututally exclusive).

floooh commented 3 years ago

...and another observation: if I hack my ImGui wrapper code to only provide one of the two wheel axis (depending which is bigger), it also appears to work. This simple hack still doesn't feel good because of the "wheel inertia" on macOS (so if I had a big change in the vertical direction, the horizontal direction is locked for some time until the vertical wheel change are close to zero).

This might also explain the different behaviour on different browsers and platforms, some of those seem to not provide X- and Y-wheel-movement at the same time (and those appear to work).

I wonder if I can fix the issue on my side with a slightly smarter "axis filter".

folays commented 2 years ago

Hi @ocornut , I'm the original poster of #3795 ;

You wrote recently in this (#4559) issue :

it only happens when: 1) Using nested child windows where a parent can scroll on a given axis and child cannot <- PROBABLY 2) Using hardware/drivers/platform where scroll inputs are treated in 2D without any single-axis locking of the stream of values, and an initial "swipe right" tend to include a Y component. <- YES

YES for (2). When we use a touchpad (on a MacBook, with the "original" built-in touchpad:

My #3795 contained three fix, and I'm going to update my reques in #3795 to add some informations in it.

ocornut commented 2 years ago

I have pushed 80a870a (slight refactor) + c7d3d22 (mitigation/fix) which should mitigate this issue. The more general issue #3795 needs work.

ocornut commented 2 years ago

Would appreciate if you can test the mouse_wheeling_target_3795 branch. See: https://github.com/ocornut/imgui/pull/3795#issuecomment-1270527155

...and another observation: if I hack my ImGui wrapper code to only provide one of the two wheel axis (depending which is bigger), it also appears to work. This simple hack still doesn't feel good because of the "wheel inertia" on macOS (so if I had a big change in the vertical direction, the horizontal direction is locked for some time until the vertical wheel change are close to zero). This might also explain the different behaviour on different browsers and platforms, some of those seem to not provide X- and Y-wheel-movement at the same time (and those appear to work). I wonder if I can fix the issue on my side with a slightly smarter "axis filter".

Test would involve disabling all those workarounds on your side and feed raw unfiltered inputs. Thanks!