imgui icon indicating copy to clipboard operation
imgui copied to clipboard

How to programmatically dock a window A to another window B?

Open shi-yan opened this issue 4 years ago • 6 comments

My Issue/Question:

I can't figure out how to programmatically dock a window to another.

I have the following code which configures the global docking space. it works well:

         static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_PassthruCentralNode;
        ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;

        ImGuiViewport *viewport = ImGui::GetMainViewport();
        ImGui::SetNextWindowPos(viewport->Pos);
        ImGui::SetNextWindowSize(viewport->Size);
        ImGui::SetNextWindowViewport(viewport->ID);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
        window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
        window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;

        if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
            window_flags |= ImGuiWindowFlags_NoBackground;

        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
        ImGui::Begin("DockSpace", nullptr, window_flags);
        ImGui::PopStyleVar();
        ImGui::PopStyleVar(2);

        // DockSpace
        ImGuiIO &io = ImGui::GetIO();
        if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable)
        {
            ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
            ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);

            static auto first_time = true;
            if (first_time)
            {
                first_time = false;

                ImGui::DockBuilderRemoveNode(dockspace_id); // clear any previous layout
                ImGui::DockBuilderAddNode(dockspace_id, dockspace_flags | ImGuiDockNodeFlags_DockSpace);
                ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size);

                auto dock_id_right = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.25f, nullptr, &dockspace_id);
                ImGui::DockBuilderDockWindow("Control Panel", dock_id_right);
                ImGui::DockBuilderDockWindow("Relight window", dockspace_id);
                ImGui::DockBuilderFinish(dockspace_id);
            }
        }

        ImGui::End();

But I also want to dock a window A to another window B's docking area. I can't find any examples, nor could I find an API to get window B's docking area's ID. I tried the following:


    bool open = false;
    ImGui::SetNextWindowSize(ImVec2(m_initialWidth, m_initialHeight), ImGuiCond_Once);
    ImGui::Begin(m_title.c_str() );

            ImVec2 availableSize = ImGui::GetContentRegionAvail();
            ImGuiID dockspace_id = ImGui::GetID("RelightDockSpace");
            ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_PassthruCentralNode);

            static auto first_time = true;
            if (first_time)
            {
                first_time = false;

                ImGui::DockBuilderRemoveNode(dockspace_id); // clear any previous layout
                ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_DockSpace);
                ImGui::DockBuilderSetNodeSize(dockspace_id, availableSize);

                auto dock_id_right = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.25f, nullptr, &dockspace_id);

                ImGui::DockBuilderDockWindow("Debug window", dock_id_right);
                ImGui::DockBuilderFinish(dockspace_id);
            }
        
    if ( ImGui::IsMouseReleased( ImGuiMouseButton_Left) || m_width == 0 ){
        // std::cout << availableSize.x << " " << availableSize.y << std::endl;
        // should I resize my FBO here
        if (availableSize.x != m_width || availableSize.y != m_height)
        {
            m_width = availableSize.x;
            m_height = availableSize.y;
            m_isFrameBufferDirty = true;
            onResize(m_width, m_height);
        }
    }
    //draw FBO
    ImGui::Image((void *)m_frameBufferTextureID, availableSize, ImVec2(0, 1),ImVec2(1, 0));
    ImGui::End();

This doesn't work at all. I expect the window named "Debug window" should appear within the current window. But the above code seems to create another docking area for the current window, the new docking area pushes everything outside the window's boundary. and the "Debug window " is also not docked.

Screenshots/Video

What I want to achieve:

Screen Shot 2021-08-12 at 8 43 27 PM

what I see with the above code:

Screen Shot 2021-08-12 at 8 44 03 PM

There are other things I can't make sense,

why do we need to remove existing layout when creating a docking area when the docking area is freshly created?

shi-yan avatar Aug 13 '21 03:08 shi-yan

Do you mean something like this?

//--------------------------------------------------------------
void ofApp::dockingReset1()
{
	ImGuiViewport* viewport = ImGui::GetMainViewport();


	ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
	//ImGuiID dockspace_id = ImGui::GetID("DockSpace");


	static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_PassthruCentralNode;


	ImGui::DockBuilderRemoveNode(dockspace_id); // clear any previous layout
	ImGui::DockBuilderAddNode(dockspace_id, dockspace_flags | ImGuiDockNodeFlags_DockSpace);
	ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size);


	// split the dockspace into 2 nodes --
	// DockBuilderSplitNode takes in the following args in the following order
	//   window ID to split, direction, fraction (between 0 and 1),
	// the final two setting let's us choose which id we want (which ever one we DON'T set as NULL,
	// will be returned by the function)
	// out_id_at_dir is the id of the node in the direction we specified earlier,
	// out_id_at_opposite_dir is in the opposite direction
	auto dock_id_top = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Up, 0.2f, nullptr, &dockspace_id);
	auto dock_id_down = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Down, 0.25f, nullptr, &dockspace_id);
	auto dock_id_left = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.2f, nullptr, &dockspace_id);
	auto dock_id_right = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.15f, nullptr, &dockspace_id);
	//auto dock_id_left2 = ImGui::DockBuilderSplitNode(dock_id_left, ImGuiDir_Down, 0.2f, nullptr, &dock_id_left);
	//auto dock_id_down2 = ImGui::DockBuilderSplitNode(dock_id_down, ImGuiDir_Right, 0.15f, nullptr, &dock_id_down);


	// we now dock our windows into the docking node we made above


	ImGui::DockBuilderDockWindow("Window 1", dock_id_top);
	ImGui::DockBuilderDockWindow("Window 2", dock_id_right);
	ImGui::DockBuilderDockWindow("Window 3", dock_id_left);
	ImGui::DockBuilderDockWindow("Window 4", dock_id_down);
	ImGui::DockBuilderDockWindow("Window 0", dock_id_top);


	ImGui::DockBuilderFinish(dockspace_id);
}

From: https://github.com/moebiussurfing/ofxSurfingImGui/blob/master/3_Docking/3_0_Layout_Docking2/src/ofApp.cpp#L324-L361

A Guide for Docking: https://gist.github.com/moebiussurfing/d7e6ec46a44985dd557d7678ddfeda99

moebiussurfing avatar Aug 16 '21 08:08 moebiussurfing

I don't quite understand this line

ImGuiViewport* viewport = ImGui::GetMainViewport();

I want to dock a window a within another window b. is MainViewport the global docking area? or the dockspace of the window b?

shi-yan avatar Aug 16 '21 15:08 shi-yan

Sorry, viewport is not being used here.... I think there's more than one way of creating the main container. (borderless, transparent etc), using ImGui::DockSpace( or the ImGuiDockNodeFlags_PassthruCentralNode flag.

The point to splitting is here:

auto dock_id_top = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Up, 0.2f, nullptr, &dockspace_id);
auto dock_id_down = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Down, 0.25f, nullptr, &dockspace_id);
auto dock_id_left = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.2f, nullptr, &dockspace_id);
auto dock_id_right = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.15f, nullptr, &dockspace_id);
//auto dock_id_left2 = ImGui::DockBuilderSplitNode(dock_id_left, ImGuiDir_Down, 0.2f, nullptr, &dock_id_left);
//auto dock_id_down2 = ImGui::DockBuilderSplitNode(dock_id_down, ImGuiDir_Right, 0.15f, nullptr, &dock_id_down);

// we now dock our windows into the docking node we made above
ImGui::DockBuilderDockWindow("Window 1", dock_id_top);
ImGui::DockBuilderDockWindow("Window 2", dock_id_right);
ImGui::DockBuilderDockWindow("Window 3", dock_id_left);
ImGui::DockBuilderDockWindow("Window 4", dock_id_down);
ImGui::DockBuilderDockWindow("Window 0", dock_id_top);

