玩了好长时间 imgui,最近才知道 imgui 的 docking 版本,直接讲如何实现自定义窗口布局吧。

docking 版本的布局控制接口在 imgui_internal.h 里面,一直没发现,偶然搜索到了几个关键字,查阅了 github 的几个库的源码,大概了解到了如何使用。

首先要包含头文件:

#include "imgui-docking/imgui_internal.h"

停靠窗口布局设置代码:

复制代码
  1 // 窗口的 ID 和 标题
  2 ImGuiID dockspaceID         = ImGui::GetID("##ui.dock_space");
  3 const char* UI_DOCK_WINDOW  = "##ui.dock_window";
  4 const char* UI_PROJECT_BOX  = u8"工程##ui.project";
  5 const char* UI_PROPERTY_BOX = u8"属性##ui.property";
  6 const char* UI_TOOL_BOX     = u8"工具##ui.tools";
  7 const char* UI_MESSAGE_BOX  = u8"消息##ui.message";
  8 const char* UI_LOG_BOX      = u8"日志##ui.log";
  9 const char* UI_VIEW_BOX     = u8"##ui.view";
 10 
 11 const ImGuiViewport* viewport = ImGui::GetMainViewport();
 12 ImGui::SetNextWindowPos(viewport->WorkPos);
 13 ImGui::SetNextWindowSize(viewport->WorkSize);
 14 ImGui::SetNextWindowViewport(viewport->ID);
 15 
 16 int windowFlags = ImGuiWindowFlags_NoDecoration            // 无标题栏、不可改变大小、无滚动条、不可折叠
 17                   | ImGuiWindowFlags_NoMove                // 不可移动
 18                   | ImGuiWindowFlags_NoBackground          // 无背景(背景透明)
 19                   | ImGuiWindowFlags_MenuBar               // 菜单栏
 20                   | ImGuiWindowFlags_NoDocking             // 不可停靠
 21                   | ImGuiWindowFlags_NoBringToFrontOnFocus // 无法设置前台和聚焦
 22                   | ImGuiWindowFlags_NoNavFocus            // 无法通过键盘和手柄聚焦
 23     ;
 24 
 25 // 压入样式设置
 26 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);            // 无边框
 27 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); // 无边界
 28 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);              // 无圆角
 29 // ImGui::SetNextWindowBgAlpha(0.0f); // 窗口 alpha 为 0,同样可以不显示背景
 30 
 31 ImGui::Begin(UI_DOCK_WINDOW, 0, windowFlags); // 开始停靠窗口
 32 ImGui::PopStyleVar(3);                        // 弹出样式设置
 33 
 34 // 创建停靠空间
 35 if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable) { // 判断是否开启停靠
 36     // 判断是否有根节点,防止一直重建
 37     if (!ImGui::DockBuilderGetNode(dockspaceID)) {
 38         // 移除根节点
 39         ImGui::DockBuilderRemoveNode(dockspaceID);
 40 
 41         // 创建根节点
 42         ImGuiID root = ImGui::DockBuilderAddNode(dockspaceID, ImGuiDockNodeFlags_None);
 43 
 44         // 设置根节点位置大小
 45         ImGui::DockBuilderSetNodePos(root, { 0.0f, 0.0f });
 46         ImGui::DockBuilderSetNodeSize(root, ImGui::GetWindowSize());
 47 
 48         // 分割停靠空间
 49 
 50         // 根节点分割左节点
 51         ImGuiID leftNode = ImGui::DockBuilderSplitNode(root, ImGuiDir_Left, 0.25f, nullptr, &root);
 52 
 53         // 根节点分割右节点
 54         ImGuiID rightNode = ImGui::DockBuilderSplitNode(root, ImGuiDir_Right, 0.25f / 0.75f, nullptr, &root);
 55 
 56         // 根节点分割下节点
 57         ImGuiID bottomNode = ImGui::DockBuilderSplitNode(root, ImGuiDir_Down, 0.25f, nullptr, &root);
 58 
 59         // 左节点分割上下节点
 60         ImGuiID leftTopNode, leftBottomNode;
 61         ImGui::DockBuilderSplitNode(leftNode, ImGuiDir_Up, 0.5f, &leftTopNode, &leftBottomNode);
 62 
 63         // 设置节点属性
 64 
 65         // 禁止其他窗口/节点分割根节点
 66         // ImGui::DockBuilderGetNode(dockspaceID)->LocalFlags |= ImGuiDockNodeFlags_NoDockingSplit;
 67 
 68         // 设置分割到最后的根节点隐藏标签栏
 69         ImGui::DockBuilderGetNode(root)->LocalFlags |= ImGuiDockNodeFlags_HiddenTabBar;
 70 
 71         // 设置节点停靠窗口
 72         ImGui::DockBuilderDockWindow(UI_PROJECT_BOX, leftTopNode);     // 左上节点
 73         ImGui::DockBuilderDockWindow(UI_PROPERTY_BOX, leftBottomNode); // 左下节点
 74         ImGui::DockBuilderDockWindow(UI_TOOL_BOX, rightNode);          // 右边节点
 75 
 76         ImGui::DockBuilderDockWindow(UI_MESSAGE_BOX, bottomNode);      // 下面节点同时停靠两个窗口
 77         ImGui::DockBuilderDockWindow(UI_LOG_BOX, bottomNode);
 78 
 79         ImGui::DockBuilderDockWindow(UI_VIEW_BOX, root); // 观察窗口平铺“客户区”,停靠的节点是 CentralNode
 80 
 81         // 结束停靠设置
 82         ImGui::DockBuilderFinish(dockspaceID);
 83 
 84         // 设置焦点窗口
 85         ImGui::SetWindowFocus(UI_VIEW_BOX);
 86     }
 87 
 88     // 创建停靠空间
 89     ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
 90     ImGui::PushStyleColor(ImGuiCol_DockingEmptyBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
 91     ImGui::DockSpace(dockspaceID, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None);
 92     ImGui::PopStyleVar();
 93     ImGui::PopStyleColor();
 94 }
 95 ImGui::End(); // 结束停靠窗口
 96 
 97 // 工程框
 98 if (ImGui::Begin(UI_PROJECT_BOX)) {
 99     ImGui::LabelText("label", "text");
100     ImGui::Button("button");
101 }
102 ImGui::End();
103 
104 // 属性框
105 if (ImGui::Begin(UI_PROPERTY_BOX)) {
106     ImGui::LabelText("label", "text");
107     ImGui::Button("button");
108 }
109 ImGui::End();
110 
111 // 工具框
112 if (ImGui::Begin(UI_TOOL_BOX)) {
113     ImGui::LabelText("label", "text");
114     ImGui::Button("button");
115 
116     if (ImGui::Button(u8"重置布局")) {
117         // 移除根节点,布局会自动重建
118         ImGui::DockBuilderRemoveNode(dockspaceID);
119     }
120 }
121 ImGui::End();
122 
123 // 消息框
124 if (ImGui::Begin(UI_MESSAGE_BOX)) {
125     ImGui::LabelText("label", "text");
126     ImGui::Button("button");
127 }
128 ImGui::End();
129 
130 // 日志框
131 if (ImGui::Begin(UI_LOG_BOX)) {
132     ImGui::LabelText("label", "text");
133     ImGui::Button("button");
134 }
135 ImGui::End();
136 
137 // 观察窗口,背景设置透明,窗口后面就能进行本地 API 的绘制
138 // 压入样式设置
139 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);            // 无边框
140 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); // 无边界
141 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);              // 无圆角
142 // ImGui::SetNextWindowBgAlpha(0.0f); // 窗口 alpha 为 0,同样可以不显示背景
143 
144 if (ImGui::Begin(UI_VIEW_BOX, 0, ImGuiWindowFlags_NoBackground)) { // 无背景窗口
145     // 获取窗口坐标
146     ImVec2 pos  = ImGui::GetWindowPos();
147     ImVec2 size = ImGui::GetWindowSize();
148 
149     ImGui::Text("position: %0.2f, %0.2f", pos.x, pos.y);
150     ImGui::Text("size: %0.2f, %0.2f", size.x, size.y);
151 
152     // 记录下视口位置给本地 API 使用
153     g_viewportPos  = glm::ivec2(pos.x, size.y);
154     g_viewportSize = glm::ivec2(size.x, size.y);
155 }
156 ImGui::End();
157 ImGui::PopStyleVar(3); // 弹出样式设置
复制代码

 

关键的停靠节点分割函数比较绕,比如:

// 左节点分割上下节点
ImGuiID leftTopNode, leftBottomNode;
ImGui::DockBuilderSplitNode(leftNode, ImGuiDir_Down, 0.5f, &leftTopNode, &leftBottomNode);

分割左节点,最后两个参数是输出的两个子节点。而

ImGuiID leftNode = ImGui::DockBuilderSplitNode(root, ImGuiDir_Left, 0.25f, nullptr, &root);

则是分割 root 节点,把分割的第一个节点返回给 leftNode,第二个子节点重新赋值给 root,root 实际上是新的子节点了,可以继续再分割。每个节点只能分割一次,分割成两个节点,分割的方向比如:ImGuiDir_Left、ImGuiDir_Up 等。

最终窗口显示效果: