Skip to content
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

How do exactly ImGui::DockBuilderXXX functions work? #6535

Open
SC5Shout opened this issue Jun 19, 2023 · 5 comments
Open

How do exactly ImGui::DockBuilderXXX functions work? #6535

SC5Shout opened this issue Jun 19, 2023 · 5 comments
Labels

Comments

@SC5Shout
Copy link

SC5Shout commented Jun 19, 2023

Version/Branch of Dear ImGui:

Version: v1.89 WIP
Branch: docking

My Issue/Question:

I have 2 windows, Properties and Canvas.

I want the Properties window to be on the left side and take 50% of the docking space and the Canvas to take the rest of the docking space. I'm having a hard time understanding how this all works.

I've already seen #2583, but it's not exactly what I want

I've tried:

mainDockID = ImGui::GetID(name.c_str());

ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_None;	
ImGui::DockBuilderDockWindow(name.c_str(), mainDockID);
ImGui::DockBuilderRemoveNode(mainDockID);

ImGuiID rootNode = ImGui::DockBuilderAddNode(mainDockID, ImGuiDockNodeFlags_DockSpace);
ImGuiID dockLeft;
ImGuiID dockRight = ImGui::DockBuilderSplitNode(rootNode, ImGuiDir_Right, 0.5f, nullptr, &dockLeft);

ImGui::DockBuilderDockWindow("Properties", dockLeft);
ImGui::DockBuilderDockWindow("Details", dockRight);
ImGui::DockBuilderFinish(mainDockID);

ImGui::DockSpace(mainDockID, ImVec2(0.0f, 0.0f), dockspaceFlags);

//does ImGui::Begin("Properties") etc.
UI_DrawProperties();
UI_DrawDetails();

I thought I'd split the docking space in half, but the Properties window takes like 90% of a space:

image

So, I've played around with the size_ratio_for_node_at_dir, but it only went worse.

I've tried setting DockBuilderSetNodePos and/or DockBuilderSetNodeSize.
I've tried to use DockBuilderSplitNode on ImGuiID dockLeft like this:

ImGuiID rootNode = ImGui::DockBuilderAddNode(mainDockID, ImGuiDockNodeFlags_DockSpace);
ImGuiID dockLeft = ImGui::DockBuilderSplitNode(rootNode, ImGuiDir_Left, 0.5f, nullptr, &rootNode);
ImGuiID dockRight = ImGui::DockBuilderSplitNode(rootNode, ImGuiDir_Right, 0.5f, nullptr, &dockLeft);

ImGui::DockBuilderDockWindow("Properties", dockLeft);
ImGui::DockBuilderDockWindow("Details", dockRight);

and then play around with the size_ratio_for_node_at_dir.

Nothing works as I wish.

For the main docking space it works:

void RenderInADockspace(auto funcT)
		{
			const ImGuiViewport* viewport = ImGui::GetMainViewport();
			ImGui::SetNextWindowPos(viewport->WorkPos);
			ImGui::SetNextWindowSize(viewport->WorkSize);
			ImGui::SetNextWindowViewport(viewport->ID);
			ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
			ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);

			static ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking;
			window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
			window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;

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

			float h = ui.DrawTopBar();
			float w = ui.DrawLeftBar();

			ImGui::SetCursorPosX(w + ImGui::GetCurrentWindow()->WindowPadding.x);
			ImGui::SetCursorPosY(h + ImGui::GetCurrentWindow()->WindowPadding.y);

			static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None | ImGuiDockNodeFlags_NoWindowMenuButton;

			ImGuiID dockspaceID = ImGui::GetID("MainDock");
			if (!ImGui::DockBuilderGetNode(dockspaceID)) {
				ImGui::DockBuilderRemoveNode(dockspaceID);
				ImGui::DockBuilderAddNode(dockspaceID, dockspace_flags);

				ImGuiID dock_main_id = dockspaceID;
				ImGuiID dock_up_id = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Up, 0.6f, nullptr, &dock_main_id);
				ImGuiID dock_down_id = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Down, 0.4f, nullptr, &dock_main_id);

				ImGui::DockBuilderDockWindow("Dear ImGui Demo", dock_up_id);
				ImGui::DockBuilderDockWindow("Content Browser", dock_down_id);

				ImGui::DockBuilderFinish(dock_main_id);
			}
			ImGui::DockSpace(dockspaceID, ImVec2(0.0f, 0.0f), dockspace_flags);

			funcT();

                        ImGui::End();
		}

in this example, I actually split vertically, but I guess it should not matter.

image

I've tried doing the same for the second docking space:

void RenderInADockspace2(auto funcT)
		{
			ImVec2 m_MinSize(200, 400), m_MaxSize(2000, 2000);
			ImGui::SetNextWindowSizeConstraints(m_MinSize, m_MaxSize);
			ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
			ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
			ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));

			static ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse;
			window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
                        ImGui::Begin(titleAndId.c_str(), &open, window_flags);
			ImGui::PopStyleVar(3);

			static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None | ImGuiDockNodeFlags_NoWindowMenuButton;

			dockspaceID = ImGui::GetID(name.c_str());
			if (!ImGui::DockBuilderGetNode(dockspaceID)) {
				ImGui::DockBuilderRemoveNode(dockspaceID);
				ImGui::DockBuilderAddNode(dockspaceID, dockspace_flags);

				ImGuiID dock_main_id = dockspaceID;
				ImGuiID dock_left_id = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Left, 0.5f, nullptr, &dock_main_id);
				ImGuiID dock_right_id = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Right, 0.5f, nullptr, &dock_main_id);

				ImGui::DockBuilderDockWindow("Properties", dock_left_id);
				ImGui::DockBuilderDockWindow("Details", dock_right_id);
	
				ImGui::DockBuilderFinish(dock_main_id);
			}
			ImGui::DockSpace(dockspaceID, ImVec2(0.0f, 0.0f), dockspace_flags);

			windowClass.ClassId = dockspaceID;
			windowClass.DockingAllowUnclassed = false;

			ImGui::SetNextWindowDockID(dockspaceID, ImGuiCond_Always);
			ImGui::SetNextWindowClass(getWindowClass());

			funcT();

                       ImGui::End();
		}

I use it like this:

RenderInADockspace([](){
    ImGui::Begin("Content Browser");
    ImGui::End();

    ImGui::ShowDemoWindow();
});

RenderInADockspace2([](){
    ImGui::SetNextWindowClass(&windowClass);
    ImGui::Begin("Properties");
    ImGui::End();

    ImGui::SetNextWindowClass(&windowClass);
    ImGui::Begin("Details");
    ImGui::End();
});

where windowClass is the one that was set in RenderInADockspace2 function.

It ends up not splitting the window anyhow, there're just 2 tabs:

image

expected result:

image

I believe it may be related to ImGui::SetNextWindowClass(&windowClass); function

@ocornut
Copy link
Owner

ocornut commented Jun 20, 2023

Hello,

I haven't looked at your case in much details yet, but notice that comment:

// - If you intend to split the node immediately after creation using DockBuilderSplitNode(), make sure
//   to call DockBuilderSetNodeSize() beforehand. If you don't, the resulting split sizes may not be reliable.

Try to call DockBuilderSetNodeSize() right after DockBuilderAddNode().
Due to how the system works currently it is needed in some situations.

I wonder if what's the thing causing your problem?
I believe it might be fixable in our end but it's not a simple change.

@SC5Shout
Copy link
Author

SC5Shout commented Jun 20, 2023

Alright, so I think I've just used to small vector in DockBuilderSetNodeSize. Now that I use {2000.0f, 2000.0f} it seems to work more or less as I wish. I'm wondering what is the best size I could possibly set? ImGui::GetContentRegionAvail() does not work fine

@SC5Shout
Copy link
Author

Alright, so I've achieved what I wanted without the DockBuilderSetNodeSize function.

I've got

ImGuiID dockspaceID = 0;
ImGuiID propertiesID = 0;
ImGuiID previewID = 0;
ImGuiID sceneID = 0;
ImGuiID detailsID = 0;

as a member variables

I split nodes like this:

static ImGuiDockNodeFlags dockFlags = ImGuiDockNodeFlags_None | ImGuiDockNodeFlags_NoWindowMenuButton;
dockspaceID = ImGui::GetID(titleAndId.c_str());

static ImGuiID leftID = 0;
static ImGuiID rightID = 0;
if (!ImGui::DockBuilderGetNode(dockspaceID)) {
	ImGui::DockBuilderRemoveNode(dockspaceID);
	ImGui::DockBuilderAddNode(dockspaceID, ImGuiDockNodeFlags_None);

	ImGuiID mainID = dockspaceID;

	// Split the main dockspace node into left and right nodes
	leftID = ImGui::DockBuilderSplitNode(mainID, ImGuiDir_Left, 0.3f, nullptr, &rightID);

	//split left node into top and bottom nodes
	propertiesID = ImGui::DockBuilderSplitNode(leftID, ImGuiDir_Up, 0.5f, nullptr, &previewID);

	//split right into left and right nodes
	sceneID = ImGui::DockBuilderSplitNode(rightID, ImGuiDir_Left, 0.8, nullptr, &detailsID);

	// Dock windows to the resulting nodes
	ImGui::DockBuilderDockWindow("Properties", propertiesID);
	ImGui::DockBuilderDockWindow("Scene Preview", previewID);
	ImGui::DockBuilderDockWindow("Canvas", sceneID);
	ImGui::DockBuilderDockWindow("Details", detailsID);

	ImGui::DockBuilderFinish(mainID);
}