"Window 1" "Window 2"... etc are the ImGui window names that you will create after.

Just try some of the guide examples: https://gist.github.com/moebiussurfing/8dbc7fef5964adcd29428943b78e45d2 https://gist.github.com/moebiussurfing/d7e6ec46a44985dd557d7678ddfeda99

moebiussurfing avatar Aug 16 '21 23:08 moebiussurfing

Hi @ocornut, reading here: https://discourse.dearimgui.org/t/init-a-docked-imgui-window/323/2 as well as here: https://github.com/ocornut/imgui/issues/2109

I'm questioning if this is still not do-able using the builder API?

I used some of @moebiussurfing examples and I'm unsuccessful docking a child window to another child node programmatically.

Here is my current approach for doing regular docking into the main view port (that works):

    static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_PassthruCentralNode;
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
    ImGuiViewport * viewport = ImGui::GetMainViewport();
    ImGui::SetNextWindowPos(viewport->Pos);
    ImGui::SetNextWindowSize(viewport->Size);
    ImGui::SetNextWindowViewport(viewport->ID);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
    window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
    window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
    if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) {
        window_flags |= ImGuiWindowFlags_NoBackground;
    }

    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
    ImGui::Begin("DockSpace", nullptr, window_flags);
    ImGui::PopStyleVar();
    ImGui::PopStyleVar(2);

    ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
    //ImGuiID dockspace_id = ImGui::GetMainViewport()->ID;
    ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
    static auto first_time = true;
    if (first_time) {
        first_time = false;
        ImGui::DockBuilderRemoveNode(dockspace_id);
        ImGui::DockBuilderAddNode(dockspace_id, dockspace_flags | ImGuiDockNodeFlags_DockSpace);
        ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size);

        auto dock_id_left = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.2f, nullptr, &dockspace_id);
        auto dock_id_down = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Down, 0.25f, nullptr, &dockspace_id);
        ImGui::DockBuilderDockWindow("Left", dock_id_left);
        ImGui::DockBuilderDockWindow("Down", dock_id_down);

        ImGui::DockBuilderFinish(dockspace_id);
    }
    ImGui::End();

    ImGui::Begin("Left");
    ImGui::Text("Hello, left!");
    ImGui::End();

    ImGui::Begin("Down");
    ImGui::Text("Hello, down!");
    ImGui::End();

If I want, I can also dock one window into another doing this change:

        auto dock_id_left = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.2f, nullptr, &dockspace_id);
        ImGui::DockBuilderDockWindow("Left", dock_id_left);
        ImGui::DockBuilderDockWindow("Down", dock_id_left);

That works. But what If I want to dock a child window into another window that is not docked into the main viewport? I tried changing the dockspace to target the child window like this:

    ImGuiID dockspace_id = ImGui::GetID("Left");
    ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
    static auto first_time = true;
    if (first_time) {
        first_time = false;
        ImGui::DockBuilderRemoveNode(dockspace_id);
        ImGui::DockBuilderAddNode(dockspace_id, dockspace_flags | ImGuiDockNodeFlags_DockSpace);
        ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size);

        auto dock_id_left = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.2f, nullptr, &dockspace_id);
        ImGui::DockBuilderDockWindow("Down", dock_id_left);

        ImGui::DockBuilderFinish(dockspace_id);
    }
    ImGui::End();

This doesn't work unfortunately. I was under the assumption that every window has its own dockspace?

stevewgr avatar Nov 06 '22 17:11 stevewgr

I found a workaround, but still isn't great...:

    ImGui::DockSpaceOverViewport(ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
    //ImGuiID dockspace_id = ImGui::DockSpaceOverViewport(ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);

    ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(300, 400));
    ImGui::Begin("Dashboard");
    ImGui::PopStyleVar();
    ImGuiID dockspace_id = ImGui::GetID("DockSpace");
    ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f));
    static auto first_time = true;
    if (first_time) {
        first_time = false;
        ImGui::DockBuilderRemoveNode(dockspace_id);
        ImGui::DockBuilderAddNode(dockspace_id);
        ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->Size);

        auto dock_id_up = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Up, 0.5f, nullptr, &dockspace_id);
        auto dock_id_down = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Down, 0.5f, nullptr, &dockspace_id);
        ImGui::DockBuilderDockWindow("Up", dock_id_up);
        ImGui::DockBuilderDockWindow("Down", dock_id_down);

        ImGui::DockBuilderFinish(dockspace_id);
    }
    ImGui::End();

    ImGui::Begin("Up");
    ImGui::Text("Hello, up!");
    ImGui::End();

    ImGui::Begin("Down");
    ImGui::Text("Hello, down!");
    ImGui::End();

So instead of docking one child window into another child window, what I'm doing here is creating a "Dashboard" like dockspace in addition to the dockspace in the main viewport. That allows me to dock child windows in there. @ocornut, generally I think it would have been much friendlier if every window was having its own dockspace by default when docking is enabled (ImGuiConfigFlags_DockingEnable).

image

stevewgr avatar Nov 06 '22 19:11 stevewgr

Here is the outcome: image

and snippet I call right after setting imgui frame:

void CN3UIDebug::RenderDockSpace() {
    ImGui::DockSpaceOverViewport(ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);

    ImGui::Begin(IMGUI_WND_ID_DASHBOARD);
    ImGuiID dsId = ImGui::GetID("DashboardDS");
    if (!ImGui::DockBuilderGetNode(dsId)) {
        ImGui::SetWindowSize(ImVec2(370.0f, 680.0f));
        ImGui::SetWindowPos(ImVec2(640.0f, 55.0f));

        ImGui::DockBuilderRemoveNode(dsId);
        ImGui::DockBuilderAddNode(dsId);

        ImGuiID dsIdCopy = dsId;
        ImGuiID dsIdUp = ImGui::DockBuilderSplitNode(dsIdCopy, ImGuiDir_Up, 0.34f, NULL, &dsIdCopy);
        ImGuiID dsIdDown = ImGui::DockBuilderSplitNode(dsIdCopy, ImGuiDir_Down, 0.0f, NULL, &dsIdCopy);
        ImGui::DockBuilderDockWindow(IMGUI_WND_ID_FPS, dsIdUp);
        ImGui::DockBuilderDockWindow(IMGUI_WND_ID_METRICS, dsIdDown);
        ImGui::DockBuilderDockWindow(IMGUI_WND_ID_DEMO, dsIdDown);

        ImGui::DockBuilderFinish(dsId);
    }
    ImGui::DockSpace(dsId);
    ImGui::End();
}

Note that I call ImGui::DockSpace last in order to check if the user changed the default docking layout (imgui.ini). That way if the user first launch the app, it will setup the default layout unless it was changed by the user.

stevewgr avatar Nov 07 '22 20:11 stevewgr

I think this might be the answer:

 void split()
 {
        ImGuiID parent_node=ImGui::DockBuilderAddNode();
        ImGui::DockBuilderSetNodePos(parent_node, ImGui::GetWindowPos());
        ImGui::DockBuilderSetNodeSize(parent_node, ImGui::GetWindowSize());
        ImGuiID nodeA;
        ImGuiID nodeB;
        ImGui::DockBuilderSplitNode(parent_node, ImGuiDir_Up, 0.8f, &nodeB, &nodeA);

        ImGui::DockBuilderDockWindow("A", nodeA);
        ImGui::DockBuilderDockWindow("B", nodeB);
 }

This code should be called inside Begin End of window B, or you need to provide size and position somehow.

uvec3 avatar Mar 11 '23 22:03 uvec3