ocornut / imgui

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

Window position constraint inside parent region #4356

Open uraymeiviar opened 3 years ago

uraymeiviar commented 3 years ago

Version/Branch of Dear ImGui:

Version: 1.83 Branch: master / release

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_opengl3 Compiler: msvc 2019 Operating System: windows

My Issue/Question:

I want to restrict window position inside parent area, but there seem no SetNextWindowPosConstraints() , so I made it manually, but the result is flickering window jumping out and inside of parent area, is there any better solution than this?

yes I can use BeginChild() instead but I need the window to be "floating" and resizable

Screenshots/Video

Animation

Standalone, minimal, complete and verifiable example: (see https://github.com/ocornut/imgui/issues/2261)

if (ImGui::BeginChild("##ParentArea", ImVec2(0.0f, 0.0f),false,ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) {
    ImVec2 vMin = ImGui::GetWindowContentRegionMin();
    ImVec2 vMax = ImGui::GetWindowContentRegionMax();
    vMin.x += ImGui::GetWindowPos().x;
    vMin.y += ImGui::GetWindowPos().y;
    vMax.x += ImGui::GetWindowPos().x;
    vMax.y += ImGui::GetWindowPos().y;

    if (this->childWinPos.x < vMin.x) {
        this->childWinPos.x = vMin.x;
        ImGui::SetNextWindowPos(this->childWinPos);
    }
    if (this->childWinPos.y < vMin.y) {
        this->childWinPos.y = vMin.y;
        ImGui::SetNextWindowPos(this->childWinPos);
    }

    if (ImGui::Begin("ChildWindow", nullptr, ImGuiWindowFlags_NoCollapse)) {
        this->childWinPos  = ImGui::GetWindowPos();

        //...
        ImGui::End();
    }
}
ImGui::EndChild();
ocornut commented 3 years ago

You could call FindWindowByName("ChildWindow") to peek at the position before callingBegin()`.

Before we can investigating this further I would like to understand your use case and why you want that clamping.

uraymeiviar commented 3 years ago

because the app has two column workspace, the left panel (1st column) would be something like toolbox area, while the second column is the area where multiple floating window will be contained, so it would be like old-school windows MDI app

bars-mdi-overview-animation126683 )

aybe commented 1 year ago

@ocornut, you said:

Linking to #2827 #4356. This is best discussed in #4356.

While not answering your issue as worded, please note two things may indirectly solve your problem:

  • io.ConfigWindowsMoveFromTitleBarOnly enforce title bar being always visible.
  • style.DisplayWindowPadding ensure at least that amount of windows are visible.

io.ConfigWindowsMoveFromTitleBarOnly is mostly right, it depends on where you drag from.

style.DisplayWindowPadding, not sure how one would use that in this case to be honest.

But I've got some proof of concept that it's possible.

There's no more flicker like aforementioned but it's not tightly integrated at all with the rest, resizing etc.

TestDI_vEwucRRSuh

private bool IsMouseDown;

private Vector2 LastMousePosition;

private Rectangle<float> Rectangle = new(Vector2D<float>.Zero, new Vector2D<float>(300));

private void DrawWindow()
{
    if (ImGui.IsMouseDown(ImGuiMouseButton.Left))
    {
        if (IsMouseDown is false)
        {
            LastMousePosition = ImGui.GetMousePos();
        }

        IsMouseDown = true;
    }
    else
    {
        IsMouseDown = false;
    }

    var mousePos2 = ImGui.GetMousePos();

    if (mousePos2 != LastMousePosition)
    {
        if (IsMouseDown)
        {
            if (Rectangle.Contains(mousePos2.ToGeneric()))
            {
                var delta = mousePos2 - LastMousePosition;
                var min = Vector2.Zero;
                var max = ImGui.GetMainViewport().Size - Rectangle.Size.ToSystem();
                var pos = Vector2.Clamp(Rectangle.Origin.ToSystem() + delta, min, max);
                Rectangle.Origin = pos.ToGeneric();
                LastMousePosition = mousePos2;
            }
        }
    }

    ImGui.SetNextWindowPos(Rectangle.Origin.ToSystem());
    ImGui.SetNextWindowSize(Rectangle.Size.ToSystem());

    if (ImGui.Begin("testing!!", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize))
    {
        ImGui.Text($"Origin: {Rectangle.Origin}");
        ImGui.Text($"Size: {Rectangle.Size}");
        var xy = ImGui.GetWindowPos();
        var sz = ImGui.GetWindowSize();
        Rectangle = new Rectangle<float>(xy.X, xy.Y, sz.X, sz.Y);
    }

    ImGui.End();
}

But it needs much more polishing and that is clearly going to take quite some time.

Ideally, if IMGUI could support that out of the box, that'd be really great.

Until then, I'll stick with the original behavior just because it's well tested and reliable!

tromero commented 1 year ago

I'm achieving similar results to @aybe's GIF example. Here's my method of clamping my window to the screen edges which seems to work well enough for me and doesn't need to worry about checking mouse input:

    ImGuiWindow* existingWindow = ImGui::FindWindowByName(windowTitleStr);
    if (existingWindow != nullptr)
    {
        bool needsClampToScreen = false;
        ImVec2 targetPos = existingWindow->Pos;
        if (existingWindow->Pos.x < 0.0f)
        {
            needsClampToScreen = true;
            targetPos.x = 0.0f;
        }
        else if (existingWindow->Size.x + existingWindow->Pos.x > ImGui::GetMainViewport()->Size.x)
        {
            needsClampToScreen = true;
            targetPos.x = ImGui::GetMainViewport()->Size.x - existingWindow->Size.x;
        }
        if (existingWindow->Pos.y < 0.0f)
        {
            needsClampToScreen = true;
            targetPos.y = 0.0f;
        }
        else if (existingWindow->Size.y + existingWindow->Pos.y > ImGui::GetMainViewport()->Size.y)
        {
            needsClampToScreen = true;
            targetPos.y = ImGui::GetMainViewport()->Size.y - existingWindow->Size.y;
        }

        if (needsClampToScreen) // Necessary to prevent window from constantly undocking itself if docked.
        {
            ImGui::SetNextWindowPos(targetPos, ImGuiCond_Always);
        }
    }

    //ImGui::Begin(); etc

This has the same issue as the gif in #5719 where resizing the window into the edge/corner of the screen causes the window to grow from the other side.

I also discovered if I dock the window that uses this code, SetNextWindowPos() causes the window to auto undock as soon as I drag the two docked windows to the edge of the screen.