Skip to content

Prevent window collision when moving #8483

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

Open
TheMode opened this issue Mar 10, 2025 · 11 comments
Open

Prevent window collision when moving #8483

TheMode opened this issue Mar 10, 2025 · 11 comments

Comments

@TheMode
Copy link
Contributor

TheMode commented Mar 10, 2025

Version/Branch of Dear ImGui:

master

Back-ends:

SDL3 + SDL_GPU

Compiler, OS:

Windows 11 MSVC

Full config/build information:

No response

Details:

I am making an infinite canvas application, and I wish to have ImGui windows over it, with the ability to be moved/resized and follow the camera.

I got it somewhat working with this simple solution (please tell me if there is a better way)

const ImVec2 camera_delta = app->camera - app->camera_prev;
...
if (camera_moved) {
  ImGui::SetWindowPos(ImGui::GetWindowPos()-camera_delta);
}

(Got the idea from #1604)

One problem however, seems that the window does not want to go out of view, which seem fair as a default since you do not want to lose track of it, but in my case it is mostly a nuisance.

Screenshots/Video:

ezyZip.mp4

Minimal, Complete and Verifiable Example code:

No response

@GamingMinds-DanielC
Copy link
Contributor

I got it somewhat working with this simple solution (please tell me if there is a better way)

Just a small thing. Deltas are usually the other way around: current value minus previous value. That way they correspond to the direction of movement and must be added instead of subtracted to update a value. It is functionally identical to your version with the flipped sign, but not following the norm might get confusing for someone else using your code.

@TheMode
Copy link
Contributor Author

TheMode commented Mar 11, 2025

Fair enough, messed up my pan code and fixed it shortly after writing the issue, will edit to reduce confusion

@leonardovac
Copy link

leonardovac commented Mar 11, 2025

TLDR

Use SetNextWindowPos() and NOT SetWindowsPos()

Example

// Variable to store current window position
static ImVec2 windowPos {}; // Properly initialize this

if (ImGui::IsKeyDown(ImGuiKey_LeftArrow)) windowPos = { windowPos.x - 1, windowPos.y };
if (ImGui::IsKeyDown(ImGuiKey_UpArrow)) windowPos = { windowPos.x, windowPos.y - 1 };
if (ImGui::IsKeyDown(ImGuiKey_RightArrow)) windowPos = { windowPos.x + 1, windowPos.y };
if (ImGui::IsKeyDown(ImGuiKey_DownArrow)) windowPos = { windowPos.x, windowPos.y + 1 };

// Properly setting the position
ImGui::SetNextWindowPos(windowPos);

ImGui::Begin("Dear ImGui", nullptr, ImGuiWindowFlags_None);
{
	ImGui::Text("Hello, world!");

	windowPos = ImGui::GetWindowPos(); // Update
}
ImGui::End();

Why

imgui.cpp (line: 7390)

// Clamp position/size so window stays visible within its viewport or monitor
// Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing.
if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow))
    if (viewport_rect.GetWidth() > 0.0f && viewport_rect.GetHeight() > 0.0f)
        ClampWindowPos(window, visibility_rect);

How window_pos_set_by_api is changed (imgui.cpp (line: 7122)):

// Process SetNextWindow***() calls
// (FIXME: Consider splitting the HasXXX flags into X/Y components
bool window_pos_set_by_api = false;
bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;
if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos)
{
    window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0;
    if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f)
    {
        // May be processed on the next frame if this is our first frame and we are measuring size
        // FIXME: Look into removing the branch so everything can go through this same code path for consistency.
        window->SetWindowPosVal = g.NextWindowData.PosVal;
        window->SetWindowPosPivot = g.NextWindowData.PosPivotVal;
        window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
    }
    else
    {
        SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond);
    }
}
IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0);  // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects.

@TheMode
Copy link
Contributor Author

TheMode commented Mar 11, 2025

