From fa0ce4b7d57674a4b16501962e73b029d0ae3e48 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 24 Sep 2018 11:06:31 +0200 Subject: [PATCH] Docking: Some DockBuilder functions are applied on settings data if windows are not present. Added DockBuilderCreateNode which needs a size else if we can't split properly. DockNodeTreeSplit() doesn't clamp SizeRef. (+1 squashed commits) --- imgui.cpp | 82 +++++++++++++++++++++++++++++++++++++---------- imgui_internal.h | 11 ++++--- imgui_widgets.cpp | 4 +-- 3 files changed, 74 insertions(+), 23 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 897bcea9c..cabb8d33b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -9641,7 +9641,6 @@ struct ImGuiDockContext namespace ImGui { // ImGuiDockContext - static ImGuiDockNode* DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id); static ImGuiDockNode* DockContextAddNode(ImGuiContext* ctx, ImGuiID id); static void DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node); static void DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer); @@ -9815,7 +9814,7 @@ void ImGui::DockContextEndFrame(ImGuiContext* ctx) (void)ctx; } -static ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id) +ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id) { return (ImGuiDockNode*)ctx->DockContext->Nodes.GetVoidPtr(id); } @@ -9884,6 +9883,7 @@ void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiContext* ctx, ImGuiID root_id) return; bool has_document_root = false; + // Process active windows ImVector nodes_to_remove; for (int n = 0; n < dc->Nodes.Data.Size; n++) if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) @@ -9901,6 +9901,16 @@ void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiContext* ctx, ImGuiID root_id) } } + // Apply to settings + for (int settings_n = 0; settings_n < ctx->SettingsWindows.Size; settings_n++) + if (ImGuiID window_settings_dock_id = ctx->SettingsWindows[settings_n].DockId) + for (int n = 0; n < nodes_to_remove.Size; n++) + if (nodes_to_remove[n]->ID == window_settings_dock_id) + { + ctx->SettingsWindows[settings_n].DockId = root_id; + break; + } + // Not really efficient, but easier to destroy a whole hierarchy considering DockContextRemoveNode is attempting to merge nodes if (nodes_to_remove.Size > 1) ImQsort(nodes_to_remove.Data, nodes_to_remove.Size, sizeof(ImGuiDockNode*), DockNodeComparerDepthMostFirst); @@ -9920,8 +9930,24 @@ void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiContext* ctx, ImGuiID root_id) void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiContext* ctx, ImGuiID root_id, bool clear_persistent_docking_references) { - // Clear references in windows + // Clear references in settings ImGuiContext& g = *ctx; + if (clear_persistent_docking_references) + { + for (int n = 0; n < g.SettingsWindows.Size; n++) + { + ImGuiWindowSettings* settings = &g.SettingsWindows[n]; + bool want_removal = (root_id == 0) || (settings->DockId == root_id); + if (!want_removal && settings->DockId != 0) + if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, settings->DockId)) + if (DockNodeGetRootNode(node)->ID == root_id) + want_removal = true; + if (want_removal) + settings->DockId = 0; + } + } + + // Clear references in windows for (int n = 0; n < g.Windows.Size; n++) { ImGuiWindow* window = g.Windows[n]; @@ -9957,6 +9983,7 @@ static void ImGui::DockContextGcUnusedSettingsNodes(ImGuiContext* ctx) is_parent_map.SetInt(dc->SettingsNodes[settings_n].ParentID, 1); // If a root node has only 1 reference in window settings we clear it + // FIXME-DOCK: We should be able to merge unused nodes as well. for (int settings_n = 0; settings_n < dc->SettingsNodes.Size; settings_n++) { ImGuiDockNodeSettings* settings = &dc->SettingsNodes[settings_n]; @@ -10813,7 +10840,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w // Begin tab bar ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_NoTabListPopupButton;// | ImGuiTabBarFlags_NoTabListScrollingButtons); tab_bar_flags |= ImGuiTabBarFlags_SaveSettings; - tab_bar_flags |= ImGuiTabBarFlags_DockNode | (node->IsDockSpace ? ImGuiTabBarFlags_DockNodeExplicitRoot : 0); + tab_bar_flags |= ImGuiTabBarFlags_DockNode | (node->IsDockSpace ? ImGuiTabBarFlags_DockNodeIsDockSpace : 0); if (!host_window->Collapsed && is_focused) tab_bar_flags |= ImGuiTabBarFlags_IsFocused; BeginTabBarEx(node->TabBar, tab_bar_rect, tab_bar_flags, node); @@ -11203,7 +11230,6 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_inheritor_child_idx, float split_ratio, ImGuiDockNode* new_node) { - ImGuiContext& g = *ctx; IM_ASSERT(split_axis != ImGuiAxis_None); ImGuiDockNode* child_0 = (new_node && split_inheritor_child_idx != 0) ? new_node : DockContextAddNode(ctx, (ImGuiID)-1); @@ -11221,9 +11247,10 @@ void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG parent_node->VisibleWindow = NULL; float size_avail = (parent_node->Size[split_axis] - IMGUI_DOCK_SPLITTER_SIZE); + IM_ASSERT(size_avail > 0.0f); child_0->SizeRef = child_1->SizeRef = parent_node->Size; - child_0->SizeRef[split_axis] = ImMax(g.Style.WindowMinSize[split_axis], ImFloor(size_avail * split_ratio)); - child_1->SizeRef[split_axis] = ImMax(g.Style.WindowMinSize[split_axis], ImFloor(size_avail - child_0->SizeRef[split_axis])); + child_0->SizeRef[split_axis] = ImFloor(size_avail * split_ratio); + child_1->SizeRef[split_axis] = ImFloor(size_avail - child_0->SizeRef[split_axis]); DockNodeMoveWindows(parent_node->ChildNodes[split_inheritor_child_idx], parent_node); DockNodeTreeUpdatePosSize(parent_node, parent_node->Pos, parent_node->Size); @@ -11517,7 +11544,7 @@ void ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags doc // When a Dockspace transitioned form implicit to explicit this may be called a second time // It is possible that the node has already been claimed by a docked window which appeared before the DockSpace() node, so we overwrite IsDockSpace again. - if (node->LastFrameActive == g.FrameCount) + if (node->LastFrameActive == g.FrameCount && !(dock_space_flags & ImGuiDockNodeFlags_KeepAliveOnly)) { IM_ASSERT(node->IsDockSpace == false && "Cannot call DockSpace() twice a frame with the same ID"); node->IsDockSpace = true; @@ -11593,13 +11620,25 @@ void ImGui::DockBuilderDockWindow(ImGuiContext*, const char* window_name, ImGuiI } } +// Ensure a node is created +void ImGui::DockBuilderCreateNode(ImGuiContext* ctx, ImGuiID id, ImVec2 ref_size, ImGuiDockNodeFlags flags) +{ + DockSpace(id, ImVec2(0,0), flags | ImGuiDockNodeFlags_KeepAliveOnly); + ImGuiDockNode* node = DockContextFindNodeByID(ctx, id); + node->SizeRef = node->Size = ref_size; + node->LastFrameAlive = -1; +} + ImGuiID ImGui::DockBuilderSplitNode(ImGuiContext* ctx, ImGuiID id, ImGuiDir split_dir, float size_ratio_for_node_at_dir, ImGuiID* out_id_at_dir, ImGuiID* out_id_other) { IM_ASSERT(split_dir != ImGuiDir_None); ImGuiDockNode* node = DockContextFindNodeByID(ctx, id); if (node == NULL) + { + IM_ASSERT(node != NULL); return 0; + } IM_ASSERT(!node->IsSplitNode()); // Already Split @@ -11886,7 +11925,7 @@ static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettings if (sscanf(line, " SizeRef=%i,%i%n", &x, &y, &r) == 2) { line += r; node.SizeRef = ImVec2ih((short)x, (short)y); } } if (sscanf(line, " Split=%c%n", &c, &r) == 1) { line += r; if (c == 'X') node.SplitAxis = ImGuiAxis_X; else if (c == 'Y') node.SplitAxis = ImGuiAxis_Y; } - if (sscanf(line, " ExplicitRoot=%d%n", &x, &r) == 1) { line += r; node.IsDockSpace = (x != 0); } + if (sscanf(line, " DockSpace=%d%n", &x, &r) == 1) { line += r; node.IsDockSpace = (x != 0); } if (sscanf(line, " DocumentRoot=%d%n", &x, &r) == 1) { line += r; node.IsDocumentRoot = (x != 0); } if (sscanf(line, " SelectedTab=0x%08X%n", &node.SelectedTabID,&r) == 1) { line += r; } //if (node.ParentID == 0 && node.SplitAxis == ImGuiAxis_None) @@ -11939,6 +11978,7 @@ static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettings buf->appendf("[%s][Data]\n", handler->TypeName); for (int node_n = 0; node_n < dc->SettingsNodes.Size; node_n++) { + //const int line_start_pos = buf->size(); const ImGuiDockNodeSettings* node_settings = &dc->SettingsNodes[node_n]; buf->appendf("%*sDockNode%*s", node_settings->Depth * 2, "", (max_depth - node_settings->Depth) * 2, ""); // Text align nodes to facilitate looking at .ini file buf->appendf(" ID=0x%08X", node_settings->ID); @@ -11949,20 +11989,28 @@ static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettings if (node_settings->SplitAxis != ImGuiAxis_None) buf->appendf(" Split=%c", (node_settings->SplitAxis == ImGuiAxis_X) ? 'X' : 'Y'); if (node_settings->IsDockSpace) - buf->appendf(" ExplicitRoot=%d", node_settings->IsDockSpace); + buf->appendf(" DockSpace=%d", node_settings->IsDockSpace); if (node_settings->IsDocumentRoot) buf->appendf(" DocumentRoot=%d", node_settings->IsDocumentRoot); if (node_settings->SelectedTabID) buf->appendf(" SelectedTab=0x%08X", node_settings->SelectedTabID); -#if 0 // [DEBUG] Include windows names in the .ini file +#if 0 // [DEBUG] Include comments in the .ini file to ease debugging if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_settings->ID)) - if (node->Windows.Size > 0) - { - buf->appendf("%*s; recently: %d %s (", 15, "", node->Windows.Size, node->Windows.Size == 1 ? "window " : "windows"); - for (int window_n = 0; window_n < node->Windows.Size; window_n++) - buf->appendf("\"%s\"%s", node->Windows[window_n]->Name, (window_n + 1 < node->Windows.Size) ? ", " : ")"); - } + { + buf->appendf("%*s", ImMax(2, (line_start_pos + 90) - buf->size()), ""); // Align everything + if (node->IsDockSpace && node->HostWindow && node->HostWindow->ParentWindow) + buf->appendf(" ; in '%s'", node->HostWindow->ParentWindow->Name); + + int contains_window = 0; + for (int window_n = 0; window_n < ctx->SettingsWindows.Size; window_n++) + if (ctx->SettingsWindows[window_n].DockId == node_settings->ID) + { + if (contains_window++ == 0) + buf->appendf(" ; contains "); + buf->appendf("'%s' ", ctx->SettingsWindows[window_n].Name); + } + } #endif buf->appendf("\n"); } diff --git a/imgui_internal.h b/imgui_internal.h index 1c2b04455..25eddf29b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1296,7 +1296,7 @@ struct ImGuiItemHoveredDataBackup enum ImGuiTabBarFlagsPrivate_ { ImGuiTabBarFlags_DockNode = 1 << 20, // Part of a dock node - ImGuiTabBarFlags_DockNodeExplicitRoot = 1 << 21, // Part of an explicit dock node + ImGuiTabBarFlags_DockNodeIsDockSpace = 1 << 21, // Part of an explicit dockspace node node ImGuiTabBarFlags_IsFocused = 1 << 22, ImGuiTabBarFlags_SaveSettings = 1 << 23 // FIXME: Settings are handled by the docking system, this only request the tab bar to mark settings dirty when reordering tabs }; @@ -1472,6 +1472,7 @@ namespace ImGui IMGUI_API void DockContextEndFrame(ImGuiContext* ctx); IMGUI_API void DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window); IMGUI_API void DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node); + IMGUI_API ImGuiDockNode*DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id); inline ImGuiDockNode* DockNodeGetRootNode(ImGuiDockNode* node) { while (node->ParentNode) node = node->ParentNode; return node; } IMGUI_API void BeginDocked(ImGuiWindow* window, bool* p_open); IMGUI_API void BeginAsDockableDragDropSource(ImGuiWindow* window); @@ -1479,11 +1480,13 @@ namespace ImGui IMGUI_API void SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond); IMGUI_API void ShowDockingDebug(); - IMGUI_API void DockBuilderRemoveNodeDockedWindows(ImGuiContext* ctx, ImGuiID root_id, bool clear_persistent_docking_references = true); - IMGUI_API void DockBuilderRemoveNodeChildNodes(ImGuiContext* ctx, ImGuiID root_id); + // Docking - Builder function needs to be generally called before the DockSpace() node is submitted. + IMGUI_API void DockBuilderRemoveNodeDockedWindows(ImGuiContext* ctx, ImGuiID root_node_id, bool clear_persistent_docking_references = true); + IMGUI_API void DockBuilderRemoveNodeChildNodes(ImGuiContext* ctx, ImGuiID root_node_id); // Remove all split/hierarchy. All remaining docked windows will be re-docked to the root. IMGUI_API void DockBuilderDockWindow(ImGuiContext* ctx, const char* window_name, ImGuiID node_id); + IMGUI_API void DockBuilderCreateNode(ImGuiContext* ctx, ImGuiID id, ImVec2 ref_size, ImGuiDockNodeFlags flags = 0); IMGUI_API ImGuiID DockBuilderSplitNode(ImGuiContext* ctx, ImGuiID id, ImGuiDir split_dir, float size_ratio_for_node_at_dir, ImGuiID* out_id_dir, ImGuiID* out_id_other); - IMGUI_API void DockBuilderFinish(ImGuiContext* ctx, ImGuiID root_id); + IMGUI_API void DockBuilderFinish(ImGuiContext* ctx, ImGuiID root_node_id); // Drag and Drop IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index aa6305918..299b0d644 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5871,8 +5871,8 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG } else { - const float separator_min_x = tab_bar->BarRect.Min.x - ((flags & ImGuiTabBarFlags_DockNodeExplicitRoot) ? 0.0f : window->WindowPadding.x); - const float separator_max_x = tab_bar->BarRect.Max.x + ((flags & ImGuiTabBarFlags_DockNodeExplicitRoot) ? 0.0f : window->WindowPadding.x); + const float separator_min_x = tab_bar->BarRect.Min.x - ((flags & ImGuiTabBarFlags_DockNodeIsDockSpace) ? 0.0f : window->WindowPadding.x); + const float separator_max_x = tab_bar->BarRect.Max.x + ((flags & ImGuiTabBarFlags_DockNodeIsDockSpace) ? 0.0f : window->WindowPadding.x); window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f); } return true;