ocornut / imgui

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

A begin frame that is moved to a new viewport will no longer sync position #3695

Open mvphilip opened 3 years ago

mvphilip commented 3 years ago

Version/Branch of Dear ImGui:

Version: 1.80WIG Branch: docking

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_opengl2.h Compiler: gcc 10 Operating System: Ubuntu 18.04

My Issue/Question:

Hello, I create a dockspace and then a

Begin("top") Begin("child") End() End()

I also have viewports enabled and if "top" has a new new viewport created I will reassign child to that new viewport with SetNextWindowViewport().

The issue is that the "child" will no longer sync position with "top". If top is moved, the child will remain in its old location but not quite because it seems to jerk around. If we return "top" to MainViewport everything starts working again. The Metrics/Debugger confirms that the "child" window is indeed a part of the "top" viewport. If we replace Begin with BeginChild this works perfectly. Please see the screen capture for the example.

Is this use model supported? I've read through the faq and searched through github issues but there are only a few cases of SetNextWindowViewport so its not clear to me if this should work.

What I would like to do is have a moveable/sizeable frame with the ability to host text and/or rendering inside "top". I've experimented around with BeginChild but in that case I have to deal with a lot of tracking in terms of hovering/dragging and relative position. But if the use case of nested "Begin" in viewports is not supported would you have any comment on my BeginChild approach?

Thank you,

Screenshots/Video gdVeDCJYFj

Standalone, minimal, complete and verifiable example: (see https://github.com/ocornut/imgui/issues/2261) The test cases uses the example_glfw_opengl2/main.cpp . Please replace the while block with:

  while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    ImGui_ImplOpenGL2_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

    ImGui::SetNextWindowPos(ImGui::GetWindowViewport()->Pos, ImGuiCond_Always);
    ImGui::SetNextWindowSize(ImVec2(500, 500), ImGuiCond_Always);

    ImGui::Begin(
        "dock", nullptr,
        ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoBringToFrontOnFocus |
            ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMouseInputs |
            ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoNavInputs |
            ImGuiWindowFlags_NoScrollbar);

    ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
    ImGui::DockSpace(dockspace_id);
    static ImGuiID parentViewPortID = ImGui::GetWindowViewport()->ID;
    ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver);
    if (ImGui::Begin("top", nullptr,
                     ImGuiWindowFlags_NoScrollbar |
                         ImGuiWindowFlags_NoBackground |
                         ImGuiWindowFlags_NoBringToFrontOnFocus)) {

      auto viewPortX = ImGui::GetWindowViewport()->Pos.x;
      auto viewPortY = ImGui::GetWindowViewport()->Pos.y;
      ImGui::SetNextWindowSize(ImVec2(100, 100), ImGuiCond_Once);
      ImGui::SetNextWindowPos(ImVec2(viewPortX + 200, viewPortY + 200),
                              ImGuiCond_Once);
      auto currentParentID = ImGui::GetWindowViewport()->ID;
      ImGui::Text("top %u %u", parentViewPortID, currentParentID);
      if (parentViewPortID != currentParentID) {
        ImGui::SetNextWindowViewport(currentParentID);
      }
      ImGui::Begin("child", nullptr, 0);
      ImGui::Text("child %u %u", parentViewPortID, currentParentID);
      ImGui::End();
    }
    ImGui::End();
    ImGui::End();

    ImGui::SetNextWindowSize(ImVec2(1200, 1200), ImGuiCond_Once);
    ImGui::SetNextWindowPos(ImVec2(5047, 966), ImGuiCond_Once);
    ImGui::ShowMetricsWindow(nullptr);

    ImGui::Render();
    int display_w, display_h;
    glfwGetFramebufferSize(window, &display_w, &display_h);
    glViewport(0, 0, display_w, display_h);
    glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
    glClear(GL_COLOR_BUFFER_BIT);

    ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData());
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
      GLFWwindow* backup_current_context = glfwGetCurrentContext();
      ImGui::UpdatePlatformWindows();
      ImGui::RenderPlatformWindowsDefault();
      glfwMakeContextCurrent(backup_current_context);
    }

    glfwSwapBuffers(window);
  }
mvphilip commented 3 years ago

