Kaynağa Gözat

Docking: Misc rework/rename toward being able to rebuild a branch selectively, so we can honor settings changes on a per Dockspace basis. + Comments

omar 7 yıl önce
ebeveyn
işleme
ba7b68798d
1 değiştirilmiş dosya ile 88 ekleme ve 52 silme
  1. 88 52
      imgui.cpp

+ 88 - 52
imgui.cpp

@@ -3350,7 +3350,7 @@ void ImGui::NewFrame()
     g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame))) : FLT_MAX;
 
     // Undocking
-    // (needs to be before UpdateMovingWindow so the window is already offset and following the mouse on the detaching frame)
+    // (needs to be before UpdateMouseMovingWindow so the window is already offset and following the mouse on the detaching frame)
     DockContextNewFrameUpdateUndocking(g.DockContext);
 
     // Find hovered window
@@ -9614,9 +9614,8 @@ struct ImGuiDockContext
     ImGuiStorage                    Nodes;                      // Map ID -> ImGuiDockNode*: Active nodes
     ImVector<ImGuiDockRequest>      Requests;
     ImVector<ImGuiDockNodeSettings> SettingsNodes;
-    int                             SettingsMaxDepth;
     bool                            WantFullRebuild;
-    ImGuiDockContext()              { SettingsMaxDepth = 0; WantFullRebuild = false; }
+    ImGuiDockContext()              { WantFullRebuild = false; }
 };
 
 //-----------------------------------------------------------------------------
@@ -9632,10 +9631,10 @@ namespace ImGui
     static void             DockContextQueueDock(ImGuiDockContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer);
     static void             DockContextProcessDock(ImGuiDockContext* ctx, ImGuiDockRequest* req);
     static void             DockContextProcessUndock(ImGuiDockContext* ctx, ImGuiWindow* window);
-    static void             DockContextClearNodes(ImGuiDockContext* ctx, bool clear_references);
-    static void             DockContextGcUnusedNodes(ImGuiDockContext* ctx);
-    static void             DockContextBuildNodesFromSettings(ImGuiDockContext* ctx);
-    static void             DockContextBuildAddWindowsToNodes(ImGuiDockContext* ctx);
+    static void             DockContextGcUnusedSettingsNodes(ImGuiDockContext* ctx);
+    static void             DockContextClearNodes(ImGuiDockContext* ctx, ImGuiID root_id, bool clear_persistent_docking_references);    // Set root_id==0 to clear all
+    static void             DockContextBuildNodesFromSettings(ImGuiDockContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count);
+    static void             DockContextBuildAddWindowsToNodes(ImGuiDockContext* ctx, ImGuiID root_id);                                  // Use root_id==0 to add all
 
     // ImGuiDockNode
     static void             DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window);
@@ -9666,7 +9665,7 @@ namespace ImGui
     static ImGuiDockNode*   DockNodeTreeFindNodeByPos(ImGuiDockNode* node, ImVec2 pos);
 
     // Settings
-    static void             DockSettingsRemoveAllReferencesToNode(ImGuiID node_id);
+    static void             DockSettingsRemoveReferencesToNodes(ImGuiID* node_ids, int node_ids_count);
     static ImGuiDockNodeSettings*   DockSettingsFindNodeSettings(ImGuiDockContext* ctx, ImGuiID node_id);
     static void*            DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
     static void             DockSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
@@ -9676,6 +9675,13 @@ namespace ImGui
 //-----------------------------------------------------------------------------
 // Docking: ImGuiDockContext
 //-----------------------------------------------------------------------------
+// The lifetime model is different from the one of regular windows: we always create a ImGuiDockNode for each ImGuiDockNodeSettings,
+// or we always hold the entire docking node tree. Nodes are frequently hidden, e.g. if the window(s) or child nodes they host are not active.
+// At boot time only, we run a simple GC to remove nodes that have no references.
+// Because dock node settings (which are small, contiguous structures) are always mirrored by their corresponding dock nodes (more complete structures),
+// we can also very easily recreate the nodes from scratch given the settings data (this is what  DockContextRebuild() does).
+// This is convenient as docking reconfiguration can be implemented by mostly poking at the simpler setttings data.
+//-----------------------------------------------------------------------------
 
 void ImGui::DockContextInitialize(ImGuiContext* imgui_context)
 {
@@ -9699,10 +9705,7 @@ void ImGui::DockContextShutdown(ImGuiContext* imgui_context)
     ImGuiDockContext* ctx = g.DockContext;
     for (int n = 0; n < ctx->Nodes.Data.Size; n++)
         if (ImGuiDockNode* node = (ImGuiDockNode*)ctx->Nodes.Data[n].val_p)
-        {
-            node->ChildNodes[0] = node->ChildNodes[1] = NULL;
             IM_DELETE(node);
-        }
     IM_DELETE(g.DockContext);
     g.DockContext = NULL;
 }
@@ -9711,25 +9714,29 @@ void ImGui::DockContextOnLoadSettings()
 {
     ImGuiContext& g = *GImGui;
     ImGuiDockContext* ctx = g.DockContext;
-    DockContextGcUnusedNodes(ctx);
-    DockContextBuildNodesFromSettings(ctx);
+    DockContextGcUnusedSettingsNodes(ctx);
+    DockContextBuildNodesFromSettings(ctx, ctx->SettingsNodes.Data, ctx->SettingsNodes.Size);
 }
 
+// This function also acts as a defacto test to make sure we can rebuild from scratch without a glitch
 void ImGui::DockContextRebuild(ImGuiDockContext* ctx)
 {
+    //IMGUI_DEBUG_LOG("[docking] full rebuild\n");
     SaveIniSettingsToMemory();
-    DockContextClearNodes(ctx, false);
-    DockContextBuildNodesFromSettings(ctx);
-    DockContextBuildAddWindowsToNodes(ctx);
+    ImGuiID root_id = 0; // Rebuild all
+    DockContextClearNodes(ctx, root_id, false);
+    DockContextBuildNodesFromSettings(ctx, ctx->SettingsNodes.Data, ctx->SettingsNodes.Size);
+    DockContextBuildAddWindowsToNodes(ctx, root_id);
 }
 
+// Docking context update function, called by NewFrame()
 void ImGui::DockContextNewFrameUpdateUndocking(ImGuiDockContext* ctx)
 {
     ImGuiContext& g = *GImGui;
     if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
     {
         if (ctx->Nodes.Data.Size > 0 || ctx->Requests.Size > 0)
-            DockContextClearNodes(ctx, true);
+            DockContextClearNodes(ctx, 0, true);
         return;
     }
 
@@ -9743,12 +9750,13 @@ void ImGui::DockContextNewFrameUpdateUndocking(ImGuiDockContext* ctx)
         ctx->WantFullRebuild = false;
     }
 
-    // Process Undocking requests (called from NewFrame before UpdateMovingWindow)
+    // Process Undocking requests (we need to process them _before_ the UpdateMouseMovingWindow call in NewFrame)
     for (int n = 0; n < ctx->Requests.Size; n++)
         if (ctx->Requests[n].Type == ImGuiDockRequestType_Undock)
             DockContextProcessUndock(ctx, ctx->Requests[n].UndockTarget);
 }
 
+// Docking context update function, called by NewFrame()
 void ImGui::DockContextNewFrameUpdateDocking(ImGuiDockContext* ctx)
 {
     ImGuiContext& g = *GImGui;
@@ -9769,6 +9777,7 @@ void ImGui::DockContextNewFrameUpdateDocking(ImGuiDockContext* ctx)
                 DockNodeUpdate(node);
 }
 
