imgui icon indicating copy to clipboard operation
imgui copied to clipboard

Mouse and Window Desynchronization on Ubuntu with Docking Branch

Open mpichelm opened this issue 1 year ago • 9 comments

Version/Branch of Dear ImGui:

Version 1.91.8 WIP (19171), Branch: docking

Back-ends:

imgui_impl_glfw.cpp + imgui_impl_opengl3.cpp

Compiler, OS:

Ubuntu 24.01.1 LTS + g++ 13.3.0

Full config/build information:

Dear ImGui 1.91.8 WIP (19171)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=201103
define: __linux__
define: __GNUC__=13
define: IMGUI_HAS_VIEWPORT
define: IMGUI_HAS_DOCK
--------------------------------
io.BackendPlatformName: imgui_impl_glfw
io.BackendRendererName: imgui_impl_opengl3
io.ConfigFlags: 0x00000483
 NavEnableKeyboard
 NavEnableGamepad
 DockingEnable
 ViewportsEnable
io.ConfigViewportsNoDecoration
io.ConfigNavCaptureKeyboard
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x00001C0E
 HasMouseCursors
 HasSetMousePos
 PlatformHasViewports
 HasMouseHoveredViewport
 RendererHasVtxOffset
 RendererHasViewports
--------------------------------
io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,64
io.DisplaySize: 1280.00,720.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,3.00
style.FrameRounding: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00

Details:

Hi!

I'm encountering an issue with the docking branch related to window dragging. Specifically, when I drag a window to the edge of the screen, the window stops at the border, but the mouse pointer continues moving. This causes the mouse pointer and the window to become desynchronized.

Issue Details

  • Reproducible Example: The issue can be reproduced using the example_glfw_opengl3 sample in the current 1.91.8 WIP version.
  • Platform Specificity:
    • Ubuntu (g++): The issue occurs consistently.
    • Windows: The behavior differs and works as expected. On Windows, dragging a window to the screen's edge allows it to move partially outside the screen, which matches the expected behavior.
  • I've attached a video for clarity on the problem.

Steps to Reproduce

  1. Run the example_glfw_opengl3 sample and navigate to the "Dockspace" example in the "Examples" menu.
  2. Drag a window to the bottom of the screen. Continue dragging after the window reaches the edge, allowing the mouse pointer to move further.
  3. Attempt to interact with the window by clicking inside it. You'll notice the mouse pointer is desynchronized and has an offset.
  4. If you manage to click the window's top bar, you can drag it back up, which seems to temporarily recover the synchronization. However, the window becomes unstable and shaky.
  5. Deleting the .ini file and restarting the program is required to fully recover normal functionality.

Please let me know if you need additional details, testing, or logs to address this issue. I really appreciate your support, thanks!

Screenshots/Video:

https://github.com/user-attachments/assets/b8d2757b-76d2-409a-b76a-4528aec2ff06

Minimal, Complete and Verifiable Example code:

The example_glfw_opengl3 sample in the current 1.91.8 WIP version can be used to test it.

mpichelm avatar Jan 19 '25 09:01 mpichelm

I believe this is a duplicate of https://github.com/ocornut/imgui/issues/3899 and https://github.com/ocornut/imgui/issues/8251

Unfortunately the issue is upstream of Dear ImGui (and possibly even upstream of GLFW/SDL): https://github.com/ocornut/imgui/issues/3899#issuecomment-803845959

It might be worth trying @rokups's workaround: https://github.com/rokups/imgui/commit/cdcd58afc322a6615a5fa01ad7984354be64cac5

PathogenDavid avatar Jan 19 '25 20:01 PathogenDavid

Might also be same as #6711. I should investigate this eventually as I can repro on a Mac too.

ocornut avatar Jan 20 '25 16:01 ocornut

Thank you both for your quick feedback! And I'm sorry about the duplicate... I guess my initial research was not good enough. I will try the workaround as soon as possible, thanks for the suggestion!

mpichelm avatar Jan 20 '25 20:01 mpichelm

I must apologize that this problem hasn't been problem for a long while, principally because it doesn't reproduce on Windows. I will try to address this...

ocornut avatar Jan 20 '25 20:01 ocornut

No need to apologize, the library is fantastic! After reading the other issues you mentioned, I can see why this might be challenging to address. I’ll stay tuned for any updates if a solution is found. Thank you so much for your dedication!

mpichelm avatar Jan 20 '25 21:01 mpichelm

And I'm sorry about the duplicate... I guess my initial research was not good enough.

No worries at all! It's a weird problem to describe so it's people are going to use different terminology.

(I actually had to re-find them by looking through all the issues labeled both multi-viewports and Linux since I knew they existed but my keyword searches also drew a blank.)

PathogenDavid avatar Jan 21 '25 10:01 PathogenDavid

