MultiSelect: ImGuiSelectionBasicStorage: rework to accept massive selections requests without flinching.
Batch modification + storage only keeps selected items.
This commit is contained in:
parent
e1fd25051e
commit
e61612a687
4
imgui.h
4
imgui.h
@ -2835,7 +2835,7 @@ struct ImGuiSelectionBasicStorage
|
|||||||
{
|
{
|
||||||
// Members
|
// Members
|
||||||
ImGuiStorage _Storage; // [Internal] Selection set. Think of this as similar to e.g. std::set<ImGuiID>. Prefer not accessing directly: iterate with GetNextSelectedItem().
|
ImGuiStorage _Storage; // [Internal] Selection set. Think of this as similar to e.g. std::set<ImGuiID>. Prefer not accessing directly: iterate with GetNextSelectedItem().
|
||||||
int Size; // Number of selected items (== number of 1 in the Storage), maintained by this helper.
|
int Size; // Number of selected items, maintained by this helper.
|
||||||
void* UserData; // User data for use by adapter function // e.g. selection.UserData = (void*)my_items;
|
void* UserData; // User data for use by adapter function // e.g. selection.UserData = (void*)my_items;
|
||||||
ImGuiID (*AdapterIndexToStorageId)(ImGuiSelectionBasicStorage* self, int idx); // e.g. selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { return ((MyItems**)self->UserData)[idx]->ID; };
|
ImGuiID (*AdapterIndexToStorageId)(ImGuiSelectionBasicStorage* self, int idx); // e.g. selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { return ((MyItems**)self->UserData)[idx]->ID; };
|
||||||
|
|
||||||
@ -2849,7 +2849,7 @@ struct ImGuiSelectionBasicStorage
|
|||||||
ImGuiSelectionBasicStorage() { Size = 0; UserData = NULL; AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; }; }
|
ImGuiSelectionBasicStorage() { Size = 0; UserData = NULL; AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; }; }
|
||||||
void Clear() { _Storage.Data.resize(0); Size = 0; }
|
void Clear() { _Storage.Data.resize(0); Size = 0; }
|
||||||
void Swap(ImGuiSelectionBasicStorage& r) { _Storage.Data.swap(r._Storage.Data); int lhs_size = Size; Size = r.Size; r.Size = lhs_size; }
|
void Swap(ImGuiSelectionBasicStorage& r) { _Storage.Data.swap(r._Storage.Data); int lhs_size = Size; Size = r.Size; r.Size = lhs_size; }
|
||||||
void SetItemSelected(ImGuiID id, bool v) { int* p_int = _Storage.GetIntRef(id, 0); if (v && *p_int == 0) { *p_int = 1; Size++; } else if (!v && *p_int != 0) { *p_int = 0; Size--; } }
|
IMGUI_API void SetItemSelected(ImGuiID id, bool selected);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optional helper to apply multi-selection requests to existing randomly accessible storage.
|
// Optional helper to apply multi-selection requests to existing randomly accessible storage.
|
||||||
|
@ -7830,14 +7830,65 @@ ImGuiID ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it)
|
|||||||
if (it == NULL)
|
if (it == NULL)
|
||||||
it = _Storage.Data.Data;
|
it = _Storage.Data.Data;
|
||||||
IM_ASSERT(it >= _Storage.Data.Data && it <= it_end);
|
IM_ASSERT(it >= _Storage.Data.Data && it <= it_end);
|
||||||
if (it != it_end)
|
|
||||||
while (it->val_i == 0 && it < it_end)
|
|
||||||
it++;
|
|
||||||
const bool has_more = (it != it_end);
|
const bool has_more = (it != it_end);
|
||||||
*opaque_it = has_more ? (void**)(it + 1) : (void**)(it);
|
*opaque_it = has_more ? (void**)(it + 1) : (void**)(it);
|
||||||
return has_more ? it->key : 0;
|
return has_more ? it->key : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Basic function to update selection (for very large amounts of changes, see what ApplyRequests is doing)
|
||||||
|
void ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected)
|
||||||
|
{
|
||||||
|
ImGuiStoragePair* it = ImLowerBound(_Storage.Data.Data, _Storage.Data.Data + _Storage.Data.Size, id);
|
||||||
|
if (selected == (it != _Storage.Data.Data + _Storage.Data.Size) && (it->key == id))
|
||||||
|
return;
|
||||||
|
if (selected)
|
||||||
|
_Storage.Data.insert(it, ImGuiStoragePair(id, 1));
|
||||||
|
else
|
||||||
|
_Storage.Data.erase(it);
|
||||||
|
Size = _Storage.Data.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimized for batch edits (with same value of 'selected')
|
||||||
|
static void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicStorage* selection, ImGuiID id, bool selected, int size_before_amends)
|
||||||
|
{
|
||||||
|
ImGuiStorage* storage = &selection->_Storage;
|
||||||
|
ImGuiStoragePair* it = ImLowerBound(storage->Data.Data, storage->Data.Data + size_before_amends, id);
|
||||||
|
if (selected == (it != storage->Data.Data + size_before_amends) && (it->key == id))
|
||||||
|
return;
|
||||||
|
if (selected)
|
||||||
|
storage->Data.push_back(ImGuiStoragePair(id, 1)); // Push unsorted at end of vector, will be sorted in SelectionMultiAmendsFinish()
|
||||||
|
else
|
||||||
|
it->val_i = 0; // Clear in-place, will be removed in SelectionMultiAmendsFinish()
|
||||||
|
selection->Size += selected ? +1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGuiSelectionBasicStorage_Compact(ImGuiSelectionBasicStorage* selection)
|
||||||
|
{
|
||||||
|
ImGuiStorage* storage = &selection->_Storage;
|
||||||
|
ImGuiStoragePair* p_out = storage->Data.Data;
|
||||||
|
ImGuiStoragePair* p_end = storage->Data.Data + storage->Data.Size;
|
||||||
|
for (ImGuiStoragePair* p_in = p_out; p_in < p_end; p_in++)
|
||||||
|
if (p_in->val_i != 0)
|
||||||
|
{
|
||||||
|
if (p_out != p_in)
|
||||||
|
*p_out = *p_in;
|
||||||
|
p_out++;
|
||||||
|
}
|
||||||
|
storage->Data.Size = (int)(p_out - storage->Data.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends)
|
||||||
|
{
|
||||||
|
ImGuiStorage* storage = &selection->_Storage;
|
||||||
|
if (selection->Size == size_before_amends)
|
||||||
|
return;
|
||||||
|
if (selected)
|
||||||
|
storage->BuildSortByKey(); // When done selecting: sort everything
|
||||||
|
else
|
||||||
|
ImGuiSelectionBasicStorage_Compact(selection); // When done unselecting: compact by removing all zero values (might be done lazily when iterating selection?)
|
||||||
|
IM_ASSERT(selection->Size == storage->Data.Size);
|
||||||
|
}
|
||||||
|
|
||||||
// Apply requests coming from BeginMultiSelect() and EndMultiSelect().
|
// Apply requests coming from BeginMultiSelect() and EndMultiSelect().
|
||||||
// - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.
|
// - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.
|
||||||
// - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem.
|
// - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem.
|
||||||
@ -7862,6 +7913,10 @@ void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
|
|||||||
IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!");
|
IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!");
|
||||||
IM_ASSERT(AdapterIndexToStorageId != NULL);
|
IM_ASSERT(AdapterIndexToStorageId != NULL);
|
||||||
|
|
||||||
|
// This is optimized/specialized to cope nicely with very large selections (e.g. 1 million items)
|
||||||
|
// - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish().
|
||||||
|
// - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass.
|
||||||
|
// - (A more optimal version wouldn't even use ImGuiStorage but directly a ImVector<ImGuiID> to reduce bandwidth, but this is a reasonable trade off to reuse code)
|
||||||
for (ImGuiSelectionRequest& req : ms_io->Requests)
|
for (ImGuiSelectionRequest& req : ms_io->Requests)
|
||||||
{
|
{
|
||||||
if (req.Type == ImGuiSelectionRequestType_SetAll)
|
if (req.Type == ImGuiSelectionRequestType_SetAll)
|
||||||
@ -7870,13 +7925,19 @@ void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
|
|||||||
if (req.Selected)
|
if (req.Selected)
|
||||||
{
|
{
|
||||||
_Storage.Data.reserve(ms_io->ItemsCount);
|
_Storage.Data.reserve(ms_io->ItemsCount);
|
||||||
|
const int size_before_amends = _Storage.Data.Size;
|
||||||
for (int idx = 0; idx < ms_io->ItemsCount; idx++)
|
for (int idx = 0; idx < ms_io->ItemsCount; idx++)
|
||||||
SetItemSelected(GetStorageIdFromIndex(idx), true);
|
ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends);
|
||||||
|
ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (req.Type == ImGuiSelectionRequestType_SetRange)
|
else if (req.Type == ImGuiSelectionRequestType_SetRange)
|
||||||
|
{
|
||||||
|
const int size_before_amends = _Storage.Data.Size;
|
||||||
for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
|
for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
|
||||||
SetItemSelected(GetStorageIdFromIndex(idx), req.Selected);
|
ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends);
|
||||||
|
ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user