+// Docking context update function, called by EndFrame()
 void ImGui::DockContextEndFrame(ImGuiDockContext* ctx)
 {
     (void)ctx;
@@ -9784,7 +9793,7 @@ static ImGuiDockNode* ImGui::DockContextAddNode(ImGuiDockContext* ctx, ImGuiID i
     // Generate an ID for the new node (the exact ID value doesn't matter as long as it is not already used) and add the first window.
     if (id == (ImGuiID)-1)
     {
-        // FIXME-OPT: This is very suboptimal, however the node count is small enough not to be a worry.
+        // FIXME-OPT: This is suboptimal, even if the node count is small enough not to be a worry. We could poke in ctx->Nodes to find a suitable ID faster.
         id = 0x0001;
         while (DockContextFindNodeByID(ctx, id) != NULL)
             id++;
@@ -9819,30 +9828,46 @@ static void ImGui::DockContextRemoveNode(ImGuiDockContext* ctx, ImGuiDockNode* n
     }
 }
 
-// Stress/functional test to make sure we can rebuild from scratch without a glitch
-void ImGui::DockContextClearNodes(ImGuiDockContext* ctx, bool clear_references)
+void ImGui::DockContextClearNodes(ImGuiDockContext* ctx, ImGuiID root_id, bool clear_persistent_docking_references)
 {
     SaveIniSettingsToMemory(NULL);
 
     ImGuiContext& g = *GImGui;
-    for (int n = 0; n < ctx->Nodes.Data.Size; n++)
-        if (ImGuiDockNode* node = (ImGuiDockNode*)ctx->Nodes.Data[n].val_p)
-            IM_DELETE(node);
-    ctx->Nodes.Clear();
-    ctx->Requests.clear();
+
+    // Clear references in windows
     for (int n = 0; n < g.Windows.Size; n++)
     {
         ImGuiWindow* window = g.Windows[n];
-        window->DockNode = window->DockNodeAsHost = NULL;
-        window->DockIsActive = false;
-        if (clear_references)
-            window->DockId = 0;
+        bool want_removal = root_id == 0 || (window->DockNode && DockNodeGetRootNode(window->DockNode)->ID == root_id) || (window->DockNodeAsHost && window->DockNodeAsHost->ID == root_id);
+        if (want_removal)
+        {
+            window->DockNode = window->DockNodeAsHost = NULL;
+            window->DockIsActive = false;
+            if (clear_persistent_docking_references)
+                window->DockId = 0;
+        }
     }
+
+    // Clear nodes
+    for (int n = 0; n < ctx->Nodes.Data.Size; n++)
+        if (ImGuiDockNode* node = (ImGuiDockNode*)ctx->Nodes.Data[n].val_p)
+        {
+            bool want_removal = (root_id == 0 || DockNodeGetRootNode(node)->ID == root_id);
+            if (want_removal)
+            {
+                IM_DELETE(node);
+                ctx->Nodes.Data[n].val_p = NULL;
+            }
+        }
+    if (root_id == 0)
+        ctx->Nodes.Clear();
+    ctx->Requests.clear();
 }
 
-static void ImGui::DockContextGcUnusedNodes(ImGuiDockContext* ctx)
+static void ImGui::DockContextGcUnusedSettingsNodes(ImGuiDockContext* ctx)
 {
     ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.Windows.Size == 0);
 
     // Count reference to dock ids from window settings
     ImGuiStorage ref_count_map;  // Map dock_id -> counter
@@ -9870,19 +9895,19 @@ static void ImGui::DockContextGcUnusedNodes(ImGuiDockContext* ctx)
             remove |= (ref_count == 0 && settings->ParentID == 0 && is_parent_map.GetInt(settings->ID, 0) == 0); // Leaf nodes with 0 window
             if (remove)
             {
-                DockSettingsRemoveAllReferencesToNode(settings->ID);
+                DockSettingsRemoveReferencesToNodes(&settings->ID, 1);
                 settings->ID = 0;
             }
         }
     }
 }
 
-static void ImGui::DockContextBuildNodesFromSettings(ImGuiDockContext* ctx)
+static void ImGui::DockContextBuildNodesFromSettings(ImGuiDockContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count)
 {
     // Build nodes
-    for (int node_n = 0; node_n < ctx->SettingsNodes.Size; node_n++)
+    for (int node_n = 0; node_n < node_settings_count; node_n++)
     {
-        ImGuiDockNodeSettings* node_settings = &ctx->SettingsNodes[node_n];
+        ImGuiDockNodeSettings* node_settings = &node_settings_array[node_n];
         if (node_settings->ID == 0)
             continue;
         ImGuiDockNode* node = DockContextAddNode(ctx, node_settings->ID);
@@ -9902,20 +9927,22 @@ static void ImGui::DockContextBuildNodesFromSettings(ImGuiDockContext* ctx)
     }
 }
 
-void ImGui::DockContextBuildAddWindowsToNodes(ImGuiDockContext* ctx)
+void ImGui::DockContextBuildAddWindowsToNodes(ImGuiDockContext* ctx, ImGuiID root_id)
 {
-    // Rebuild nodes (they can also lazily rebuild but we'll have a visible glitch during the first frame)
+    // Rebind all windows to nodes (they can also lazily rebind but we'll have a visible glitch during the first frame)
     ImGuiContext& g = *GImGui;
     for (int n = 0; n < g.Windows.Size; n++)
     {
         ImGuiWindow* window = g.Windows[n];
         if (window->DockId == 0 || window->LastFrameActive < g.FrameCount - 1)
             continue;
+        if (window->DockNode != NULL)
+            continue;
 
         ImGuiDockNode* dock_node = DockContextFindNodeByID(ctx, window->DockId);
-        if (dock_node == NULL)
-            dock_node = DockContextAddNode(ctx, window->DockId);
-        DockNodeAddWindow(dock_node, window);
+        IM_ASSERT(dock_node != NULL);   // This should have been called after DockContextBuildNodesFromSettings()
+        if (root_id == 0 || DockNodeGetRootNode(dock_node)->ID == root_id)
+            DockNodeAddWindow(dock_node, window);
     }
 }
 
@@ -10089,9 +10116,9 @@ ImGuiDockNode::ImGuiDockNode(ImGuiID id)
 
 ImGuiDockNode::~ImGuiDockNode()
 {
-    IM_ASSERT(ChildNodes[0] == NULL && ChildNodes[1] == NULL);
     IM_DELETE(TabBar);
     TabBar = NULL;
+    ChildNodes[0] = ChildNodes[1] = NULL;
 }
 
 int ImGui::DockNodeGetTabOrder(ImGuiWindow* window)
