Browse Source

Docking: Added undocking of whole dock node by dragging from the Collapse button. Super useful and works great!

omar 7 years ago
parent
commit
e647f89c33
3 changed files with 91 additions and 29 deletions
  1. 65 20
      imgui.cpp
  2. 5 2
      imgui_internal.h
  3. 21 7
      imgui_widgets.cpp

+ 65 - 20
imgui.cpp

@@ -5438,7 +5438,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
 
             // Collapse button
             if (!(flags & ImGuiWindowFlags_NoCollapse))
-                if (CollapseButton(window->GetID("#COLLAPSE"), window->Pos))
+                if (CollapseButton(window->GetID("#COLLAPSE"), window->Pos, NULL))
                     window->WantCollapseToggle = true; // Defer collapsing to next frame as we are too far in the Begin() function
 
             // Close button
@@ -9537,7 +9537,6 @@ void ImGui::EndDragDropTarget()
 // B- inconsistent clipping/border 1-pixel issue (#2)
 // B- fix/disable auto-resize grip on split host nodes (~#2)
 // B- SetNextWindowFocus() doesn't seem to apply if the window is hidden this frame, need repro (#4)
-// B- drag from collapse button should drag entire dock node
 // B- implicit, invisible per-viewport dockspace to dock to
 // B- resizing a dock tree small currently has glitches (overlapping collapse and close button, etc.)
 // B- tab bar: appearing on first frame with a dumb layout would do less harm that not appearing? (when behind dynamic branch) or store titles + render in EndTabBar()
@@ -9570,13 +9569,14 @@ struct ImGuiDockRequest
     ImGuiDir                DockSplitDir;
     float                   DockSplitRatio;
     bool                    DockSplitOuter;
-    ImGuiWindow*            UndockTarget;
+    ImGuiWindow*            UndockTargetWindow;
+    ImGuiDockNode*          UndockTargetNode;
 
     ImGuiDockRequest()
     {
         Type = ImGuiDockRequestType_None;
-        DockTargetWindow = DockPayload = UndockTarget = NULL;
-        DockTargetNode = NULL;
+        DockTargetWindow = DockPayload = UndockTargetWindow = NULL;
+        DockTargetNode = UndockTargetNode = NULL;
         DockSplitDir = ImGuiDir_None;
         DockSplitRatio = 0.5f;
         DockSplitOuter = false;
@@ -9636,7 +9636,8 @@ namespace ImGui
     static void             DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer);
     static void             DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node);
     static void             DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req);
-    static void             DockContextProcessUndock(ImGuiContext* ctx, ImGuiWindow* window);
+    static void             DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window);
+    static void             DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node);
     static void             DockContextGcUnusedSettingsNodes(ImGuiContext* ctx);
     static void             DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_persistent_docking_references);    // Set root_id==0 to clear all
     static void             DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count);
@@ -9653,13 +9654,13 @@ namespace ImGui
     static void             DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* node);
     static void             DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window);
     static void             DockNodeUpdateVisibleFlag(ImGuiDockNode* node);
+    static void             DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window);
     static bool             DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* payload_window);
     static bool             DockNodePreviewDockCalc(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockPreviewData* preview_data, bool is_explicit_target, bool is_outer_docking);
     static void             DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, const ImGuiDockPreviewData* preview_data);
     static ImRect           DockNodeCalcTabBarRect(const ImGuiDockNode* node);
     static void             DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired);
     static bool             DockNodeCalcDropRects(const ImRect& parent, ImGuiDir dir, ImRect& out_draw, bool outer_docking);
-    static ImGuiDockNode*   DockNodeGetRootNode(ImGuiDockNode* node) { while (node->ParentNode) node = node->ParentNode; return node; }
     static const char*      DockNodeGetHostWindowTitle(ImGuiDockNode* node, char* buf, int buf_size) { ImFormatString(buf, buf_size, "##DockNode_%02X", node->ID); return buf; }
     static int              DockNodeGetDepth(const ImGuiDockNode* node) { int depth = 0; while (node->ParentNode) { node = node->ParentNode; depth++; } return depth; }
     static int              DockNodeGetTabOrder(ImGuiWindow* window);
@@ -9766,8 +9767,13 @@ void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext* ctx)
 
     // Process Undocking requests (we need to process them _before_ the UpdateMouseMovingWindow call in NewFrame)
     for (int n = 0; n < dc->Requests.Size; n++)
-        if (dc->Requests[n].Type == ImGuiDockRequestType_Undock)
-            DockContextProcessUndock(ctx, dc->Requests[n].UndockTarget);
+    {
+        ImGuiDockRequest* req = &dc->Requests[n];
+        if (req->Type == ImGuiDockRequestType_Undock && req->UndockTargetWindow)
+            DockContextProcessUndockWindow(ctx, req->UndockTargetWindow);
+        else if (req->Type == ImGuiDockRequestType_Undock && req->UndockTargetNode)
+            DockContextProcessUndockNode(ctx, req->UndockTargetNode);
+    }
 }
 
 // Docking context update function, called by NewFrame()
@@ -9912,7 +9918,7 @@ void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiContext* ctx, ImGuiID root_i
         if (want_removal)
         {
             ImGuiID backup_dock_id = window->DockId;
-            DockContextProcessUndock(ctx, window);
+            DockContextProcessUndockWindow(ctx, window);
             if (!clear_persistent_docking_references)
                 window->DockId = backup_dock_id;
         }
@@ -10023,11 +10029,19 @@ void ImGui::DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDo
     ctx->DockContext->Requests.push_back(req);
 }
 
-void ImGui::DockContextQueueUndock(ImGuiContext* ctx, ImGuiWindow* window)
+void ImGui::DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window)
 {
     ImGuiDockRequest req;
     req.Type = ImGuiDockRequestType_Undock;
-    req.UndockTarget = window;
+    req.UndockTargetWindow = window;
+    ctx->DockContext->Requests.push_back(req);
+}
+
+void ImGui::DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
+{
+    ImGuiDockRequest req;
+    req.Type = ImGuiDockRequestType_Undock;
+    req.UndockTargetNode = node;
     ctx->DockContext->Requests.push_back(req);
 }
 
@@ -10118,7 +10132,7 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req)
         if (payload_node != NULL)
         {
             // Transfer full payload node (with 1+ child windows or child nodes)
-            // FIXME-DOCK: Transition persistent DockId for all non-active windows?
+            // FIXME-DOCK: Transition persistent DockId for all non-active windows
             if (payload_node->IsSplitNode())
             {
                 if (target_node->Windows.Size > 0)
@@ -10157,7 +10171,7 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req)
         target_node->TabBar->NextSelectedTabId = next_selected_id;
 }
 
-void ImGui::DockContextProcessUndock(ImGuiContext* ctx, ImGuiWindow* window)
+void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window)
 {
     (void)ctx;
     if (window->DockNode)
@@ -10169,6 +10183,22 @@ void ImGui::DockContextProcessUndock(ImGuiContext* ctx, ImGuiWindow* window)
     window->DockTabIsVisible = false;
 }
 
+// Extract a node out by creating a new one.
+// In the case of a root node, a node will have to stay in place, otherwise the node will be hidden (and GC-ed later)
+// (we could handle both cases differently with little benefit)
+void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
+{
+    // FIXME-DOCK: Transition persistent DockId for all non-active windows
+    (void)ctx;
+    IM_ASSERT(!node->IsSplitNode());
+    IM_ASSERT(node->Windows.Size >= 1);
+    ImGuiDockNode* new_node = DockContextAddNode(ctx, (ImGuiID)-1);
+    DockNodeMoveWindows(new_node, node);
+    for (int n = 0; n < new_node->Windows.Size; n++)
+        UpdateWindowParentAndRootLinks(new_node->Windows[n], new_node->Windows[n]->Flags, NULL);
+    new_node->WantMouseMove = true;
+}
+
 //-----------------------------------------------------------------------------
 // Docking: ImGuiDockNode
 //-----------------------------------------------------------------------------
@@ -10188,7 +10218,7 @@ ImGuiDockNode::ImGuiDockNode(ImGuiID id)
     SelectedTabID = 0;
     WantCloseTabID = 0;
     IsVisible = true;
-    InitFromFirstWindow = IsExplicitRoot = IsDocumentRoot = HasCloseButton = HasCollapseButton = WantCloseAll = WantLockSizeOnce = false;
+    InitFromFirstWindow = IsExplicitRoot = IsDocumentRoot = HasCloseButton = HasCollapseButton = WantCloseAll = WantLockSizeOnce = WantMouseMove = false;
 }
 
 ImGuiDockNode::~ImGuiDockNode()
@@ -10447,6 +10477,16 @@ static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode* node)
     node->IsVisible = is_visible;
 }
 
+static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(node->WantMouseMove == true);
+    ImVec2 backup_active_click_offset = g.ActiveIdClickOffset;
+    StartMouseMovingWindow(window);
+    node->WantMouseMove = false;
+    g.ActiveIdClickOffset = backup_active_click_offset;
+}
+
 static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
 {
     ImGuiContext& g = *GImGui;
@@ -10488,6 +10528,9 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
         node->WantCloseTabID = 0;
         node->HasCloseButton = node->HasCollapseButton = false;
         node->LastFrameActive = g.FrameCount;
+
+        if (node->WantMouseMove)
+            DockNodeStartMouseMovingWindow(node, node->Windows[0]);
         return;
     }
 
@@ -10553,6 +10596,8 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
             node->HostWindow = host_window = node->ParentNode->HostWindow;
         }
         node->InitFromFirstWindow = false;
+        if (node->WantMouseMove && node->HostWindow)
+            DockNodeStartMouseMovingWindow(node, node->HostWindow);
     }
 
     // Update active node (the one whose title bar is highlight) within a node tree
@@ -10681,7 +10726,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w
     host_window->DrawList->AddLine(title_bar_rect.GetBL(), title_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.WindowBorderSize);
 
     // Collapse button
-    if (CollapseButton(host_window->GetID("#COLLAPSE"), title_bar_rect.Min))
+    if (CollapseButton(host_window->GetID("#COLLAPSE"), title_bar_rect.Min, node))
         OpenPopup("#TabListMenu");
     if (IsItemActive())
         focus_tab_id = tab_bar->SelectedTabId;
@@ -11524,7 +11569,7 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open)
     g.NextWindowData.PosUndock = false;
     if (want_undock)
     {
-        DockContextProcessUndock(ctx, window);
+        DockContextProcessUndockWindow(ctx, window);
         return;
     }
 
@@ -11538,7 +11583,7 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open)
 
         if (dock_node->IsSplitNode())
         {
-            DockContextProcessUndock(ctx, window);
+            DockContextProcessUndockWindow(ctx, window);
             return;
         }
 
@@ -11560,7 +11605,7 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open)
         ImGuiDockNode* root_node = DockNodeGetRootNode(dock_node);
         if (root_node->LastFrameAlive < g.FrameCount)
         {
-            DockContextProcessUndock(ctx, window);
+            DockContextProcessUndockWindow(ctx, window);
         }
         else
         {
@@ -11573,7 +11618,7 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open)
     // Undock if we are submitted earlier than the host window
     if (dock_node->HostWindow && window->BeginOrderWithinContext < dock_node->HostWindow->BeginOrderWithinContext)
     {
-        DockContextProcessUndock(ctx, window);
+        DockContextProcessUndockWindow(ctx, window);
         return;
     }
 

+ 5 - 2
imgui_internal.h

@@ -769,6 +769,7 @@ struct ImGuiDockNode
     bool                    HasCollapseButton   :1;
     bool                    WantCloseAll        :1; // Set when closing all tabs at once.
     bool                    WantLockSizeOnce    :1;