I may be doing something wrong then, I end up unable to drag the window (GetWindowPos returning the same as my SetNextWindowPos value).

Your example with keys down is indeed what I had prior (with SetNextWindowPos), but having to press keys is more cumbersome than simply dragging. Especially if I wish to support docking later one.

Although resizing the window down works fine and give me the updated position. And GetWindowSize is also properly updated.

@leonardovac
Copy link

leonardovac commented Mar 12, 2025

The key presses were just an example. You can’t move the window because you're constantly setting its position (otherwise, it would clamp). I didn't really think about dragging.

As you already figured out in #8485, you'd need a new flag.

@TheMode
Copy link
Contributor Author

TheMode commented Mar 12, 2025

SetNextWindowSize and GetWindowSize do however work, as in the value get updated on resize although constantly re-set in the loop. (and resizing down also move down GetWindowPos even with the constant pos set)

So perhaps that the bug is actually the SetNextWindowPos + GetWindowPos combo breaking dragging. No clue how to fix it though so I'd likely have to leave it to someone more experienced (if this is actually a bug)

@GamingMinds-DanielC
Copy link
Contributor

I took another look at your canvas and noticed some more problems. When using normal windows on your canvas, they are not clipped to your canvas. You are basically restricting yourself to non-docking ImGui with a full screen canvas. Even something small like a main menu above the canvas can break this. Your new flag to remove clamping would help only in this restricted scenario.

So just as a suggestion: you should attempt to get your canvas working as an item inside of a window (which can itself span the entire viewport) and your windows as child windows, after all they are conceptually child windows to your canvas. That way you can use docking, multiple canvases in multiple windows and all that stuff. Child windows are clipped to their parent and also not subjected to clamping. You would need to implement window dragging for canvas windows yourself, but if you design your interface carefully you only need to do it once (f.e. in wrapper functions like BeginCanvasWindow()/EndCanvasWindow()).

@TheMode
Copy link
Contributor Author

TheMode commented Mar 12, 2025

It sounds interesting, but also a bit messy. If I am to implement my own dragging, won't I have to write some sort of docking support as well?
By "child windows" do you mean BeginChild? If so, I don't even believe that they have the concept of "bring to front" or any other flags normal windows have, I feel like I would end up duplicating a significant part of ImGui

Guess I will have to give the docking branch a check

EDIT:
Updated my project to the docking branch, SetWindowPos was indeed problematic, I instead added a new dirty_pos param and used SetNextWindowPos. Everything work fine, can dock, resize, except that moving the canvas (invalidating the position, resulting in a SetNextWindowPos call) undock everything.
There is most likely a simple solution to this

@GamingMinds-DanielC
Copy link
Contributor

I guess I was assuming your intentions for your canvas wrong. I was thinking of f.e. nodes in a node graph based on child windows in a canvas widget. Those wouldn't need full window functionality and would most likely be data driven, so adjusting draw order by sorting the issue order would be possible.

Now I think I see what your intentions actually are. The canvas approach I described wouldn't fit that and docking with additional viewports most likely is not compatible with that idea either.

While a truly infinite canvas isn't possible due to floating point precision issues, you could try to adjust ImGuiStyle::DisplayWindowPadding and ImGuiStyle::DisplaySafeAreaPadding to massive negative values, that would effectively eliminate clamping (not sure about possible side effects though) without modifying library sources.

@ocornut
Copy link
Owner

ocornut commented Mar 12, 2025

Disclaimer I haven't read this thread nor the other carefully yet, but I have been meaning to add a concept of decorated child windows, aka windows that looks normal but are part of the boundaries of their parent. This would essentially allow for that.

@TheMode
Copy link
Contributor Author

TheMode commented Mar 12, 2025

Yeah that would for sure be the ideal solution.

For now, it seems that my hacked version works well enough. Except SetNextWindowPos causing the window to undock itself.

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

No branches or pull requests

4 participants