|
@@ -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;
|
|
|
- }
|
|
|
|
|
|
- // Reached end of list
|
|
|
- if (DisplayEnd >= ItemsCount)
|
|
|
- {
|
|
|
- End();
|
|
|
- return false;
|
|
|
+ calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards.
|
|
|
}
|
|
|
|
|
|
- // 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;
|
|
|
}
|
|
|
|