Skip to content

Commit

Permalink
Docking: docked windows honor ImGuiCol_WindowBg. Host window in charg…
Browse files Browse the repository at this point in the history
…e of rendering seams. (#2700, #2539 + Docked windows honor display their border properly. (#2522)

Plus: better support for transparent one in nodes
Side effects: DockContextBindNodeToWindow doesn't alter node->IsVisible.
Side effects: ImDrawList:: _ResetForNewFrame() needs to merge, sane (in case of
(Amended, force-pushed)
  • Loading branch information
ocornut committed Dec 3, 2021
1 parent bf80204 commit b16f738
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 21 deletions.
2 changes: 2 additions & 0 deletions docs/CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ Docking+Viewports Branch:
- Docking: Revert removal of io.ConfigDockingWithShift config option (removed in 1.83). (#4643)
- Docking: Fixed a bug undocking windows docked into a non-visible or _KeepAliveOnly dockspace
when unrelated windows submitted before the dockspace have dynamic visibility. (#4757)
- Docking, Style: Docked windows honor ImGuiCol_WindowBg. (#2700, #2539)
- Docking, Style: Docked windows honor display their border properly. (#2522)
- Docking: Fixed incorrectly rounded tab bars for dock node that are not at the top of their dock tree.
- Docking: Fixed single-frame node pos/size inconsistencies when window stop or start being submitted.
- Viewports: Made it possible to explicitly assign ImGuiWindowClass::ParentViewportId to 0 in order
Expand Down
94 changes: 76 additions & 18 deletions imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4528,6 +4528,8 @@ static void AddWindowToDrawData(ImGuiWindow* window, int layer)
ImGuiContext& g = *GImGui;
ImGuiViewportP* viewport = window->Viewport;
g.IO.MetricsRenderWindows++;
if (window->Flags & ImGuiWindowFlags_DockNodeHost)
window->DrawList->ChannelsMerge();
AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[layer], window->DrawList);
for (int i = 0; i < window->DC.ChildWindows.Size; i++)
{
Expand Down Expand Up @@ -4709,6 +4711,9 @@ void ImGui::EndFrame()
// Update navigation: CTRL+Tab, wrap-around requests
NavEndFrame();

// Update docking
DockContextEndFrame(&g);

SetCurrentViewport(NULL, NULL);

// Drag and Drop: Elapse payload (if delivered, or if source stops being submitted)
Expand Down Expand Up @@ -5653,11 +5658,11 @@ ImVec2 ImGui::CalcWindowNextAutoFitSize(ImGuiWindow* window)
return size_final;
}

static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags)
static ImGuiCol GetWindowBgColorIdx(ImGuiWindow* window)
{
if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
if (window->Flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
return ImGuiCol_PopupBg;
if (flags & ImGuiWindowFlags_ChildWindow)
if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !window->DockIsActive)
return ImGuiCol_ChildBg;
return ImGuiCol_WindowBg;
}
Expand Down Expand Up @@ -5950,7 +5955,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar
if (g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) && *(ImGuiWindow**)g.DragDropPayload.Data == window)
is_docking_transparent_payload = true;

ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags));
ImU32 bg_col = GetColorU32(GetWindowBgColorIdx(window));
if (window->ViewportOwned)
{
// No alpha
Expand All @@ -5976,8 +5981,21 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar
if (override_alpha)
bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT);
}
window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom);

// Render, for docked windows and host windows we ensure bg goes before decorations
ImDrawList* bg_draw_list = window->DockIsActive ? window->DockNode->HostWindow->DrawList : window->DrawList;
if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost))
bg_draw_list->ChannelsSetCurrent(0);
if (window->DockIsActive)
window->DockNode->LastBgColor = bg_col;

bg_draw_list->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom);

if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost))
bg_draw_list->ChannelsSetCurrent(1);
}
if (window->DockIsActive)
window->DockNode->IsBgDrawnThisFrame = true;