Hello I have a work around to the child issue. I save the current location of the child on every pass. And then if current host becomes a viewport I check for mouse drag events and save the delta. The key is that you must reset ResetMouseDragDelta() on every pass. The updated code is below in case someone runs into this problem and it's not working natively. I'm not sure how expensive this so I hope that it can be supported natively.

  while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    ImGui_ImplOpenGL2_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

    ImGui::SetNextWindowPos(ImGui::GetWindowViewport()->Pos, ImGuiCond_Always);
    ImGui::SetNextWindowSize(ImVec2(500, 500), ImGuiCond_Always);

    ImGui::Begin(
        "dock", nullptr,
        ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoBringToFrontOnFocus |
            ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMouseInputs |
            ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoNavInputs |
            ImGuiWindowFlags_NoScrollbar);

    static float childX = 0;
    static float childY = 0;
    static float childOffsetX = 0;
    static float childOffsetY = 0;
    ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
    ImGui::DockSpace(dockspace_id);
    static ImGuiID parentViewPortID = ImGui::GetWindowViewport()->ID;
    ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver);
    if (ImGui::Begin("top", nullptr,
                     ImGuiWindowFlags_NoScrollbar |
                         ImGuiWindowFlags_NoBackground |
                         ImGuiWindowFlags_NoBringToFrontOnFocus)) {

      auto viewPortX = ImGui::GetWindowViewport()->Pos.x;
      auto viewPortY = ImGui::GetWindowViewport()->Pos.y;
      ImGui::SetNextWindowSize(ImVec2(100, 100), ImGuiCond_Once);
      ImGui::SetNextWindowPos(ImVec2(viewPortX + 200, viewPortY + 200),
                              ImGuiCond_Once);
      auto currentParentID = ImGui::GetWindowViewport()->ID;
      ImGui::Text("top %u %u", parentViewPortID, currentParentID);
      if (parentViewPortID != currentParentID) {
        ImGui::SetNextWindowViewport(currentParentID);
        ImGui::SetNextWindowPos(
            ImVec2(ImGui::GetWindowViewport()->Pos.x + childX + childOffsetX,
                   ImGui::GetWindowViewport()->Pos.y + childY + childOffsetY),
            ImGuiCond_Always);
        childOffsetX = 0;
        childOffsetY = 0;
      }
      ImGui::Begin("child", nullptr, 0);
      ImGui::Text("child %u %u", parentViewPortID, currentParentID);
      if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow) &&
          ImGui::IsMouseDragging(0)) {
        childOffsetX = ImGui::GetMouseDragDelta(0, 0.0f).x;
        childOffsetY = ImGui::GetMouseDragDelta(0, 0.0f).y;
        ImGui::ResetMouseDragDelta();
      }
      childX = ImGui::GetWindowPos().x - viewPortX;
      childY = ImGui::GetWindowPos().y - viewPortY;
      ImGui::End();
    }
    ImGui::End();
    ImGui::End();

    ImGui::SetNextWindowSize(ImVec2(1200, 1200), ImGuiCond_Once);
    ImGui::SetNextWindowPos(ImVec2(5047, 966), ImGuiCond_Once);
    ImGui::ShowMetricsWindow(nullptr);

    ImGui::Render();
    int display_w, display_h;
    glfwGetFramebufferSize(window, &display_w, &display_h);
    glViewport(0, 0, display_w, display_h);
    glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
    glClear(GL_COLOR_BUFFER_BIT);

    ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData());
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
      GLFWwindow* backup_current_context = glfwGetCurrentContext();
      ImGui::UpdatePlatformWindows();
      ImGui::RenderPlatformWindowsDefault();
      glfwMakeContextCurrent(backup_current_context);
    }

    glfwSwapBuffers(window);
  }
ocornut commented 3 years ago

Hello,

For a start please read the Glossary ( https://github.com/ocornut/imgui/wiki/Glossary ) because you are using lots of misleading terms.

At core the issue is that your viewport created for "top" is not a host viewport. We ought to make it possible to have 0 to N host viewports (rather than always 1) in a more flexible manner, this is discussed in various issues but it's not currently possible.

mvphilip commented 3 years ago

Thanks! And my apologies. Is there a way to create a new host viewport even in a non-flexible way using the apis or would I have to modify imgui?

-mp