+    bool                    WantMouseMove       :1; // After a node extraction we need to transition toward moving the newly created hode window
 
     ImGuiDockNode(ImGuiID id);
     ~ImGuiDockNode();
@@ -1468,7 +1469,9 @@ namespace ImGui
     IMGUI_API void          DockContextNewFrameUpdateUndocking(ImGuiContext* ctx);
     IMGUI_API void          DockContextNewFrameUpdateDocking(ImGuiContext* ctx);
     IMGUI_API void          DockContextEndFrame(ImGuiContext* ctx);
-    IMGUI_API void          DockContextQueueUndock(ImGuiContext* ctx, ImGuiWindow* window);
+    IMGUI_API void          DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window);
+    IMGUI_API void          DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node);
+    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);
     IMGUI_API void          BeginAsDockableDragDropTarget(ImGuiWindow* window);
@@ -1531,7 +1534,7 @@ namespace ImGui
     // Widgets
     IMGUI_API bool          ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0,0), ImGuiButtonFlags flags = 0);
     IMGUI_API bool          CloseButton(ImGuiID id, const ImVec2& pos, float radius);
-    IMGUI_API bool          CollapseButton(ImGuiID id, const ImVec2& pos);
+    IMGUI_API bool          CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node);
     IMGUI_API bool          ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags);
     IMGUI_API void          Scrollbar(ImGuiLayoutType direction);
     IMGUI_API void          VerticalSeparator();        // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout.

+ 21 - 7
imgui_widgets.cpp

@@ -669,7 +669,7 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
     return pressed;
 }
 
-bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
+bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node)
 {
     ImGuiContext& g = *GImGui;
     ImGuiWindow* window = g.CurrentWindow;
@@ -679,20 +679,34 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
     bool hovered, held;
     bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
 
-    bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed);
-    ImVec2 off = is_dock_menu ? ImVec2((float)(int)(-g.Style.ItemInnerSpacing.x * 0.5f) + 0.5f, 0.0f) : ImVec2(0.0f, 0.0f);
+    //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed);
+    ImVec2 off = dock_node ? ImVec2((float)(int)(-g.Style.ItemInnerSpacing.x * 0.5f) + 0.5f, 0.0f) : ImVec2(0.0f, 0.0f);
     ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
     if (hovered || held)
         window->DrawList->AddCircleFilled(bb.GetCenter() + off + ImVec2(0,-0.5f), g.FontSize * 0.5f + 1.0f, col, 9);
 
-    if (is_dock_menu)
+    if (dock_node)
         RenderArrowDockMenu(window->DrawList, bb.Min + g.Style.FramePadding, g.FontSize, GetColorU32(ImGuiCol_Text));
     else
         RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
 
     // Switch to moving the window after mouse is moved beyond the initial drag threshold
-    if (IsItemActive() && IsMouseDragging())
-        StartMouseMovingWindow(window);
+    if (IsItemActive() && IsMouseDragging(0))
+    {
+        if (dock_node != NULL && DockNodeGetRootNode(dock_node)->OnlyNodeWithWindows != dock_node)
+        {
+            float threshold_base = g.FontSize;
+            float threshold_x = (threshold_base * 2.2f);
+            float threshold_y = (threshold_base * 1.5f);
+            IM_ASSERT(window->DockNodeAsHost != NULL);
+            if (g.IO.MouseDragMaxDistanceAbs[0].x > threshold_x || g.IO.MouseDragMaxDistanceAbs[0].y > threshold_y)
+                DockContextQueueUndockNode(&g, dock_node);
+        }
+        else
+        {
+            StartMouseMovingWindow(window);
+        }
+    }
 
     return pressed;
 }
@@ -6456,7 +6470,7 @@ bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
             // Undock
             if (undocking_tab && g.ActiveId == id && IsMouseDragging())
             {
-                DockContextQueueUndock(&g, docked_window);
+                DockContextQueueUndockWindow(&g, docked_window);
                 g.MovingWindow = docked_window;
                 g.ActiveId = g.MovingWindow->MoveId;
                 g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;