// Title bar
// (when docked, DockNode are drawing their own title bar. Individual windows however do NOT set the _NoTitleBar flag,
Expand Down Expand Up @@ -6346,6 +6364,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
window->IDStack.resize(1);
window->DrawList->_ResetForNewFrame();
window->DC.CurrentTableIdx = -1;
if (flags & ImGuiWindowFlags_DockNodeHost)
{
window->DrawList->ChannelsSplit(2);
window->DrawList->ChannelsSetCurrent(1); // Render decorations on channel 1 as we will render the backgrounds manually later
}

// Restore buffer capacity when woken from a compacted state, to avoid
if (window->MemoryCompacted)
Expand Down Expand Up @@ -12786,6 +12809,9 @@ void ImGui::DestroyPlatformWindows()
// | BeginDockableDragDropTarget()
// | - DockNodePreviewDockRender()
//-----------------------------------------------------------------------------
// - EndFrame()
// | DockContextEndFrame()
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Docking: Internal Types
Expand Down Expand Up @@ -12940,6 +12966,7 @@ namespace ImGui
// - DockContextRebuildNodes()
// - DockContextNewFrameUpdateUndocking()
// - DockContextNewFrameUpdateDocking()
// - DockContextEndFrame()
// - DockContextFindNodeByID()
// - DockContextBindNodeToWindow()
// - DockContextGenNodeID()
Expand Down Expand Up @@ -13075,6 +13102,22 @@ void ImGui::DockContextNewFrameUpdateDocking(ImGuiContext* ctx)
DockNodeUpdate(node);
}

void ImGui::DockContextEndFrame(ImGuiContext* ctx)
{
// Draw backgrounds of node missing their window
ImGuiContext& g = *ctx;
ImGuiDockContext* dc = &g.DockContext;
for (int n = 0; n < dc->Nodes.Data.Size; n++)
if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
if (node->LastFrameActive == g.FrameCount && node->IsVisible && node->HostWindow && node->IsLeafNode() && !node->IsBgDrawnThisFrame)
{
ImRect bg_rect(node->Pos + ImVec2(0.0f, GetFrameHeight()), node->Pos + node->Size);
ImDrawFlags bg_rounding_flags = CalcRoundingFlagsForRectInRect(bg_rect, node->HostWindow->Rect(), DOCKING_SPLITTER_SIZE);
node->HostWindow->DrawList->ChannelsSetCurrent(0);
node->HostWindow->DrawList->AddRectFilled(bg_rect.Min, bg_rect.Max, node->LastBgColor, node->HostWindow->WindowRounding, bg_rounding_flags);
}
}

static ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id)
{
return (ImGuiDockNode*)ctx->DockContext.Nodes.GetVoidPtr(id);
Expand Down Expand Up @@ -13592,6 +13635,7 @@ ImGuiDockNode::ImGuiDockNode(ImGuiID id)
SplitAxis = ImGuiAxis_None;

State = ImGuiDockNodeState_Unknown;
LastBgColor = IM_COL32_WHITE;
HostWindow = VisibleWindow = NULL;
CentralNode = OnlyNodeWithWindows = NULL;
CountNodeWithWindows = 0;
Expand All @@ -13603,6 +13647,7 @@ ImGuiDockNode::ImGuiDockNode(ImGuiID id)
AuthorityForViewport = ImGuiDataAuthority_Auto;
IsVisible = true;
IsFocused = HasCloseButton = HasWindowMenuButton = HasCentralNodeChild = false;
IsBgDrawnThisFrame = false;
WantCloseAll = WantLockSizeOnce = WantMouseMove = WantHiddenTabBarUpdate = WantHiddenTabBarToggle = false;
}

Expand Down Expand Up @@ -14044,6 +14089,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
ImGuiContext& g = *GImGui;
IM_ASSERT(node->LastFrameActive != g.FrameCount);
node->LastFrameAlive = g.FrameCount;
node->IsBgDrawnThisFrame = false;

node->CentralNode = node->OnlyNodeWithWindows = NULL;
if (node->IsRootNode())
Expand Down Expand Up @@ -14186,6 +14232,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoCollapse;
window_flags |= ImGuiWindowFlags_NoTitleBar;