@@ -11546,18 +11573,23 @@ void ImGui::BeginAsDockableDragDropTarget(ImGuiWindow* window)
 // Docking: Settings
 //-----------------------------------------------------------------------------
 
-static void ImGui::DockSettingsRemoveAllReferencesToNode(ImGuiID node_id)
+// Remove references stored in ImGuiWindowSettings to the given ImGuiDockNodeSettings
+static void ImGui::DockSettingsRemoveReferencesToNodes(ImGuiID* node_ids, int node_ids_count)
 {
     ImGuiContext& g = *GImGui;
+    int found = 0;
     for (int settings_n = 0; settings_n < g.SettingsWindows.Size; settings_n++)     // FIXME-OPT: We could remove this loop by storing the index in the map
     {
         ImGuiWindowSettings* window_settings = &g.SettingsWindows[settings_n];
-        if (window_settings->DockId == node_id)
-        {
-            window_settings->DockId = 0;
-            window_settings->DockOrder = -1;
-            break;
-        }
+        for (int node_n = 0; node_n < node_ids_count; node_n++)
+            if (window_settings->DockId == node_ids[node_n])
+            {
+                window_settings->DockId = 0;
+                window_settings->DockOrder = -1;
+                if (++found < node_ids_count)
+                    break;
+                return;
+            }
     }
 }
 
@@ -11617,6 +11649,7 @@ static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* imgui_ctx, ImGuiSe
 static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* ctx, ImGuiDockNode* node, int depth)
 {
     ImGuiDockNodeSettings node_settings;
+    IM_ASSERT(depth < (1 << (sizeof(node_settings.Depth) << 3)));
     node_settings.ID = node->ID;
     node_settings.ParentID = node->ParentNode ? node->ParentNode->ID : 0;
     node_settings.SelectedTabID = node->SelectedTabID;
@@ -11629,7 +11662,6 @@ static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* ctx, ImGuiD
     node_settings.Size = ImVec2ih((short)node->Size.x, (short)node->Size.y);
     node_settings.LastExplicitSize = ImVec2ih((short)node->LastExplicitSize.x, (short)node->LastExplicitSize.y);
     ctx->SettingsNodes.push_back(node_settings);
-    ctx->SettingsMaxDepth = ImMax(ctx->SettingsMaxDepth, depth);
     if (node->ChildNodes[0])
         DockSettingsHandler_DockNodeToSettings(ctx, node->ChildNodes[0], depth + 1);
     if (node->ChildNodes[1])
@@ -11639,21 +11671,25 @@ static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* ctx, ImGuiD
 static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
 {
     // Gather settings data
+    // (unlike our windows settings, because nodes are always built we can do a full rewrite of the SettingsNode buffer)
     ImGuiDockContext* ctx = imgui_ctx->DockContext;
     ctx->SettingsNodes.resize(0);
     ctx->SettingsNodes.reserve(ctx->Nodes.Data.Size);
-    ctx->SettingsMaxDepth = 0;
     for (int n = 0; n < ctx->Nodes.Data.Size; n++)
         if (ImGuiDockNode* node = (ImGuiDockNode*)ctx->Nodes.Data[n].val_p)
             if (node->IsRootNode())
                 DockSettingsHandler_DockNodeToSettings(ctx, node, 0);
 
+    int max_depth = 0;
+    for (int node_n = 0; node_n < ctx->SettingsNodes.Size; node_n++)
+        max_depth = ImMax((int)ctx->SettingsNodes[node_n].Depth, max_depth);
+
     // Write to text buffer
     buf->appendf("[%s][Data]\n", handler->TypeName);
     for (int node_n = 0; node_n < ctx->SettingsNodes.Size; node_n++)
     {
         const ImGuiDockNodeSettings* node_settings = &ctx->SettingsNodes[node_n];
-        buf->appendf("%*sDockNode%*s", node_settings->Depth * 2, "", (ImMax(ctx->SettingsMaxDepth,0) - node_settings->Depth) * 2, "");  // Text align nodes to facilitate looking at .ini file
+        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);
         if (node_settings->ParentID)
             buf->appendf(" Parent=0x%08X LastExplicitSize=%d,%d", node_settings->ParentID, node_settings->LastExplicitSize.x, node_settings->LastExplicitSize.y);
@@ -12561,7 +12597,7 @@ void ImGui::ShowDockingDebug()
 
     if (ImGui::TreeNode("Dock nodes"))
     {
-        if (ImGui::SmallButton("Clear settings"))   { DockContextClearNodes(ctx, true); }
+        if (ImGui::SmallButton("Clear settings"))   { DockContextClearNodes(ctx, 0, true); }
         ImGui::SameLine();
         if (ImGui::SmallButton("Rebuild all"))      { ctx->WantFullRebuild = true; }
         for (int n = 0; n < ctx->Nodes.Data.Size; n++)