mirror of
				https://github.com/Drezil/imgui.git
				synced 2025-11-03 22:51:06 +01:00 
			
		
		
		
	Clipper: enhanced ImGuiListClipper (#3841)
This commit is contained in:
		
				
					committed by
					
						
						ocornut
					
				
			
			
				
	
			
			
			
						parent
						
							b409df34db
						
					
				
				
					commit
					cd1b5f7883
				
			
							
								
								
									
										153
									
								
								imgui.cpp
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user