From bbb0f0ade4d716be73c5c4b2552e124e54c63681 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 10 Apr 2025 14:08:10 +0200 Subject: [PATCH 1/6] TreeNode: fixed incorrect clipping of arrow/bullet when using ImGuiTreeNodeFlags_SpanAllColumns. --- docs/CHANGELOG.txt | 1 + imgui_widgets.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index b45cc1e3a..5ab36ed18 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -69,6 +69,7 @@ Other changes: nodes in unusual ways, using indent to create tree-looking structures, etc.) and the feature may not accurately represent them in every cases. - The feature adds a little cost as extra data needs to be stored. +- TreeNode: fixed incorrect clipping of arrow/bullet when using ImGuiTreeNodeFlags_SpanAllColumns. - Nav: fixed assertion when holding gamepad FaceLeft/West button to open CTRL+Tab windowing + pressing a keyboard key. (#8525) - Error Handling: added better error report and recovery for extraneous diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c1d1f54ba..bd56f509b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6794,6 +6794,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + if (span_all_columns && !span_all_columns_label) + TablePopBackgroundChannel(); if (flags & ImGuiTreeNodeFlags_Bullet) RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); else if (!is_leaf) @@ -6814,6 +6816,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); } RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + if (span_all_columns && !span_all_columns_label) + TablePopBackgroundChannel(); if (flags & ImGuiTreeNodeFlags_Bullet) RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); else if (!is_leaf) @@ -6825,9 +6829,6 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if (draw_tree_lines) TreeNodeDrawLineToChildNode(ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.5f)); - if (span_all_columns && !span_all_columns_label) - TablePopBackgroundChannel(); - // Label if (display_frame) RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); From ed50bb167671f716358c8bd7bb5a6fadde39fae3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 10 Apr 2025 14:47:08 +0200 Subject: [PATCH 2/6] TreeNode, Tables: fixed ImGuiTreeNodeFlags_DrawLinesXXX feature when TreePop() is called from a different column. (#2920) --- imgui_internal.h | 13 ++++++++----- imgui_tables.cpp | 31 ++++++++++++++++++++++++++++++- imgui_widgets.cpp | 8 +++++++- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index b4c2a429e..070a75e6f 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -206,6 +206,10 @@ typedef int ImGuiTooltipFlags; // -> enum ImGuiTooltipFlags_ // F typedef int ImGuiTypingSelectFlags; // -> enum ImGuiTypingSelectFlags_ // Flags: for GetTypingSelectRequest() typedef int ImGuiWindowRefreshFlags; // -> enum ImGuiWindowRefreshFlags_ // Flags: for SetNextWindowRefreshPolicy() +// Table column indexing +typedef ImS16 ImGuiTableColumnIdx; +typedef ImU16 ImGuiTableDrawChannelIdx; + //----------------------------------------------------------------------------- // [SECTION] Context pointer // See implementation of this variable in imgui.cpp for comments and details. @@ -1296,6 +1300,7 @@ struct ImGuiTreeNodeStackData ImRect NavRect; // Used for nav landing float DrawLinesX1; float DrawLinesY2; + ImGuiTableColumnIdx DrawLinesTableColumn; }; // sizeof() = 20 @@ -2704,11 +2709,7 @@ struct IMGUI_API ImGuiTabBar //----------------------------------------------------------------------------- #define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. -#define IMGUI_TABLE_MAX_COLUMNS 512 // May be further lifted - -// Our current column maximum is 64 but we may raise that in the future. -typedef ImS16 ImGuiTableColumnIdx; -typedef ImU16 ImGuiTableDrawChannelIdx; +#define IMGUI_TABLE_MAX_COLUMNS 512 // Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx. // [Internal] sizeof() ~ 112 // We use the terminology "Enabled" to refer to a column that is not Hidden by user/api. @@ -3344,6 +3345,8 @@ namespace ImGui IMGUI_API float TableGetHeaderAngledMaxLabelWidth(); IMGUI_API void TablePushBackgroundChannel(); IMGUI_API void TablePopBackgroundChannel(); + IMGUI_API void TablePushColumnChannel(int column_n); + IMGUI_API void TablePopColumnChannel(); IMGUI_API void TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count); // Tables: Internals diff --git a/imgui_tables.cpp b/imgui_tables.cpp index a3d4a886b..6d4d673a1 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2193,6 +2193,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) g.LastItemData.StatusFlags = 0; } + // Also see TablePushColumnChannel() if (table->Flags & ImGuiTableFlags_NoClip) { // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. @@ -2466,10 +2467,38 @@ void ImGui::TablePopBackgroundChannel() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiTable* table = g.CurrentTable; - ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; // Optimization: avoid PopClipRect() + SetCurrentChannel() SetWindowClipRectBeforeSetChannel(window, table->HostBackupInnerClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[table->CurrentColumn].DrawChannelCurrent); +} + +// Also see TableBeginCell() +void ImGui::TablePushColumnChannel(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + // Optimization: avoid SetCurrentChannel() + PushClipRect() + if (table->Flags & ImGuiTableFlags_NoClip) + return; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiTableColumn* column = &table->Columns[column_n]; + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); +} + +void ImGui::TablePopColumnChannel() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + // Optimization: avoid PopClipRect() + SetCurrentChannel() + if (table->Flags & ImGuiTableFlags_NoClip) + return; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index bd56f509b..8d98dab6e 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6562,7 +6562,9 @@ static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) tree_node_data->NavRect = g.LastItemData.NavRect; // Initially I tried to latch value for GetColorU32(ImGuiCol_TreeLines) but it's not a good trade-off for very large trees. - tree_node_data->DrawLinesX1 = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) ? (x1 + g.FontSize * 0.5f + g.Style.FramePadding.x) : +FLT_MAX; + const bool draw_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) != 0; + tree_node_data->DrawLinesX1 = draw_lines ? (x1 + g.FontSize * 0.5f + g.Style.FramePadding.x) : +FLT_MAX; + tree_node_data->DrawLinesTableColumn = draw_lines && g.CurrentTable ? (ImGuiTableColumnIdx)g.CurrentTable->CurrentColumn : -1; tree_node_data->DrawLinesY2 = -FLT_MAX; window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth); } @@ -6919,7 +6921,11 @@ void ImGui::TreePop() if (y1 < y2) { float x = ImTrunc(data->DrawLinesX1); + if (data->DrawLinesTableColumn != -1) + TablePushColumnChannel(data->DrawLinesTableColumn); window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + if (data->DrawLinesTableColumn != -1) + TablePopColumnChannel(); } } g.TreeNodeStack.pop_back(); From 531125346847d26f87e5a0dde9fa74b50d5725f9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 10 Apr 2025 16:03:39 +0200 Subject: [PATCH 3/6] TreeNode: ImGuiTreeNodeFlags_DrawLinesFull uses ToNodes Y2 when they are close (using a threshold). (#2920) --- imgui.h | 4 ++-- imgui_internal.h | 2 +- imgui_widgets.cpp | 14 ++++++++++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/imgui.h b/imgui.h index d5e24f09e..98761113f 100644 --- a/imgui.h +++ b/imgui.h @@ -1217,8 +1217,8 @@ enum ImGuiTreeNodeFlags_ // [EXPERIMENTAL] Draw lines connecting TreeNode hierarchy. Discuss in GitHub issue #2920. // Default value is pulled from style.TreeLinesFlags. May be overridden in TreeNode calls. ImGuiTreeNodeFlags_DrawLinesNone = 1 << 18, // No lines drawn - ImGuiTreeNodeFlags_DrawLinesFull = 1 << 19, // Horizontal lines to child nodes. Vertical line drawn down to TreePop() position: cover full contents. - ImGuiTreeNodeFlags_DrawLinesToNodes = 1 << 20, // Horizontal lines to child nodes. Vertical line drawn down to bottom-most child node. A little bit slower. + ImGuiTreeNodeFlags_DrawLinesFull = 1 << 19, // Horizontal lines to child nodes. Vertical line drawn down to TreePop() position: cover full contents. Faster (for large trees). + ImGuiTreeNodeFlags_DrawLinesToNodes = 1 << 20, // Horizontal lines to child nodes. Vertical line drawn down to bottom-most child node. Slower (for large trees). #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiTreeNodeFlags_AllowItemOverlap = ImGuiTreeNodeFlags_AllowOverlap, // Renamed in 1.89.7 diff --git a/imgui_internal.h b/imgui_internal.h index 070a75e6f..3100bd4ac 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1299,7 +1299,7 @@ struct ImGuiTreeNodeStackData ImGuiItemFlags ItemFlags; // Used for nav landing ImRect NavRect; // Used for nav landing float DrawLinesX1; - float DrawLinesY2; + float DrawLinesToNodesY2; ImGuiTableColumnIdx DrawLinesTableColumn; }; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 8d98dab6e..42741b227 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6565,7 +6565,7 @@ static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) const bool draw_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) != 0; tree_node_data->DrawLinesX1 = draw_lines ? (x1 + g.FontSize * 0.5f + g.Style.FramePadding.x) : +FLT_MAX; tree_node_data->DrawLinesTableColumn = draw_lines && g.CurrentTable ? (ImGuiTableColumnIdx)g.CurrentTable->CurrentColumn : -1; - tree_node_data->DrawLinesY2 = -FLT_MAX; + tree_node_data->DrawLinesToNodesY2 = -FLT_MAX; window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth); } @@ -6659,7 +6659,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if (draw_tree_lines && (flags & ImGuiTreeNodeFlags_DrawLinesToNodes) && (window->DC.TreeHasStackDataDepthMask & (1 << window->DC.TreeDepth))) { ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; - parent_data->DrawLinesY2 = ImMax(parent_data->DrawLinesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway. + parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway. } if (is_open && store_tree_node_stack_data) TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID() @@ -6863,7 +6863,7 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) float x1 = ImTrunc(parent_data->DrawLinesX1); float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x); float y = ImTrunc(target_pos.y); - parent_data->DrawLinesY2 = ImMax(parent_data->DrawLinesY2, y); + parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y); if (x1 < x2) window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } @@ -6916,7 +6916,13 @@ void ImGui::TreePop() { // Draw vertical line of the hierarchy float y1 = ImMax(data->NavRect.Max.y, window->ClipRect.Min.y); - float y2 = (data->TreeFlags & ImGuiTreeNodeFlags_DrawLinesToNodes) ? data->DrawLinesY2 : ImTrunc(window->DC.CursorPos.y - g.Style.ItemSpacing.y - g.FontSize * 0.5f); + float y2 = data->DrawLinesToNodesY2; + if (data->TreeFlags & ImGuiTreeNodeFlags_DrawLinesFull) + { + float y2_full = ImTrunc(window->DC.CursorPos.y - g.Style.ItemSpacing.y - g.FontSize * 0.5f); + if (y2 + g.Style.ItemSpacing.y < y2_full) // FIXME: threshold to use ToNodes Y2 instead of Full Y2 when close by ItemSpacing.y + y2 = y2_full; + } y2 = ImMin(y2, window->ClipRect.Max.y); if (y1 < y2) { From 8c977bf7b3a74ac3a9bea4ad003676ffd2a3d940 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 10 Apr 2025 16:26:38 +0200 Subject: [PATCH 4/6] TreeNode, Tables: fixed ImGuiTreeNodeFlags_DrawLinesXXX feature when TreePop() is called in table: in no column or at top of row. (#2920) --- docs/CHANGELOG.txt | 9 +++++++-- imgui_tables.cpp | 2 +- imgui_widgets.cpp | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 5ab36ed18..1e6766842 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -55,8 +55,8 @@ Other changes: - Windows: loosened code to allow hovering of resize grips, borders, and table borders while hovering a sibling child window, so that the code in master matches one in docking (they accidentally diverged). (#8554) -- TreeNode: added flags to draw tree hierarchy outlines linking parent - and tree nodes: (#2920) +- TreeNode: added experimental flags to draw tree hierarchy outlines linking + parent and tree nodes: (#2920) - ImGuiTreeNodeFlags_DrawLinesNone: No lines drawn (default value in style.TreeLinesFlags). - ImGuiTreeNodeFlags_DrawLinesFull: Horizontal lines to child nodes. Vertical line drawn down to TreePop() position: cover full contents. - ImGuiTreeNodeFlags_DrawLinesToNodes: Horizontal lines to child nodes. Vertical line drawn down to bottom-most child node. @@ -69,6 +69,11 @@ Other changes: nodes in unusual ways, using indent to create tree-looking structures, etc.) and the feature may not accurately represent them in every cases. - The feature adds a little cost as extra data needs to be stored. + (ImGuiTreeNodeFlags_DrawLinesToNodes is slower than ImGuiTreeNodeFlags_DrawLinesFull + which may be meaningful on very large trees, as it needs to record bottom-most + Y position even for clipped nodes). + - The feature is unlikely to ever work properly when using a coarse clipper + such as ImGuiListClipper. - TreeNode: fixed incorrect clipping of arrow/bullet when using ImGuiTreeNodeFlags_SpanAllColumns. - Nav: fixed assertion when holding gamepad FaceLeft/West button to open CTRL+Tab windowing + pressing a keyboard key. (#8525) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 6d4d673a1..b8b869301 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2494,7 +2494,7 @@ void ImGui::TablePopColumnChannel() ImGuiTable* table = g.CurrentTable; // Optimization: avoid PopClipRect() + SetCurrentChannel() - if (table->Flags & ImGuiTableFlags_NoClip) + if ((table->Flags & ImGuiTableFlags_NoClip) || (table->CurrentColumn == -1)) // Calling TreePop() after TableNextRow() is supported. return; ImGuiWindow* window = g.CurrentWindow; const ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 42741b227..6933fdcb6 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6919,7 +6919,10 @@ void ImGui::TreePop() float y2 = data->DrawLinesToNodesY2; if (data->TreeFlags & ImGuiTreeNodeFlags_DrawLinesFull) { - float y2_full = ImTrunc(window->DC.CursorPos.y - g.Style.ItemSpacing.y - g.FontSize * 0.5f); + float y2_full = window->DC.CursorPos.y; + if (g.CurrentTable) + y2_full = ImMax(g.CurrentTable->RowPosY2, y2_full); + y2_full = ImTrunc(y2_full - g.Style.ItemSpacing.y - g.FontSize * 0.5f); if (y2 + g.Style.ItemSpacing.y < y2_full) // FIXME: threshold to use ToNodes Y2 instead of Full Y2 when close by ItemSpacing.y y2 = y2_full; } From ee0d96ac0d7c0a2d79ad1650eb4c12f9c7cd739a Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 10 Apr 2025 17:00:17 +0200 Subject: [PATCH 5/6] TreeNode: extract code out of TreePop() into TreeNodeDrawLineToTreePop(). (#2920) --- imgui_internal.h | 1 + imgui_widgets.cpp | 60 ++++++++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 3100bd4ac..d5155e425 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3471,6 +3471,7 @@ namespace ImGui // Widgets: Tree Nodes IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); IMGUI_API void TreeNodeDrawLineToChildNode(const ImVec2& target_pos); + IMGUI_API void TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data); IMGUI_API void TreePushOverrideID(ImGuiID id); IMGUI_API bool TreeNodeGetOpen(ImGuiID storage_id); IMGUI_API void TreeNodeSetOpen(ImGuiID storage_id, bool open); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6933fdcb6..13c1362b1 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6868,6 +6868,33 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } +// Draw vertical line of the hierarchy +void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + float y1 = ImMax(data->NavRect.Max.y, window->ClipRect.Min.y); + float y2 = data->DrawLinesToNodesY2; + if (data->TreeFlags & ImGuiTreeNodeFlags_DrawLinesFull) + { + float y2_full = window->DC.CursorPos.y; + if (g.CurrentTable) + y2_full = ImMax(g.CurrentTable->RowPosY2, y2_full); + y2_full = ImTrunc(y2_full - g.Style.ItemSpacing.y - g.FontSize * 0.5f); + if (y2 + g.Style.ItemSpacing.y < y2_full) // FIXME: threshold to use ToNodes Y2 instead of Full Y2 when close by ItemSpacing.y + y2 = y2_full; + } + y2 = ImMin(y2, window->ClipRect.Max.y); + if (y2 <= y1) + return; + float x = ImTrunc(data->DrawLinesX1); + if (data->DrawLinesTableColumn != -1) + TablePushColumnChannel(data->DrawLinesTableColumn); + window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + if (data->DrawLinesTableColumn != -1) + TablePopColumnChannel(); +} + void ImGui::TreePush(const char* str_id) { ImGuiWindow* window = GetCurrentWindow(); @@ -6906,37 +6933,16 @@ void ImGui::TreePop() { const ImGuiTreeNodeStackData* data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; IM_ASSERT(data->ID == window->IDStack.back()); + + // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) - { - // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data); - } + + // Draw hierarchy lines if (data->DrawLinesX1 != +FLT_MAX && window->DC.CursorPos.y >= window->ClipRect.Min.y) - { - // Draw vertical line of the hierarchy - float y1 = ImMax(data->NavRect.Max.y, window->ClipRect.Min.y); - float y2 = data->DrawLinesToNodesY2; - if (data->TreeFlags & ImGuiTreeNodeFlags_DrawLinesFull) - { - float y2_full = window->DC.CursorPos.y; - if (g.CurrentTable) - y2_full = ImMax(g.CurrentTable->RowPosY2, y2_full); - y2_full = ImTrunc(y2_full - g.Style.ItemSpacing.y - g.FontSize * 0.5f); - if (y2 + g.Style.ItemSpacing.y < y2_full) // FIXME: threshold to use ToNodes Y2 instead of Full Y2 when close by ItemSpacing.y - y2 = y2_full; - } - y2 = ImMin(y2, window->ClipRect.Max.y); - if (y1 < y2) - { - float x = ImTrunc(data->DrawLinesX1); - if (data->DrawLinesTableColumn != -1) - TablePushColumnChannel(data->DrawLinesTableColumn); - window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); - if (data->DrawLinesTableColumn != -1) - TablePopColumnChannel(); - } - } + TreeNodeDrawLineToTreePop(data); + g.TreeNodeStack.pop_back(); window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask; } From 9943137d1e926d4e4901c5721fea249c8ee4355f Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 10 Apr 2025 17:25:06 +0200 Subject: [PATCH 6/6] TreeNode: fixed non-opened clipped child node not moving Y2 marker. (#2920) --- imgui.cpp | 2 +- imgui_internal.h | 3 ++- imgui_widgets.cpp | 8 ++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b32874ec5..c7566494b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7649,7 +7649,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.MenuBarAppending = false; window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user); window->DC.TreeDepth = 0; - window->DC.TreeHasStackDataDepthMask = 0x00; + window->DC.TreeHasStackDataDepthMask = window->DC.TreeRecordsClippedNodesY2Mask = 0x00; window->DC.ChildWindows.resize(0); window->DC.StateStorage = &window->StateStorage; window->DC.CurrentColumns = NULL; diff --git a/imgui_internal.h b/imgui_internal.h index d5155e425..65de255f5 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2474,7 +2474,8 @@ struct IMGUI_API ImGuiWindowTempData ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs. ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement int TreeDepth; // Current tree depth. - ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. + ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. + ImU32 TreeRecordsClippedNodesY2Mask; // Store whether we should keep recording Y2. Cleared when passing clip max. Equivalent TreeHasStackDataDepthMask value should always be set. ImVector ChildWindows; ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) ImGuiOldColumns* CurrentColumns; // Current columns set diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 13c1362b1..140ceb9e1 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6564,9 +6564,11 @@ static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) // Initially I tried to latch value for GetColorU32(ImGuiCol_TreeLines) but it's not a good trade-off for very large trees. const bool draw_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) != 0; tree_node_data->DrawLinesX1 = draw_lines ? (x1 + g.FontSize * 0.5f + g.Style.FramePadding.x) : +FLT_MAX; - tree_node_data->DrawLinesTableColumn = draw_lines && g.CurrentTable ? (ImGuiTableColumnIdx)g.CurrentTable->CurrentColumn : -1; + tree_node_data->DrawLinesTableColumn = (draw_lines && g.CurrentTable) ? (ImGuiTableColumnIdx)g.CurrentTable->CurrentColumn : -1; tree_node_data->DrawLinesToNodesY2 = -FLT_MAX; window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth); + if (flags & ImGuiTreeNodeFlags_DrawLinesToNodes) + window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth); } // When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop. @@ -6656,10 +6658,12 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; if (!is_visible) { - if (draw_tree_lines && (flags & ImGuiTreeNodeFlags_DrawLinesToNodes) && (window->DC.TreeHasStackDataDepthMask & (1 << window->DC.TreeDepth))) + if ((flags & ImGuiTreeNodeFlags_DrawLinesToNodes) && (window->DC.TreeRecordsClippedNodesY2Mask & (1 << (window->DC.TreeDepth - 1)))) { ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway. + if (frame_bb.Min.y >= window->ClipRect.Max.y) + window->DC.TreeRecordsClippedNodesY2Mask &= ~(1 << (window->DC.TreeDepth - 1)); // Done } if (is_open && store_tree_node_stack_data) TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID()