diff --git a/imgui.cpp b/imgui.cpp index 75469f7a2..5409d56d9 100644 --- a/imgui.cpp +++ b/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; } diff --git a/imgui_internal.h b/imgui_internal.h index 0477056f8..03d9011f7 100644 --- a/imgui_internal.h +++ b/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. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c1ae9208e..3a1415c59 100644 --- a/imgui_widgets.cpp +++ b/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;