Hi Could someone please summarize the status of that issue. Because is definitively present in my setup and as far as I see it have been not fixed for really long time. I can fix that at the application level, but for sure it should be addressed inside ImGui itself.

My setup: Dear ImGui version 1.91.8, branch docking, glfw back-end, Ubuntu 22.

anton-mir avatar Feb 19 '25 09:02 anton-mir

It is a wayland related issue. I managed to reproduce the same issue without using imgui.

STEPS TO REPRODUCE

  1. Create glfw widow with GLFW_DECORATOR = FALSE

  2. Set the mouse callbacks to drag the screen as you move the cursor

You will se the issue as you drag the window

BlueMoonHunt avatar Feb 25 '25 06:02 BlueMoonHunt

As has been pointed out elsewhere (e.g., https://github.com/ocornut/imgui/issues/8251), the issue stems from the fact that ImGui assumes that any given window position is always allowed by the backend. However, on for example glfw + x11 this isn't the case. Glfw clamps the values to 0 - (monitor max width/height). So in this function ImGui::UpdateMouseMovingWindowNewFrame this line ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; seems to be the culprit.

To take into account the fact that some backends might clamp the values, that "pos" would need to be calculated using MouseDelta value instead and then clamp the new value calculated by the delta. This still has the issue though that the value will not be clamped.

Now, it would be great if it'd be possible to do some sort of dry run call to glwf function to set the window pos which would return the actual clamped position and then clamp the pos if glfw so wants.

ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
const auto actual_pos = g.PlatformIO.Platform_SetWindowPos_DryRun(viewport, pos);
// Use the actual_pos

but I don't think that's possible. On top of that it'd need to work somehow with hopefully all of the supported backends. Anyhow, at least how glfw (with x11) seems to work is that the clamping values are not tied to the specific monitors but to the min/max value of the whole monitor setup. E.g., if you have 2 monitors A & B and A has a height of 500 and B has a height of 200, then glfw will clamp the y values between 0 and 500 (almost... actually it'd be 500 minus the toolbar/panel height). Thus the x value just needs to be clamped between 0 and combined monitor width minus window width and the y value needs to be clamped between 0 and max monitor height minus window height.

So if you want a quick fix to the issue you can create this function to imgui.cpp

#ifdef linux
ImVec2 GetDesktopDimensions()
{
    ImVec2 max(-FLT_MAX, -FLT_MAX);
    const ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
    for (int i = 0; i < platform_io.Monitors.Size; ++i)
    {
        const ImGuiPlatformMonitor& monitor = platform_io.Monitors[i];
        const ImVec2 end = monitor.WorkPos + monitor.WorkSize;
        if (end.x > max.x) {
            max.x = end.x;
        }
        if (end.y > max.y) {
            max.y = end.y;
        }
    }
    return max;
}
#endif

Then find the function ImGui::UpdateMouseMovingWindowNewFrame from imgui.cpp and replace the beginning with

void ImGui::UpdateMouseMovingWindowNewFrame()
{
    ImGuiContext& g = *GImGui;
    if (g.MovingWindow != NULL)
    {
        // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).
        // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency.
        KeepAliveID(g.ActiveId);
        IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindowDockTree);
        ImGuiWindow* moving_window = g.MovingWindow->RootWindowDockTree;

        // When a window stop being submitted while being dragged, it may will its viewport until next Begin()
        const bool window_disappared = (!moving_window->WasActive && !moving_window->Active);
#ifdef linux
        static bool is_dragging{false};
        if (g.IO.MouseReleased[0]) {
            is_dragging = false;
        }
#endif
        if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos) && !window_disappared)
        {
#ifdef linux
            static ImVec2 pos{0, 0};
            if (!is_dragging) {
                pos = g.IO.MousePos - g.ActiveIdClickOffset;
                is_dragging = true;
            } else {
                pos += g.IO.MouseDelta;
            }
            const ImVec2 monitor_worksize = GetDesktopDimensions();
            const float max_x = monitor_worksize.x - moving_window->Rect().GetWidth();
            const float max_y = monitor_worksize.y - moving_window->Rect().GetHeight();
            pos.x = (pos.x < 0.f ? 0.f : (pos.x > max_x ? max_x : pos.x));
            pos.y = (pos.y < 0.f ? 0.f : (pos.y > max_y ? max_y : pos.y));
#else
            ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
#endif
            // The function then continues with...
            // if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
            // {
            //     SetWindowPos(moving_window, pos, ImGuiCond_Always);
            // ...

Noteworthy is that this change will restrict the window position within the screen bounds even if the backend & windowing system would not clamp the values. Solving that is another issue (and that's also why I limited the change to linux builds in the snippets above).

hultsi avatar Apr 13 '25 19:04 hultsi