ImGuiListClipper: internal rework and tidying up to facilitate supporting frozen rows in tables + stop promoting using constructors parameters.

This commit is contained in:
ocornut
2020-09-24 18:08:01 +02:00
committed by omar
parent 324e0310ad
commit 52c0b1a340
4 changed files with 99 additions and 52 deletions

130
imgui.cpp
View File

@ -2192,35 +2192,44 @@ static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height)
columns->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly
}
// Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1
ImGuiListClipper::ImGuiListClipper(int items_count, float items_height)
{
DisplayStart = DisplayEnd = 0;
ItemsCount = -1;
StepNo = 0;
ItemsHeight = StartPosY = 0.0f;
Begin(items_count, items_height);
}
ImGuiListClipper::~ImGuiListClipper()
{
IM_ASSERT(ItemsCount == -1 && "Forgot to call End(), or to Step() until false?");
}
// Use case A: Begin() called from constructor with items_height<0, then called again from Step() in StepNo 1
// Use case B: Begin() called from constructor with items_height>0
// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style.
void ImGuiListClipper::Begin(int count, float items_height)
void ImGuiListClipper::Begin(int items_count, float items_height)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
StartPosY = window->DC.CursorPos.y;
ItemsHeight = items_height;
ItemsCount = count;
ItemsCount = items_count;
StepNo = 0;
DisplayEnd = DisplayStart = -1;
if (ItemsHeight > 0.0f)
{
ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display
if (DisplayStart > 0)
SetCursorPosYAndSetupForPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor
StepNo = 2;
}
DisplayStart = -1;
DisplayEnd = 0;
}
void ImGuiListClipper::End()
{
if (ItemsCount < 0)
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.
if (ItemsCount < INT_MAX)
SetCursorPosYAndSetupForPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor
if (ItemsCount < INT_MAX && DisplayStart >= 0)
SetCursorPosYAndSetupForPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight);
ItemsCount = -1;
StepNo = 3;
}
@ -2230,38 +2239,70 @@ bool ImGuiListClipper::Step()
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
if (ItemsCount == 0 || window->SkipItems)
// Reached end of list
if (DisplayEnd >= ItemsCount || window->SkipItems)
{
End();
return 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)
{
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 = 0;
DisplayEnd = 1;
StepNo = 1;
return true;
}
// Already has item height (given by user in Begin): skip to calculating step
DisplayStart = DisplayEnd;
StepNo = 2;
}
// Step 1: the clipper infer height from first element
if (StepNo == 1)
{
IM_ASSERT(ItemsHeight <= 0.0f);
ItemsHeight = window->DC.CursorPos.y - StartPosY;
IM_ASSERT(ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!");
StepNo = 2;
}
// Step 2: calculate the actual range of elements to display, and position the cursor before the first element
if (StepNo == 2)
{
IM_ASSERT(ItemsHeight > 0.0f);
int already_submitted = DisplayEnd;
ImGui::CalcListClipping(ItemsCount - already_submitted, ItemsHeight, &DisplayStart, &DisplayEnd);
DisplayStart += already_submitted;
DisplayEnd += already_submitted;
// Seek cursor
if (DisplayStart > already_submitted)
SetCursorPosYAndSetupForPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight);
StepNo = 3;
return true;
}
// Step 3: 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 * ItemsHeight, ItemsHeight); // advance cursor
ItemsCount = -1;
return false;
}
if (StepNo == 0) // Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height.
{
DisplayStart = 0;
DisplayEnd = 1;
StartPosY = window->DC.CursorPos.y;
StepNo = 1;
return true;
}
if (StepNo == 1) // Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element.
{
if (ItemsCount == 1) { ItemsCount = -1; return false; }
float items_height = window->DC.CursorPos.y - StartPosY;
IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically
Begin(ItemsCount - 1, items_height);
DisplayStart++;
DisplayEnd++;
StepNo = 3;
return true;
}
if (StepNo == 2) // Step 2: empty step only required if an explicit items_height was passed to constructor or Begin() and user still call Step(). Does nothing and switch to Step 3.
{
IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0);
StepNo = 3;
return true;
}
if (StepNo == 3) // Step 3: 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.
End();
IM_ASSERT(0);
return false;
}
@ -8062,7 +8103,7 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s
// For tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible.
if (policy == ImGuiPopupPositionPolicy_Tooltip)
return ref_pos + ImVec2(2, 2);
return ref_pos + ImVec2(2, 2);
// Otherwise try to keep within display
ImVec2 pos = ref_pos;
@ -10439,7 +10480,8 @@ void ImGui::ShowMetricsWindow(bool* p_open)
NodeDrawCmdShowMeshAndBoundingBox(window, draw_list, pcmd, elem_offset, true, false);
// Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted.
ImGuiListClipper clipper(pcmd->ElemCount / 3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible.
ImGuiListClipper clipper;
clipper.Begin(pcmd->ElemCount / 3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible.
while (clipper.Step())
for (int prim = clipper.DisplayStart, idx_i = elem_offset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++)
{