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

106
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 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 // 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. // 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; ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* window = g.CurrentWindow;
StartPosY = window->DC.CursorPos.y; StartPosY = window->DC.CursorPos.y;
ItemsHeight = items_height; ItemsHeight = items_height;
ItemsCount = count; ItemsCount = items_count;
StepNo = 0; StepNo = 0;
DisplayEnd = DisplayStart = -1; DisplayStart = -1;
if (ItemsHeight > 0.0f) DisplayEnd = 0;
{
ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display
if (DisplayStart > 0)
SetCursorPosYAndSetupForPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor
StepNo = 2;
}
} }
void ImGuiListClipper::End() void ImGuiListClipper::End()
{ {
if (ItemsCount < 0) if (ItemsCount < 0) // Already ended
return; 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 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) if (ItemsCount < INT_MAX && DisplayStart >= 0)
SetCursorPosYAndSetupForPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor SetCursorPosYAndSetupForPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight);
ItemsCount = -1; ItemsCount = -1;
StepNo = 3; StepNo = 3;
} }
@ -2230,38 +2239,70 @@ bool ImGuiListClipper::Step()
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* window = g.CurrentWindow;
if (ItemsCount == 0 || window->SkipItems) // Reached end of list
if (DisplayEnd >= ItemsCount || window->SkipItems)
{ {
ItemsCount = -1; End();
return false; 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.
// 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; DisplayStart = 0;
DisplayEnd = 1; DisplayEnd = 1;
StartPosY = window->DC.CursorPos.y;
StepNo = 1; StepNo = 1;
return true; 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.
// 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)
{ {
if (ItemsCount == 1) { ItemsCount = -1; return false; } IM_ASSERT(ItemsHeight <= 0.0f);
float items_height = window->DC.CursorPos.y - StartPosY; ItemsHeight = window->DC.CursorPos.y - StartPosY;
IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically IM_ASSERT(ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!");
Begin(ItemsCount - 1, items_height); StepNo = 2;
DisplayStart++; }
DisplayEnd++;
// 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; StepNo = 3;
return true; 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.
// 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)
{ {
IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0); // Seek cursor
StepNo = 3; if (ItemsCount < INT_MAX)
return true; SetCursorPosYAndSetupForPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor
ItemsCount = -1;
return false;
} }
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; return false;
} }
@ -10439,7 +10480,8 @@ void ImGui::ShowMetricsWindow(bool* p_open)
NodeDrawCmdShowMeshAndBoundingBox(window, draw_list, pcmd, elem_offset, true, false); NodeDrawCmdShowMeshAndBoundingBox(window, draw_list, pcmd, elem_offset, true, false);
// Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. // 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()) while (clipper.Step())
for (int prim = clipper.DisplayStart, idx_i = elem_offset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++) for (int prim = clipper.DisplayStart, idx_i = elem_offset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++)
{ {

View File

@ -1900,7 +1900,8 @@ struct ImGuiStorage
// - 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. // - 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.
struct ImGuiListClipper struct ImGuiListClipper
{ {
int DisplayStart, DisplayEnd; int DisplayStart;
int DisplayEnd;
int ItemsCount; int ItemsCount;
// [Internal] // [Internal]
@ -1911,12 +1912,12 @@ struct ImGuiListClipper
// items_count: Use -1 to ignore (you can call Begin later). 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_count: Use -1 to ignore (you can call Begin later). 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(). // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing().
// If you don't specify an items_height, you NEED to call Step(). If you specify items_height you may call the old Begin()/End() api directly, but prefer calling Step(). // If you don't specify an items_height, you NEED to call Step(). If you specify items_height you may call the old Begin()/End() api directly, but prefer calling Step().
ImGuiListClipper(int items_count = -1, float items_height = -1.0f) { Begin(items_count, items_height); } // NB: Begin() initialize every fields (as we allow user to call Begin/End multiple times on a same instance if they want). ImGuiListClipper(int items_count = -1, float items_height = -1.0f);
~ImGuiListClipper() { IM_ASSERT(ItemsCount == -1); } // Assert if user forgot to call End() or Step() until false. ~ImGuiListClipper();
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 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 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 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.
}; };
// Helpers macros to generate 32-bit encoded colors // Helpers macros to generate 32-bit encoded colors

View File

@ -3340,7 +3340,8 @@ static void ShowDemoWindowColumns()
ImGui::BeginChild("##ScrollingRegion", child_size, false, ImGuiWindowFlags_HorizontalScrollbar); ImGui::BeginChild("##ScrollingRegion", child_size, false, ImGuiWindowFlags_HorizontalScrollbar);
ImGui::Columns(10); ImGui::Columns(10);
int ITEMS_COUNT = 2000; int ITEMS_COUNT = 2000;
ImGuiListClipper clipper(ITEMS_COUNT); // Also demonstrate using the clipper for large list ImGuiListClipper clipper; // Also demonstrate using the clipper for large list
clipper.Begin(ITEMS_COUNT);
while (clipper.Step()) while (clipper.Step())
{ {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
@ -4330,7 +4331,8 @@ struct ExampleAppConsole
// To use the clipper we can replace your standard loop: // To use the clipper we can replace your standard loop:
// for (int i = 0; i < Items.Size; i++) // for (int i = 0; i < Items.Size; i++)
// With: // With:
// ImGuiListClipper clipper(Items.Size); // ImGuiListClipper clipper;
// clipper.Begin(Items.Size);
// while (clipper.Step()) // while (clipper.Step())
// for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
// - That your items are evenly spaced (same height) // - That your items are evenly spaced (same height)
@ -4897,7 +4899,8 @@ static void ShowExampleAppLongText(bool* p_open)
{ {
// Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper.
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
ImGuiListClipper clipper(lines); ImGuiListClipper clipper;
clipper.Begin(lines);
while (clipper.Step()) while (clipper.Step())
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); ImGui::Text("%i The quick brown fox jumps over the lazy dog", i);

View File

@ -6154,7 +6154,8 @@ bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(v
// Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper. // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
bool value_changed = false; bool value_changed = false;
ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. ImGuiListClipper clipper;
clipper.Begin(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
while (clipper.Step()) while (clipper.Step())
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
{ {