From b409df34dbe88411261771719c81373953d06d59 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 3 Nov 2021 17:00:40 +0100 Subject: [PATCH 1/6] Clipper: Fixed content height declaration slightly mismatching the one of when not using a clipper. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 2 +- imgui.h | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 59cb8d87..052915d2 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,6 +41,8 @@ Other Changes: - Added IsMouseTripleClicked() function. Tracking multi-click count in IO structure. (#3229) [@kudaba] - Modals: fixed issue hovering popups inside a child inside a modal. (#4676, #4527) - Fixed IsWindowFocused()/IsWindowHovered() issues with childs inside popups. (#4676) +- Clipper: Fixed content height declaration slightly mismatching the one of when not using a clipper. + (an additional ItemSpacing.y was declared, affecting scrollbar range). - Nav: pressing PageUp/PageDown/Home/End when in Menu layer automatically moves back to Main layer. - Nav: fixed resizing window from borders setting navigation to Menu layer. - Nav: pressing Esc to exit a child window reactivates the Nav highlight if it was disabled by mouse. diff --git a/imgui.cpp b/imgui.cpp index 95a799df..8f9a4f0c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2297,7 +2297,7 @@ static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height) ImGuiWindow* window = g.CurrentWindow; float off_y = pos_y - window->DC.CursorPos.y; window->DC.CursorPos.y = pos_y; - window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y); + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y - g.Style.ItemSpacing.y); window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHereY() can properly function after the end of our clipper usage. window->DC.PrevLineSize.y = (line_height - g.Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. if (ImGuiOldColumns* columns = window->DC.CurrentColumns) diff --git a/imgui.h b/imgui.h index e3d2b41e..a95c814f 100644 --- a/imgui.h +++ b/imgui.h @@ -64,7 +64,7 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) #define IMGUI_VERSION "1.86 WIP" -#define IMGUI_VERSION_NUM 18504 +#define IMGUI_VERSION_NUM 18505 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE From cd1b5f7883fa9f1faf550a051abd32d5fd9dc218 Mon Sep 17 00:00:00 2001 From: GamingMinds-DanielC Date: Mon, 11 Oct 2021 15:57:29 +0200 Subject: [PATCH 2/6] Clipper: enhanced ImGuiListClipper (#3841) --- imgui.cpp | 153 +++++++++++++++++++++++++++++++++++++++++------------- imgui.h | 10 ++++ 2 files changed, 128 insertions(+), 35 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 8f9a4f0c..d77c8ff3 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2288,6 +2288,42 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items *out_items_display_end = end; } +static int SortAndFuseRanges(int* range_start, int* range_end, int range_count) +{ + // Helper to order ranges and fuse them together if possible. + // First sort both rangeStart and rangeEnd by rangeStart. Since this helper will just sort 2 or 3 entries, a bubble sort will do fine. + for (int sort_end = range_count - 1; sort_end > 0; --sort_end) + { + for (int i = 0; i < sort_end; ++i) + { + if (range_start[i] > range_start[i + 1]) + { + ImSwap(range_start[i], range_start[i + 1]); + ImSwap(range_end[i], range_end[i + 1]); + } + } + } + + // Now fuse ranges together as much as possible. + for (int i = 1; i < range_count;) + { + if (range_end[i - 1] >= range_start[i]) + { + range_end[i - 1] = ImMax(range_end[i - 1], range_end[i]); + range_count--; + for (int j = i; j < range_count; ++j) + { + range_start[j] = range_start[j + 1]; + range_end[j] = range_end[j + 1]; + } + } + else + i++; + } + + return range_count; +} + static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height) { // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor. @@ -2354,7 +2390,27 @@ void ImGuiListClipper::End() if (ItemsCount < INT_MAX && DisplayStart >= 0) SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); ItemsCount = -1; - StepNo = 3; + StepNo = RangeCount; +} + +void ImGuiListClipper::ForceDisplayRange(int item_start, int item_end) +{ + if (DisplayStart < 0 && RangeCount + YRangeCount < 1) // Only allowed after Begin() and if there has not been a specified range yet. + { + RangeStart[RangeCount] = item_start; + RangeEnd[RangeCount] = item_end; + RangeCount++; + } +} + +void ImGuiListClipper::ForceDisplayYRange(float y_min, float y_max) +{ + if (DisplayStart < 0 && RangeCount + YRangeCount < 1) // Only allowed after Begin() and if there has not been a specified range yet. + { + YRangeMin[YRangeCount] = y_min; + YRangeMax[YRangeCount] = y_max; + YRangeCount++; + } } bool ImGuiListClipper::Step() @@ -2373,6 +2429,8 @@ bool ImGuiListClipper::Step() return false; } + bool calc_clipping = false; + // Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element height) if (StepNo == 0) { @@ -2389,22 +2447,24 @@ bool ImGuiListClipper::Step() StartPosY = window->DC.CursorPos.y; if (ItemsHeight <= 0.0f) { - // Submit the first item so we can measure its height (generally it is 0..1) - DisplayStart = ItemsFrozen; - DisplayEnd = ItemsFrozen + 1; + // Submit the first item (or range) so we can measure its height (generally it is 0..1) + RangeStart[RangeCount] = ItemsFrozen; + RangeEnd[RangeCount] = ItemsFrozen + 1; + if (++RangeCount > 1) + RangeCount = SortAndFuseRanges(RangeStart, RangeEnd, RangeCount); + DisplayStart = ImMax(RangeStart[0], ItemsFrozen); + DisplayEnd = ImMin(RangeEnd[0], ItemsCount); StepNo = 1; return true; } - // Already has item height (given by user in Begin): skip to calculating step - DisplayStart = DisplayEnd; - StepNo = 2; + calc_clipping = true; // If on the first step with known item height, calculate clipping. } - // Step 1: the clipper infer height from first element - if (StepNo == 1) + // Step 1: Let the clipper infer height from first range + if (ItemsHeight <= 0.0f) { - IM_ASSERT(ItemsHeight <= 0.0f); + IM_ASSERT(StepNo == 1); if (table) { const float pos_y1 = table->RowPosY1; // Using this instead of StartPosY to handle clipper straddling the frozen row @@ -2414,49 +2474,72 @@ bool ImGuiListClipper::Step() } else { - ItemsHeight = window->DC.CursorPos.y - StartPosY; + ItemsHeight = (window->DC.CursorPos.y - StartPosY) / (float)(DisplayEnd - DisplayStart); } IM_ASSERT(ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); - StepNo = 2; + + calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards. } - // Reached end of list - if (DisplayEnd >= ItemsCount) - { - End(); - return false; - } - - // Step 2: calculate the actual range of elements to display, and position the cursor before the first element - if (StepNo == 2) + // Step 0 or 1: Calculate the actual range of visible elements. + if (calc_clipping) { IM_ASSERT(ItemsHeight > 0.0f); int already_submitted = DisplayEnd; - ImGui::CalcListClipping(ItemsCount - already_submitted, ItemsHeight, &DisplayStart, &DisplayEnd); - DisplayStart += already_submitted; - DisplayEnd += already_submitted; + ImGui::CalcListClipping(ItemsCount - already_submitted, ItemsHeight, &RangeStart[RangeCount], &RangeEnd[RangeCount]); + + // Only add another range if it hasn't been handled by the initial range. + if (RangeStart[RangeCount] < RangeEnd[RangeCount]) + { + RangeStart[RangeCount] += already_submitted; + RangeEnd[RangeCount] += already_submitted; + RangeCount++; + } + + // Convert specified y ranges to item index ranges. + for (int i = 0; i < YRangeCount; ++i) + { + int start = already_submitted + (int)((YRangeMin[i] - window->DC.CursorPos.y) / ItemsHeight); + int end = already_submitted + (int)((YRangeMax[i] - window->DC.CursorPos.y) / ItemsHeight) + 1; + + start = ImMax(start, already_submitted); + end = ImMin(end, ItemsCount); + + if (start < end) + { + RangeStart[RangeCount] = start; + RangeEnd[RangeCount] = end; + RangeCount++; + } + } + + // Try to sort and fuse only if there is more than 1 range remaining. + if (RangeCount > StepNo + 1) + RangeCount = StepNo + SortAndFuseRanges(&RangeStart[StepNo], &RangeEnd[StepNo], RangeCount - StepNo); + } + + // Step 0+ (if item height is given in advance) or 1+: Display the next range in line. + if (StepNo < RangeCount) + { + int already_submitted = DisplayEnd; + DisplayStart = ImMax(RangeStart[StepNo], already_submitted); + DisplayEnd = ImMin(RangeEnd[StepNo], ItemsCount); // Seek cursor if (DisplayStart > already_submitted) SetCursorPosYAndSetupForPrevLine(StartPosY + (DisplayStart - ItemsFrozen) * ItemsHeight, ItemsHeight); - StepNo = 3; + StepNo++; return true; } - // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), + // After the last step: Let the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), // Advance the cursor to the end of the list and then returns 'false' to end the loop. - if (StepNo == 3) - { - // Seek cursor - if (ItemsCount < INT_MAX) - SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); // advance cursor - ItemsCount = -1; - return false; - } + if (ItemsCount < INT_MAX) + SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); // advance cursor + ItemsCount = -1; - IM_ASSERT(0); return false; } diff --git a/imgui.h b/imgui.h index a95c814f..132375e6 100644 --- a/imgui.h +++ b/imgui.h @@ -2179,6 +2179,7 @@ struct ImGuiStorage // Usage: // ImGuiListClipper clipper; // clipper.Begin(1000); // We have 1000 elements, evenly spaced. +// clipper.ForceDisplay(42); // Optional, force element with given index to be displayed (use f.e. if you need to update a tooltip for a drag&drop source) // while (clipper.Step()) // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) // ImGui::Text("line number %d", i); @@ -2195,6 +2196,12 @@ struct ImGuiListClipper // [Internal] int ItemsCount; + int RangeStart[4]; // 1 for the user, rest for internal use + int RangeEnd[4]; + int RangeCount; + int YRangeMin[1]; + int YRangeMax[1]; + int YRangeCount; int StepNo; int ItemsFrozen; float ItemsHeight; @@ -2207,6 +2214,9 @@ struct ImGuiListClipper // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). IMGUI_API void Begin(int items_count, float items_height = -1.0f); // Automatically called by constructor if you passed 'items_count' or by Step() in Step 1. IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. + IMGUI_API void ForceDisplayRange(int item_start, int item_end); // Optionally call before the first call to Step() if you need a range of items to be displayed regardless of visibility. + inline void ForceDisplay(int item_start, int item_count = 1) { ForceDisplayRange(item_start, item_start + item_count); } // Like ForceDisplayRange, but with a number instead of an end index. + IMGUI_API void ForceDisplayYRange(float y_min, float y_max); // Like ForceDisplayRange, but with y coordinates instead of item indices. IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS From 93cccd27f6eb0cd53ea952dcf14dd87b1e52cedd Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 11 Oct 2021 17:19:20 +0200 Subject: [PATCH 3/6] Clipper: bunch of rework. (#3841, #1725) - Focused/NavId now always included in display range. - Any number of steps (while preserving zero-alloc policy). - Non contiguous ranges for nav processing - Moved new fields internally (+ moved StepNo away from sight so it doesn't get missused). - Generally tweaks/refactors. --- docs/CHANGELOG.txt | 7 +- imgui.cpp | 253 ++++++++++++++++++++++++--------------------- imgui.h | 51 ++++----- imgui_internal.h | 36 +++++++ 4 files changed, 197 insertions(+), 150 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 052915d2..83618619 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,7 +41,12 @@ Other Changes: - Added IsMouseTripleClicked() function. Tracking multi-click count in IO structure. (#3229) [@kudaba] - Modals: fixed issue hovering popups inside a child inside a modal. (#4676, #4527) - Fixed IsWindowFocused()/IsWindowHovered() issues with childs inside popups. (#4676) -- Clipper: Fixed content height declaration slightly mismatching the one of when not using a clipper. +- Clipper: currently focused item is automatically included in clipper range. + Fixes issue where e.g. drag and dropping an item and scrolling ensure the item source location is + still submitted. (#3841, #1725) [@GamingMinds-DanielC, @ocornut] +- Clipper: rework so gamepad/keyboard navigation doesn't create spikes in number of items requested + by the clipper to display. (#3841) +- Clipper: Fixed content height declaration slightly mismatching the value of when not using a clipper. (an additional ItemSpacing.y was declared, affecting scrollbar range). - Nav: pressing PageUp/PageDown/Home/End when in Menu layer automatically moves back to Main layer. - Nav: fixed resizing window from borders setting navigation to Menu layer. diff --git a/imgui.cpp b/imgui.cpp index d77c8ff3..d7c7385e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2248,6 +2248,8 @@ static bool GetSkipItemForListClipping() // Helper to calculate coarse clipping of large list of evenly sized items. // NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. // NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX +// FIXME: This legacy API is not ideal because it assume we will return a single contiguous rectangle. +// Prefer using ImGuiListClipper which returns disconnected ranges. void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) { ImGuiContext& g = *GImGui; @@ -2266,15 +2268,16 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items } // We create the union of the ClipRect and the scoring rect which at worst should be 1 page away from ClipRect - ImRect unclipped_rect = window->ClipRect; + // We don't include g.NavId's rectangle in there (unless g.NavJustMovedToId is set) because the rectangle enlargement can get costly. + ImRect rect = window->ClipRect; if (g.NavMoveScoringItems) - unclipped_rect.Add(g.NavScoringRect); + rect.Add(g.NavScoringNoClipRect); if (g.NavJustMovedToId && window->NavLastIds[0] == g.NavJustMovedToId) - unclipped_rect.Add(WindowRectRelToAbs(window, window->NavRectRel[0])); // Could store and use NavJustMovedToRectRel + rect.Add(WindowRectRelToAbs(window, window->NavRectRel[0])); // Could store and use NavJustMovedToRectRel const ImVec2 pos = window->DC.CursorPos; - int start = (int)((unclipped_rect.Min.y - pos.y) / items_height); - int end = (int)((unclipped_rect.Max.y - pos.y) / items_height); + int start = (int)((rect.Min.y - pos.y) / items_height); + int end = (int)((rect.Max.y - pos.y) / items_height); // When performing a navigation request, ensure we have one item extra in the direction we are moving to if (g.NavMoveScoringItems && g.NavMoveClipDir == ImGuiDir_Up) @@ -2288,47 +2291,35 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items *out_items_display_end = end; } -static int SortAndFuseRanges(int* range_start, int* range_end, int range_count) +static void ImGuiListClipper_SortAndFuseRanges(ImVector& ranges, int offset = 0) { - // Helper to order ranges and fuse them together if possible. - // First sort both rangeStart and rangeEnd by rangeStart. Since this helper will just sort 2 or 3 entries, a bubble sort will do fine. - for (int sort_end = range_count - 1; sort_end > 0; --sort_end) - { - for (int i = 0; i < sort_end; ++i) - { - if (range_start[i] > range_start[i + 1]) - { - ImSwap(range_start[i], range_start[i + 1]); - ImSwap(range_end[i], range_end[i + 1]); - } - } - } + if (ranges.Size - offset <= 1) + return; + + // Helper to order ranges and fuse them together if possible (bubble sort is fine as we are only sorting 2-3 entries) + for (int sort_end = ranges.Size - offset - 1; sort_end > 0; --sort_end) + for (int i = offset; i < sort_end + offset; ++i) + if (ranges[i].Min > ranges[i + 1].Min) + ImSwap(ranges[i], ranges[i + 1]); // Now fuse ranges together as much as possible. - for (int i = 1; i < range_count;) + for (int i = 1 + offset; i < ranges.Size; i++) { - if (range_end[i - 1] >= range_start[i]) - { - range_end[i - 1] = ImMax(range_end[i - 1], range_end[i]); - range_count--; - for (int j = i; j < range_count; ++j) - { - range_start[j] = range_start[j + 1]; - range_end[j] = range_end[j + 1]; - } - } - else - i++; + IM_ASSERT(!ranges[i].PosToIndexConvert && !ranges[i - 1].PosToIndexConvert); + if (ranges[i - 1].Max < ranges[i].Min) + continue; + ranges[i - 1].Min = ImMin(ranges[i - 1].Min, ranges[i].Min); + ranges[i - 1].Max = ImMax(ranges[i - 1].Max, ranges[i].Max); + ranges.erase(ranges.Data + i); + i--; } - - return range_count; } -static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height) +static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_height) { // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor. // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. - // The clipper should probably have a 4th step to display the last item in a regular manner. + // The clipper should probably have a final step to display the last item in a regular manner, maybe with an opt-out flag for data sets which may have costly seek? ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; float off_y = pos_y - window->DC.CursorPos.y; @@ -2349,6 +2340,14 @@ static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height) } } +static void ImGuiListClipper_SeekCursorForItem(ImGuiListClipper* clipper, int item_n) +{ + // StartPosY starts from ItemsFrozen hence the subtraction + ImGuiListClipperData* data = (ImGuiListClipperData*)clipper->TempData; + float pos_y = clipper->StartPosY + (item_n - data->ItemsFrozen) * clipper->ItemsHeight; + ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, clipper->ItemsHeight); +} + ImGuiListClipper::ImGuiListClipper() { memset(this, 0, sizeof(*this)); @@ -2375,48 +2374,60 @@ void ImGuiListClipper::Begin(int items_count, float items_height) StartPosY = window->DC.CursorPos.y; ItemsHeight = items_height; ItemsCount = items_count; - ItemsFrozen = 0; - StepNo = 0; DisplayStart = -1; DisplayEnd = 0; + + // Acquire temporary buffer + if (++g.ClipperTempDataStacked > g.ClipperTempData.Size) + g.ClipperTempData.resize(g.ClipperTempDataStacked, ImGuiListClipperData()); + ImGuiListClipperData* data = &g.ClipperTempData[g.ClipperTempDataStacked - 1]; + data->Reset(this); + TempData = data; } void ImGuiListClipper::End() { + ImGuiContext& g = *GImGui; if (ItemsCount < 0) // Already ended return; - // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. + // In theory here we should assert that we are already at the right position, but it seems saner to just seek at the end and not assert/crash the user. + ImGuiListClipperData* data = (ImGuiListClipperData*)TempData; if (ItemsCount < INT_MAX && DisplayStart >= 0) - SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); + ImGuiListClipper_SeekCursorForItem(this, ItemsCount); ItemsCount = -1; - StepNo = RangeCount; + data->StepNo = data->Ranges.Size; + + // Restore temporary buffer and fix back pointers which may be invalidated when nesting + IM_ASSERT(g.ClipperTempDataStacked > 0); + data = (--g.ClipperTempDataStacked > 0) ? &g.ClipperTempData[g.ClipperTempDataStacked - 1] : NULL; + if (data) + data->ListClipper->TempData = data; } -void ImGuiListClipper::ForceDisplayRange(int item_start, int item_end) +void ImGuiListClipper::ForceDisplayRangeByIndices(int item_min, int item_max) { - if (DisplayStart < 0 && RangeCount + YRangeCount < 1) // Only allowed after Begin() and if there has not been a specified range yet. - { - RangeStart[RangeCount] = item_start; - RangeEnd[RangeCount] = item_end; - RangeCount++; - } + ImGuiListClipperData* data = (ImGuiListClipperData*)TempData; + IM_ASSERT(DisplayStart < 0); // Only allowed after Begin() and if there has not been a specified range yet. + IM_ASSERT(item_min <= item_max); + if (item_min < item_max) + data->Ranges.push_back(ImGuiListClipperRange::FromIndices(item_min, item_max)); } -void ImGuiListClipper::ForceDisplayYRange(float y_min, float y_max) +void ImGuiListClipper::ForceDisplayRangeByPositions(float y_min, float y_max) { - if (DisplayStart < 0 && RangeCount + YRangeCount < 1) // Only allowed after Begin() and if there has not been a specified range yet. - { - YRangeMin[YRangeCount] = y_min; - YRangeMax[YRangeCount] = y_max; - YRangeCount++; - } + ImGuiListClipperData* data = (ImGuiListClipperData*)TempData; + IM_ASSERT(DisplayStart < 0); // Only allowed after Begin() and if there has not been a specified range yet. + IM_ASSERT(y_min <= y_max); + if (y_min < y_max) + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(y_min, y_max, 0, 0)); } bool ImGuiListClipper::Step() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + ImGuiListClipperData* data = (ImGuiListClipperData*)TempData; ImGuiTable* table = g.CurrentTable; if (table && table->IsInsideRow) @@ -2429,46 +2440,41 @@ bool ImGuiListClipper::Step() return false; } - bool calc_clipping = false; + // While we are in frozen row state, keep displaying items one by one, unclipped + // FIXME: Could be stored as a table-agnostic state. + if (data->StepNo == 0 && table != NULL && !table->IsUnfrozenRows) + { + DisplayStart = data->ItemsFrozen; + DisplayEnd = data->ItemsFrozen + 1; + data->ItemsFrozen++; + return true; + } // Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element height) - if (StepNo == 0) + bool calc_clipping = false; + if (data->StepNo == 0) { - // While we are in frozen row state, keep displaying items one by one, unclipped - // FIXME: Could be stored as a table-agnostic state. - if (table != NULL && !table->IsUnfrozenRows) - { - DisplayStart = ItemsFrozen; - DisplayEnd = ItemsFrozen + 1; - ItemsFrozen++; - return true; - } - StartPosY = window->DC.CursorPos.y; if (ItemsHeight <= 0.0f) { - // Submit the first item (or range) so we can measure its height (generally it is 0..1) - RangeStart[RangeCount] = ItemsFrozen; - RangeEnd[RangeCount] = ItemsFrozen + 1; - if (++RangeCount > 1) - RangeCount = SortAndFuseRanges(RangeStart, RangeEnd, RangeCount); - DisplayStart = ImMax(RangeStart[0], ItemsFrozen); - DisplayEnd = ImMin(RangeEnd[0], ItemsCount); - StepNo = 1; + // Submit the first item (or range) so we can measure its height (generally the first range is 0..1) + data->Ranges.push_front(ImGuiListClipperRange::FromIndices(data->ItemsFrozen, data->ItemsFrozen + 1)); + DisplayStart = ImMax(data->Ranges[0].Min, data->ItemsFrozen); + DisplayEnd = ImMin(data->Ranges[0].Max, ItemsCount); + data->StepNo = 1; return true; } - calc_clipping = true; // If on the first step with known item height, calculate clipping. } // Step 1: Let the clipper infer height from first range if (ItemsHeight <= 0.0f) { - IM_ASSERT(StepNo == 1); + IM_ASSERT(data->StepNo == 1); if (table) { - const float pos_y1 = table->RowPosY1; // Using this instead of StartPosY to handle clipper straddling the frozen row - const float pos_y2 = table->RowPosY2; // Using this instead of CursorPos.y to take account of tallest cell. + const float pos_y1 = table->RowPosY1; // Using RowPosY1 instead of StartPosY to handle clipper straddling the frozen row + const float pos_y2 = table->RowPosY2; // Using RowPosY2 instead of CursorPos.y to take account of tallest cell. ItemsHeight = pos_y2 - pos_y1; window->DC.CursorPos.y = pos_y2; } @@ -2477,67 +2483,64 @@ bool ImGuiListClipper::Step() ItemsHeight = (window->DC.CursorPos.y - StartPosY) / (float)(DisplayEnd - DisplayStart); } IM_ASSERT(ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); - calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards. } - // Step 0 or 1: Calculate the actual range of visible elements. + // Step 0 or 1: Calculate the actual ranges of visible elements. + const int already_submitted = DisplayEnd; if (calc_clipping) { - IM_ASSERT(ItemsHeight > 0.0f); - - int already_submitted = DisplayEnd; - ImGui::CalcListClipping(ItemsCount - already_submitted, ItemsHeight, &RangeStart[RangeCount], &RangeEnd[RangeCount]); - - // Only add another range if it hasn't been handled by the initial range. - if (RangeStart[RangeCount] < RangeEnd[RangeCount]) + if (g.LogEnabled) { - RangeStart[RangeCount] += already_submitted; - RangeEnd[RangeCount] += already_submitted; - RangeCount++; + // If logging is active, do not perform any clipping + data->Ranges.push_back(ImGuiListClipperRange::FromIndices(0, ItemsCount)); + } + else + { + // Add range selected to be included for navigation + if (g.NavMoveScoringItems) + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, 0, 0)); + + // Add focused/active item + ImRect nav_rect_abs = ImGui::WindowRectRelToAbs(window, window->NavRectRel[0]); + if (g.NavId != 0 && window->NavLastIds[0] == g.NavId) + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0)); + + // Add visible range + const int off_min = (g.NavMoveScoringItems && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0; + const int off_max = (g.NavMoveScoringItems && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0; + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(window->ClipRect.Min.y, window->ClipRect.Max.y, off_min, off_max)); } - // Convert specified y ranges to item index ranges. - for (int i = 0; i < YRangeCount; ++i) - { - int start = already_submitted + (int)((YRangeMin[i] - window->DC.CursorPos.y) / ItemsHeight); - int end = already_submitted + (int)((YRangeMax[i] - window->DC.CursorPos.y) / ItemsHeight) + 1; - - start = ImMax(start, already_submitted); - end = ImMin(end, ItemsCount); - - if (start < end) + // Convert position ranges to item index ranges + // - Very important: when a starting position is after our maximum item, we set Min to (ItemsCount - 1). This allows us to handle most forms of wrapping. + // - Due to how Selectable extra padding they tend to be "unaligned" with exact unit in the item list, + // which with the flooring/ceiling tend to lead to 2 items instead of one being submitted. + for (int i = 0; i < data->Ranges.Size; i++) + if (data->Ranges[i].PosToIndexConvert) { - RangeStart[RangeCount] = start; - RangeEnd[RangeCount] = end; - RangeCount++; + data->Ranges[i].Min = ImClamp(already_submitted + (int)ImFloor((data->Ranges[i].Min - window->DC.CursorPos.y) / ItemsHeight) + data->Ranges[i].PosToIndexOffsetMin, already_submitted, ItemsCount - 1); + data->Ranges[i].Max = ImClamp(already_submitted + (int)ImCeil((data->Ranges[i].Max - window->DC.CursorPos.y) / ItemsHeight) + 0 + data->Ranges[i].PosToIndexOffsetMax, data->Ranges[i].Min + 1, ItemsCount); + data->Ranges[i].PosToIndexConvert = false; } - } - - // Try to sort and fuse only if there is more than 1 range remaining. - if (RangeCount > StepNo + 1) - RangeCount = StepNo + SortAndFuseRanges(&RangeStart[StepNo], &RangeEnd[StepNo], RangeCount - StepNo); + ImGuiListClipper_SortAndFuseRanges(data->Ranges, data->StepNo); } // Step 0+ (if item height is given in advance) or 1+: Display the next range in line. - if (StepNo < RangeCount) + if (data->StepNo < data->Ranges.Size) { - int already_submitted = DisplayEnd; - DisplayStart = ImMax(RangeStart[StepNo], already_submitted); - DisplayEnd = ImMin(RangeEnd[StepNo], ItemsCount); - - // Seek cursor - if (DisplayStart > already_submitted) - SetCursorPosYAndSetupForPrevLine(StartPosY + (DisplayStart - ItemsFrozen) * ItemsHeight, ItemsHeight); - - StepNo++; + DisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted); + DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, ItemsCount); + if (DisplayStart > already_submitted) //-V1051 + ImGuiListClipper_SeekCursorForItem(this, DisplayStart); + data->StepNo++; return true; } // After the last step: Let the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), // Advance the cursor to the end of the list and then returns 'false' to end the loop. if (ItemsCount < INT_MAX) - SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); // advance cursor + ImGuiListClipper_SeekCursorForItem(this, ItemsCount); ItemsCount = -1; return false; @@ -4336,6 +4339,8 @@ void ImGui::Shutdown(ImGuiContext* context) g.CurrentTabBarStack.clear(); g.ShrinkWidthBuffer.clear(); + g.ClipperTempData.clear_destruct(); + g.Tables.Clear(); g.TablesTempData.clear_destruct(); g.DrawChannelsTempMergeBuffer.clear(); @@ -9605,6 +9610,7 @@ void ImGui::NavUpdateCreateMoveRequest() if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && (IsNavInputTest(ImGuiNavInput_DpadDown, read_mode) || IsNavInputTest(ImGuiNavInput_KeyDown_, read_mode))) { g.NavMoveDir = ImGuiDir_Down; } } g.NavMoveClipDir = g.NavMoveDir; + g.NavScoringNoClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); } // Update PageUp/PageDown/Home/End scroll @@ -9613,6 +9619,11 @@ void ImGui::NavUpdateCreateMoveRequest() float scoring_rect_offset_y = 0.0f; if (window && g.NavMoveDir == ImGuiDir_None && nav_keyboard_active) scoring_rect_offset_y = NavUpdatePageUpPageDown(); + if (scoring_rect_offset_y != 0.0f) + { + g.NavScoringNoClipRect = window->InnerRect; + g.NavScoringNoClipRect.TranslateY(scoring_rect_offset_y); + } // [DEBUG] Always send a request #if IMGUI_DEBUG_NAV_SCORING @@ -9666,8 +9677,10 @@ void ImGui::NavUpdateCreateMoveRequest() scoring_rect.Max.x = scoring_rect.Min.x; IM_ASSERT(!scoring_rect.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem(). //GetForegroundDrawList()->AddRect(scoring_rect.Min, scoring_rect.Max, IM_COL32(255,200,0,255)); // [DEBUG] + //if (!g.NavScoringNoClipRect.IsInverted()) { GetForegroundDrawList()->AddRect(g.NavScoringNoClipRect.Min, g.NavScoringNoClipRect.Max, IM_COL32(255, 200, 0, 255)); } // [DEBUG] } g.NavScoringRect = scoring_rect; + g.NavScoringNoClipRect.Add(scoring_rect); } // Apply result from previous frame navigation directional move request. Always called from NavUpdate() diff --git a/imgui.h b/imgui.h index 132375e6..cfc813e8 100644 --- a/imgui.h +++ b/imgui.h @@ -2172,52 +2172,45 @@ struct ImGuiStorage }; // Helper: Manually clip large list of items. -// If you are submitting lots of evenly spaced items and you have a random access to the list, you can perform coarse -// clipping based on visibility to save yourself from processing those items at all. +// If you have lots evenly spaced items and you have a random access to the list, you can perform coarse +// clipping based on visibility to only submit items that are in view. // The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped. -// (Dear ImGui already clip items based on their bounds but it needs to measure text size to do so, whereas manual coarse clipping before submission makes this cost and your own data fetching/submission cost almost null) +// (Dear ImGui already clip items based on their bounds but: it needs to first layout the item to do so, and generally +// fetching/submitting your own data incurs additional cost. Coarse clipping using ImGuiListClipper allows you to easily +// scale using lists with tens of thousands of items without a problem) // Usage: // ImGuiListClipper clipper; // clipper.Begin(1000); // We have 1000 elements, evenly spaced. -// clipper.ForceDisplay(42); // Optional, force element with given index to be displayed (use f.e. if you need to update a tooltip for a drag&drop source) // while (clipper.Step()) // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) // ImGui::Text("line number %d", i); // Generally what happens is: // - Clipper lets you process the first element (DisplayStart = 0, DisplayEnd = 1) regardless of it being visible or not. -// - User code submit one element. +// - User code submit that one element. // - Clipper can measure the height of the first element // - Clipper calculate the actual range of elements to display based on the current clipping rectangle, position the cursor before the first visible element. // - User code submit visible elements. +// - The clipper also handles various subtleties related to keyboard/gamepad navigation, wrapping etc. struct ImGuiListClipper { - int DisplayStart; - int DisplayEnd; - - // [Internal] - int ItemsCount; - int RangeStart[4]; // 1 for the user, rest for internal use - int RangeEnd[4]; - int RangeCount; - int YRangeMin[1]; - int YRangeMax[1]; - int YRangeCount; - int StepNo; - int ItemsFrozen; - float ItemsHeight; - float StartPosY; - - IMGUI_API ImGuiListClipper(); - IMGUI_API ~ImGuiListClipper(); + int DisplayStart; // First item to display, updated by each call to Step() + int DisplayEnd; // End of items to display (exclusive) + int ItemsCount; // [Internal] Number of items + float ItemsHeight; // [Internal] Height of item after a first step and item submission can calculate it + float StartPosY; // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed + void* TempData; // [Internal] Internal data // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step) // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). - IMGUI_API void Begin(int items_count, float items_height = -1.0f); // Automatically called by constructor if you passed 'items_count' or by Step() in Step 1. - IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. - IMGUI_API void ForceDisplayRange(int item_start, int item_end); // Optionally call before the first call to Step() if you need a range of items to be displayed regardless of visibility. - inline void ForceDisplay(int item_start, int item_count = 1) { ForceDisplayRange(item_start, item_start + item_count); } // Like ForceDisplayRange, but with a number instead of an end index. - IMGUI_API void ForceDisplayYRange(float y_min, float y_max); // Like ForceDisplayRange, but with y coordinates instead of item indices. - IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. + IMGUI_API ImGuiListClipper(); + IMGUI_API ~ImGuiListClipper(); + IMGUI_API void Begin(int items_count, float items_height = -1.0f); + IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. + IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. + + // Call ForceDisplayRangeXXX functions before first call to Step() if you need a range of items to be displayed regardless of visibility. + IMGUI_API void ForceDisplayRangeByIndices(int item_min, int item_max); // item_max is exclusive e.g. use (42, 42+1) to make item 42 always visible BUT due to alignment/padding of certain items it is likely that an extra item may be included on either end of the display range. + IMGUI_API void ForceDisplayRangeByPositions(float y_min, float y_max); // Absolute coordinates #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline ImGuiListClipper(int items_count, float items_height = -1.0f) { memset(this, 0, sizeof(*this)); ItemsCount = -1; Begin(items_count, items_height); } // [removed in 1.79] diff --git a/imgui_internal.h b/imgui_internal.h index 3373db06..cf1f1b37 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -18,6 +18,7 @@ Index of this file: // [SECTION] Generic helpers // [SECTION] ImDrawList support // [SECTION] Widgets support: flags, enums, data structures +// [SECTION] Clipper support // [SECTION] Navigation support // [SECTION] Columns support // [SECTION] Multi-select support @@ -1162,6 +1163,34 @@ struct ImGuiPtrOrIndex ImGuiPtrOrIndex(int index) { Ptr = NULL; Index = index; } }; +//----------------------------------------------------------------------------- +// [SECTION] Clipper support +//----------------------------------------------------------------------------- + +struct ImGuiListClipperRange +{ + int Min; + int Max; + bool PosToIndexConvert; // Begin/End are absolute position (will be converted to indices later) + ImS8 PosToIndexOffsetMin; // Add to Min after converting to indices + ImS8 PosToIndexOffsetMax; // Add to Min after converting to indices + + static ImGuiListClipperRange FromIndices(int min, int max) { ImGuiListClipperRange r = { min, max, false, 0, 0 }; return r; } + static ImGuiListClipperRange FromPositions(float y1, float y2, int off_min, int off_max) { ImGuiListClipperRange r = { (int)y1, (int)y2, true, (ImS8)off_min, (ImS8)off_max }; return r; } +}; + +// Temporary clipper data, buffers shared/reused between instances +struct ImGuiListClipperData +{ + ImGuiListClipper* ListClipper; + int StepNo; + int ItemsFrozen; + ImVector Ranges; + + ImGuiListClipperData() { memset(this, 0, sizeof(*this)); } + void Reset(ImGuiListClipper* clipper) { ListClipper = clipper; StepNo = ItemsFrozen = 0; Ranges.resize(0); } +}; + //----------------------------------------------------------------------------- // [SECTION] Navigation support //----------------------------------------------------------------------------- @@ -1583,6 +1612,7 @@ struct ImGuiContext ImGuiDir NavMoveDirForDebug; ImGuiDir NavMoveClipDir; // FIXME-NAV: Describe the purpose of this better. Might want to rename? ImRect NavScoringRect; // Rectangle used for scoring, in screen space. Based of window->NavRectRel[], modified for directional navigation scoring. + ImRect NavScoringNoClipRect; // Some nav operations (such as PageUp/PageDown) enforce a region which clipper will attempt to always keep submitted int NavScoringDebugCount; // Metrics for debugging int NavTabbingInputableRemaining; // >0 when counting items for tabbing ImGuiNavItemData NavMoveResultLocal; // Best move request candidate within NavWindow @@ -1627,6 +1657,10 @@ struct ImGuiContext ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly, ImGuiPayload only holds pointer+size unsigned char DragDropPayloadBufLocal[16]; // Local buffer for small payloads + // Clipper + int ClipperTempDataStacked; + ImVector ClipperTempData; + // Table ImGuiTable* CurrentTable; int TablesTempDataStacked; // Temporary table data size (because we leave previous instances undestructed, we generally don't use TablesTempData.Size) @@ -1814,6 +1848,8 @@ struct ImGuiContext DragDropHoldJustPressedId = 0; memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + ClipperTempDataStacked = 0; + CurrentTable = NULL; TablesTempDataStacked = 0; CurrentTabBar = NULL; From 6a7e2c74fb079fe21953307c48447b880f1437d0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 4 Nov 2021 13:55:37 +0100 Subject: [PATCH 4/6] Clipper: remove ForceDisplayRangeByIndices/ForceDisplayRangeByPositions functions until we find a need for them, since #3841 is now solved automatically. --- docs/TODO.txt | 2 +- imgui.cpp | 18 ------------------ imgui.h | 4 ---- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/docs/TODO.txt b/docs/TODO.txt index 95a9326f..27b6f1f3 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -142,7 +142,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - plot: option/feature: draw unit - plot: add a helper e.g. Plot(char* label, float value, float time_span=2.0f) that stores values and Plot them for you - probably another function name. and/or automatically allow to plot ANY displayed value (more reliance on stable ID) - - clipper: ability to force display 1 item in the list would be convenient (for patterns where we need to set active id etc.) + - clipper: ability to force display 1 item in the list would be convenient (for patterns where we need to set active id etc.) (#3841) (can resurrect ForceDisplayRangeXXX functions removed) - clipper: ability to disable the clipping through a simple flag/bool. - clipper: ability to run without knowing full count in advance. - clipper: horizontal clipping support. (#2580) diff --git a/imgui.cpp b/imgui.cpp index d7c7385e..7654e3bc 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2405,24 +2405,6 @@ void ImGuiListClipper::End() data->ListClipper->TempData = data; } -void ImGuiListClipper::ForceDisplayRangeByIndices(int item_min, int item_max) -{ - ImGuiListClipperData* data = (ImGuiListClipperData*)TempData; - IM_ASSERT(DisplayStart < 0); // Only allowed after Begin() and if there has not been a specified range yet. - IM_ASSERT(item_min <= item_max); - if (item_min < item_max) - data->Ranges.push_back(ImGuiListClipperRange::FromIndices(item_min, item_max)); -} - -void ImGuiListClipper::ForceDisplayRangeByPositions(float y_min, float y_max) -{ - ImGuiListClipperData* data = (ImGuiListClipperData*)TempData; - IM_ASSERT(DisplayStart < 0); // Only allowed after Begin() and if there has not been a specified range yet. - IM_ASSERT(y_min <= y_max); - if (y_min < y_max) - data->Ranges.push_back(ImGuiListClipperRange::FromPositions(y_min, y_max, 0, 0)); -} - bool ImGuiListClipper::Step() { ImGuiContext& g = *GImGui; diff --git a/imgui.h b/imgui.h index cfc813e8..cf3c3198 100644 --- a/imgui.h +++ b/imgui.h @@ -2208,10 +2208,6 @@ struct ImGuiListClipper IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. - // Call ForceDisplayRangeXXX functions before first call to Step() if you need a range of items to be displayed regardless of visibility. - IMGUI_API void ForceDisplayRangeByIndices(int item_min, int item_max); // item_max is exclusive e.g. use (42, 42+1) to make item 42 always visible BUT due to alignment/padding of certain items it is likely that an extra item may be included on either end of the display range. - IMGUI_API void ForceDisplayRangeByPositions(float y_min, float y_max); // Absolute coordinates - #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline ImGuiListClipper(int items_count, float items_height = -1.0f) { memset(this, 0, sizeof(*this)); ItemsCount = -1; Begin(items_count, items_height); } // [removed in 1.79] #endif From 64daeddf6f6ca877e2a4ed3b8f0eee926cc465fd Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 4 Nov 2021 14:14:16 +0100 Subject: [PATCH 5/6] Removed CalcListClipping() function. (#3841) --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 11 ++++++----- imgui.h | 5 +++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 83618619..defb517e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -36,6 +36,9 @@ HOW TO UPDATE? Breaking Changes: +- Removed CalcListClipping() function. Prefer using ImGuiListClipper which can return non-contiguous ranges. + Please open an issue if you think you really need this function. (#3841) + Other Changes: - Added IsMouseTripleClicked() function. Tracking multi-click count in IO structure. (#3229) [@kudaba] diff --git a/imgui.cpp b/imgui.cpp index 7654e3bc..f1c1b3c7 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -380,6 +380,7 @@ CODE When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2021/11/04 (1.86) - removed CalcListClipping() function. Prefer using ImGuiListClipper which can return non-contiguous ranges. Please open an issue if you think you really need this function. - 2021/08/23 (1.85) - removed GetWindowContentRegionWidth() function. keep inline redirection helper. can use 'GetWindowContentRegionMax().x - GetWindowContentRegionMin().x' instead for generally 'GetContentRegionAvail().x' is more useful. - 2021/07/26 (1.84) - commented out redirecting functions/enums names that were marked obsolete in 1.67 and 1.69 (March 2019): - ImGui::GetOverlayDrawList() -> use ImGui::GetForegroundDrawList() @@ -2245,11 +2246,10 @@ static bool GetSkipItemForListClipping() return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems); } -// Helper to calculate coarse clipping of large list of evenly sized items. -// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. -// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX -// FIXME: This legacy API is not ideal because it assume we will return a single contiguous rectangle. -// Prefer using ImGuiListClipper which returns disconnected ranges. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// Legacy helper to calculate coarse clipping of large list of evenly sized items. +// This legacy API is not ideal because it assume we will return a single contiguous rectangle. +// Prefer using ImGuiListClipper which can returns non-contiguous ranges. void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) { ImGuiContext& g = *GImGui; @@ -2290,6 +2290,7 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items *out_items_display_start = start; *out_items_display_end = end; } +#endif static void ImGuiListClipper_SortAndFuseRanges(ImVector& ranges, int offset = 0) { diff --git a/imgui.h b/imgui.h index cf3c3198..4ef88105 100644 --- a/imgui.h +++ b/imgui.h @@ -64,7 +64,7 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) #define IMGUI_VERSION "1.86 WIP" -#define IMGUI_VERSION_NUM 18505 +#define IMGUI_VERSION_NUM 18506 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE @@ -869,7 +869,6 @@ namespace ImGui IMGUI_API const char* GetStyleColorName(ImGuiCol idx); // get a string corresponding to the enum value (for display, saving, etc.). IMGUI_API void SetStateStorage(ImGuiStorage* storage); // replace current window storage with our own (if you want to manipulate it yourself, typically clear subsection of it) IMGUI_API ImGuiStorage* GetStateStorage(); - IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // calculate coarse clipping for large list of evenly sized items. Prefer using the ImGuiListClipper higher-level helper if you can. IMGUI_API bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags flags = 0); // helper to create a child window / scrolling region that looks like a normal widget frame IMGUI_API void EndChildFrame(); // always call EndChildFrame() regardless of BeginChildFrame() return values (which indicates a collapsed/clipped window) @@ -2829,6 +2828,8 @@ struct ImGuiViewport #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { + // OBSOLETED in 1.86 (from November 2021) + IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // Calculate coarse clipping for large list of evenly sized items. Prefer using ImGuiListClipper. // OBSOLETED in 1.85 (from August 2021) static inline float GetWindowContentRegionWidth() { return GetWindowContentRegionMax().x - GetWindowContentRegionMin().x; } // OBSOLETED in 1.81 (from February 2021) From 32779c5b648a7f9631c16de4cbd078fe2f234f99 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 4 Nov 2021 15:14:35 +0100 Subject: [PATCH 6/6] Clipper: hotfix (amend 93cccd2, was broken) (#3841, #1725) --- imgui.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f1c1b3c7..6a527121 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2357,7 +2357,7 @@ ImGuiListClipper::ImGuiListClipper() ImGuiListClipper::~ImGuiListClipper() { - IM_ASSERT(ItemsCount == -1 && "Forgot to call End(), or to Step() until false?"); + End(); } // Use case A: Begin() called from constructor with items_height<0, then called again from Step() in StepNo 1 @@ -2388,22 +2388,24 @@ void ImGuiListClipper::Begin(int items_count, float items_height) void ImGuiListClipper::End() { - ImGuiContext& g = *GImGui; - if (ItemsCount < 0) // Already ended - return; - // In theory here we should assert that we are already at the right position, but it seems saner to just seek at the end and not assert/crash the user. - ImGuiListClipperData* data = (ImGuiListClipperData*)TempData; - if (ItemsCount < INT_MAX && DisplayStart >= 0) + ImGuiContext& g = *GImGui; + if (ItemsCount >= 0 && ItemsCount < INT_MAX && DisplayStart >= 0) ImGuiListClipper_SeekCursorForItem(this, ItemsCount); ItemsCount = -1; - data->StepNo = data->Ranges.Size; // Restore temporary buffer and fix back pointers which may be invalidated when nesting - IM_ASSERT(g.ClipperTempDataStacked > 0); - data = (--g.ClipperTempDataStacked > 0) ? &g.ClipperTempData[g.ClipperTempDataStacked - 1] : NULL; - if (data) - data->ListClipper->TempData = data; + if (ImGuiListClipperData* data = (ImGuiListClipperData*)TempData) + { + IM_ASSERT(data->ListClipper == this); + data->StepNo = data->Ranges.Size; + if (--g.ClipperTempDataStacked > 0) + { + data = &g.ClipperTempData[g.ClipperTempDataStacked - 1]; + data->ListClipper->TempData = data; + } + TempData = NULL; + } } bool ImGuiListClipper::Step()