SetNextWindowBgAlpha(0.0f); // Don't set ImGuiWindowFlags_NoBackground because it disables borders
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
Begin(window_label, NULL, window_flags);
PopStyleVar();
Expand Down Expand Up @@ -14224,15 +14271,6 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
if (g.NavWindow && g.NavWindow->RootWindow->DockNode && g.NavWindow->RootWindow->ParentWindow == host_window)
node->LastFocusedNodeId = g.NavWindow->RootWindow->DockNode->ID;

// We need to draw a background at the root level if requested by ImGuiDockNodeFlags_PassthruCentralNode, but we will only know the correct pos/size
// _after_ processing the resizing splitters. So we are using the DrawList channel splitting facility to submit drawing primitives out of order!
const bool render_dockspace_bg = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0;
if (render_dockspace_bg)
{
host_window->DrawList->ChannelsSplit(2);
host_window->DrawList->ChannelsSetCurrent(1);
}

// Register a hit-test hole in the window unless we are currently dragging a window that is compatible with our dockspace
ImGuiDockNode* central_node = node->CentralNode;
const bool central_node_hole = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0 && central_node != NULL && central_node->IsEmpty();
Expand Down Expand Up @@ -14265,26 +14303,37 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
// Update position/size, process and draw resizing splitters
if (node->IsRootNode() && host_window)
{
host_window->DrawList->ChannelsSetCurrent(1);
DockNodeTreeUpdatePosSize(node, host_window->Pos, host_window->Size);
DockNodeTreeUpdateSplitter(node);
}

// Draw empty node background (currently can only be the Central Node)
if (host_window && node->IsEmpty() && node->IsVisible && !(node_flags & ImGuiDockNodeFlags_PassthruCentralNode))
host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_DockingEmptyBg));
if (host_window && node->IsEmpty() && node->IsVisible)
{
host_window->DrawList->ChannelsSetCurrent(0);
node->LastBgColor = (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) ? 0 : GetColorU32(ImGuiCol_DockingEmptyBg);
if (node->LastBgColor != 0)
host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, node->LastBgColor);
node->IsBgDrawnThisFrame = true;
}

// Draw whole dockspace background if ImGuiDockNodeFlags_PassthruCentralNode if set.
// We need to draw a background at the root level if requested by ImGuiDockNodeFlags_PassthruCentralNode, but we will only know the correct pos/size
// _after_ processing the resizing splitters. So we are using the DrawList channel splitting facility to submit drawing primitives out of order!
const bool render_dockspace_bg = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0;
if (render_dockspace_bg && node->IsVisible)
{
host_window->DrawList->ChannelsSetCurrent(0);
if (central_node_hole)
RenderRectFilledWithHole(host_window->DrawList, node->Rect(), central_node->Rect(), GetColorU32(ImGuiCol_WindowBg), 0.0f);
else
host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_WindowBg), 0.0f);
host_window->DrawList->ChannelsMerge();
}

// Draw and populate Tab Bar
if (host_window)
host_window->DrawList->ChannelsSetCurrent(1);
if (host_window && node->Windows.Size > 0)
{
DockNodeUpdateTabBar(node, host_window);
Expand Down Expand Up @@ -14319,7 +14368,13 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node)

// Render outer borders last (after the tab bar)
if (node->IsRootNode())
{
host_window->DrawList->ChannelsSetCurrent(1);
RenderWindowOuterBorders(host_window);
}

// Further rendering (= hosted windows background) will be drawn on layer 0
host_window->DrawList->ChannelsSetCurrent(0);
}

// End host window
Expand Down Expand Up @@ -15330,7 +15385,8 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node)
float cur_size_1 = child_1->Size[axis];
float min_size_0 = resize_limits[0] - child_0->Pos[axis];
float min_size_1 = child_1->Pos[axis] + child_1->Size[axis] - resize_limits[1];
if (SplitterBehavior(bb, GetID("##Splitter"), axis, &cur_size_0, &cur_size_1, min_size_0, min_size_1, WINDOWS_HOVER_PADDING, WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER))
ImU32 bg_col = GetColorU32(ImGuiCol_WindowBg);
if (SplitterBehavior(bb, GetID("##Splitter"), axis, &cur_size_0, &cur_size_1, min_size_0, min_size_1, WINDOWS_HOVER_PADDING, WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER, bg_col))
{
if (touching_nodes[0].Size > 0 && touching_nodes[1].Size > 0)
{
Expand Down Expand Up @@ -16061,7 +16117,9 @@ static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGu
}

// Add window to node
bool node_was_visible = node->IsVisible;
DockNodeAddWindow(node, window, true);
node->IsVisible = node_was_visible; // Don't mark visible right away (so DockContextEndFrame() doesn't render it, maybe other side effects? will see)
IM_ASSERT(node == window->DockNode);
return node;
}
Expand Down
2 changes: 2 additions & 0 deletions imgui_draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ void ImDrawList::_ResetForNewFrame()
IM_ASSERT(IM_OFFSETOF(ImDrawCmd, ClipRect) == 0);
IM_ASSERT(IM_OFFSETOF(ImDrawCmd, TextureId) == sizeof(ImVec4));
IM_ASSERT(IM_OFFSETOF(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureID));
if (_Splitter._Count > 1)
_Splitter.Merge(this);

CmdBuffer.resize(0);
IdxBuffer.resize(0);
Expand Down
5 changes: 4 additions & 1 deletion imgui_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,7 @@ struct IMGUI_API ImGuiDockNode
ImVec2 SizeRef; // [Split node only] Last explicitly written-to size (overridden when using a splitter affecting the node), used to calculate Size.
ImGuiAxis SplitAxis; // [Split node only] Split axis (X or Y)
ImGuiWindowClass WindowClass; // [Root node only]
ImU32 LastBgColor;

ImGuiWindow* HostWindow;
ImGuiWindow* VisibleWindow; // Generally point to window which is ID is == SelectedTabID, but when CTRL+Tabbing this can be a different window.
Expand All @@ -1449,6 +1450,7 @@ struct IMGUI_API ImGuiDockNode
ImGuiDataAuthority AuthorityForViewport :3;
bool IsVisible :1; // Set to false when the node is hidden (usually disabled as it has no active window)
bool IsFocused :1;
bool IsBgDrawnThisFrame :1;
bool HasCloseButton :1; // Provide space for a close button (if any of the docked window has one). Note that button may be hidden on window without one.
bool HasWindowMenuButton :1;
bool HasCentralNodeChild :1;
Expand Down Expand Up @@ -2828,6 +2830,7 @@ namespace ImGui
IMGUI_API void DockContextRebuildNodes(ImGuiContext* ctx);
IMGUI_API void DockContextNewFrameUpdateUndocking(ImGuiContext* ctx);
IMGUI_API void DockContextNewFrameUpdateDocking(ImGuiContext* ctx);
IMGUI_API void DockContextEndFrame(ImGuiContext* ctx);
IMGUI_API ImGuiID DockContextGenNodeID(ImGuiContext* ctx);
IMGUI_API void DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer);
IMGUI_API void DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window);
Expand Down Expand Up @@ -3005,7 +3008,7 @@ namespace ImGui
IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0);
IMGUI_API bool DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags);
IMGUI_API bool SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb);
IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f);
IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f, ImU32 bg_col = 0);
IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL);
IMGUI_API bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextItemOpen() data, if any. May return true when logging
IMGUI_API void TreePushOverrideID(ImGuiID id);
Expand Down
6 changes: 4 additions & 2 deletions imgui_widgets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1435,7 +1435,7 @@ void ImGui::Separator()
}

// Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise.
bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay)
bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay, ImU32 bg_col)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
Expand Down Expand Up @@ -1487,7 +1487,9 @@ bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float
}
}

// Render
// Render at new position
if (bg_col & IM_COL32_A_MASK)
window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f);
const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);

Expand Down

0 comments on commit b16f738

Please sign in to comment.