ImGui::DockSpace(dockspaceID, ImVec2(0, 0), dockFlags);

and I use

ImGui::SetNextWindowClass
and 
ImGui::SetNextWindowDockID

before every window,

like this:

ImGui::SetNextWindowClass(&windowClass);
ImGui::SetNextWindowDockID(sceneID);
ImGui::Begin("Scene Preview");
//...
ImGui::End();

//... etc 

works great!

@ocornut
Copy link
Owner

ocornut commented Jun 21, 2023

so I think I've just used to small vector in DockBuilderSetNodeSize.

Basically there's an issue where shrinking down to small size is slightly "lossy" as there are occasional back and forth between ratio and absolute sizes following some interactions, and this breaks this situation. Will let you know if it gets improved.

@SC5Shout
Copy link
Author

SC5Shout commented Jun 21, 2023

Alright, so I've achieved what I wanted without the DockBuilderSetNodeSize function.

I've got

ImGuiID dockspaceID = 0;
ImGuiID propertiesID = 0;
ImGuiID previewID = 0;
ImGuiID sceneID = 0;
ImGuiID detailsID = 0;

as a member variables

I split nodes like this:

static ImGuiDockNodeFlags dockFlags = ImGuiDockNodeFlags_None | ImGuiDockNodeFlags_NoWindowMenuButton;
dockspaceID = ImGui::GetID(titleAndId.c_str());

static ImGuiID leftID = 0;
static ImGuiID rightID = 0;
if (!ImGui::DockBuilderGetNode(dockspaceID)) {
	ImGui::DockBuilderRemoveNode(dockspaceID);
	ImGui::DockBuilderAddNode(dockspaceID, ImGuiDockNodeFlags_None);

	ImGuiID mainID = dockspaceID;

	// Split the main dockspace node into left and right nodes
	leftID = ImGui::DockBuilderSplitNode(mainID, ImGuiDir_Left, 0.3f, nullptr, &rightID);

	//split left node into top and bottom nodes
	propertiesID = ImGui::DockBuilderSplitNode(leftID, ImGuiDir_Up, 0.5f, nullptr, &previewID);

	//split right into left and right nodes
	sceneID = ImGui::DockBuilderSplitNode(rightID, ImGuiDir_Left, 0.8, nullptr, &detailsID);

	// Dock windows to the resulting nodes
	ImGui::DockBuilderDockWindow("Properties", propertiesID);
	ImGui::DockBuilderDockWindow("Scene Preview", previewID);
	ImGui::DockBuilderDockWindow("Canvas", sceneID);
	ImGui::DockBuilderDockWindow("Details", detailsID);

	ImGui::DockBuilderFinish(mainID);
}

ImGui::DockSpace(dockspaceID, ImVec2(0, 0), dockFlags);

and I use

ImGui::SetNextWindowClass
and 
ImGui::SetNextWindowDockID

before every window,

like this:

ImGui::SetNextWindowClass(&windowClass);
ImGui::SetNextWindowDockID(sceneID);
ImGui::Begin("Scene Preview");
//...
ImGui::End();

//... etc 

works great!

There's also a problem with this approach, when I restart the app, the docked windows are undocked and I'm unable to dock them again. Quick fix:

get rid of:

if (!ImGui::DockBuilderGetNode(dockspaceID)) {
	ImGui::DockBuilderRemoveNode(dockspaceID);
	ImGui::DockBuilderAddNode(dockspaceID, ImGuiDockNodeFlags_None);

       //body remains the same
}

call

ImGui::DockSpace(dockspaceID, ImVec2(0, 0), dockFlags);

before

DockBuilderSplitNode

I've also slightly changed DockBuilderSplitNode

Instead of throwing asset IM_ASSERT(!node->IsSplitNode()); // Assert if already Split

I just return id_at_dir

 if (node->IsSplitNode()) {
        ImGuiID id_at_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 0 : 1]->ID;
        ImGuiID id_at_opposite_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0]->ID;
        if (out_id_at_dir)
            *out_id_at_dir = id_at_dir;
        if (out_id_at_opposite_dir)
            *out_id_at_opposite_dir = id_at_opposite_dir;
        return id_at_dir;
    }

Now it seems to be working fine. If I find anything more, I'll post it here. Maybe someone find it helpful for a quick solution

@SC5Shout SC5Shout reopened this Jun 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants