RangeSelect/MultiSelect: Demo: rework ExampleSelection with an ExampleSelectionAdapter layer, allowing to share more code accross examples using different storage systems.

Not ideal way to showcase this demo but this is really more flexible.
features/range_select
ocornut ago%!(EXTRA string=2 years)
parent 157efbb7ad
commit 1fdb880118
  1. 101
      imgui_demo.cpp

@ -2741,19 +2741,41 @@ static void ShowDemoWindowWidgets()
} }
} }
// [Advanced] Helper class to simulate storage of a multi-selection state, used by the BeginMultiSelect() demos. // We use a little extra indirection layer here in order to use and demonstrate different ways to
// We use ImGuiStorage (simple key->value storage) to avoid external dependencies but it's probably not optimal. // - identify items in the multi-selection system (using index? using identifiers? using pointers?)
// - store our persistent selection data (using index? using identifiers? using pointers?)
// Many combinations are possible depending on how you prefer to store your items and how you prefer to store your selection.
// WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ExampleSelectionAdapter' INDIRECTION LOGIC.
// In theory we could add a IndexToUserData() function which would be used when calling SetNextItemSelectionUserData(), but omitting it makes things clearer.
struct ExampleSelectionAdapter
{
void* Data = NULL;
int (*UserDataToIndex)(ExampleSelectionAdapter* self, ImGuiSelectionUserData item_data); // Function to convert item ImGuiSelectionUserData to item index
ImGuiID (*IndexToStorage)(ExampleSelectionAdapter* self, int idx); // Function to convert item index to data stored in persistent selection
ExampleSelectionAdapter() { SetupForDirectIndexes(); }
// Example for the simplest case: UserData==Index==SelectionStorage (this adapter doesn't even need to use the item data field)
void SetupForDirectIndexes()
{
UserDataToIndex = [](ExampleSelectionAdapter*, ImGuiSelectionUserData item_data) { return (int)item_data; }; // No transform: Pass indices to SetNextItemSelectionUserData()
IndexToStorage = [](ExampleSelectionAdapter*, int idx) { return (ImGuiID)idx; }; // No transform: Store indices inside persistent selection storage
}
};
// [Advanced] Helper class to store multi-selection state, used by the BeginMultiSelect() demos.
// Provide an abstraction layer for the purpose of the demo showcasing different forms of underlying selection data.
// To store a single-selection: // To store a single-selection:
// - You only need a single variable and don't need any of this! // - You only need a single variable and don't need any of this!
// To store a multi-selection, in your real application you could: // To store a multi-selection, in your real application you could:
// - Use external storage: e.g. unordered_set/set/hash/map/interval trees (storing indices, objects id, etc.) // - A) Use external storage: e.g. std::set<index>, std::set<MyObjectId>, std::vector<MyObjectId>, interval trees, etc.
// are generally appropriate. Even a large array of bool might work for you... This is what we are doing here. // are generally appropriate. Even a large array of bool might work for you...
// - Use intrusively stored selection (e.g. 'bool IsSelected' inside your object). // This code here use ImGuiStorage (a simple key->value storage) as a std::set replacement to avoid external dependencies.
// - This is simple, but it means you cannot have multiple simultaneous views over your objects. // - B) Or use intrusively stored selection (e.g. 'bool IsSelected' inside your object).
// - This is what many of the simpler demos in other sections of this file are using (so they are not using this class). // - It is simple, but it means you cannot have multiple simultaneous views over your objects.
// - Some of our features requires you to provide the selection size, which with this specific strategy require additional work: // - Some of our features requires you to provide the selection _size_, which with this specific strategy require additional work:
// either you maintain it (which requires storage outside of objects) either you recompute (which may be costly for large sets). // either you maintain it (which requires storage outside of objects) either you recompute (which may be costly for large sets).
// - So I would suggest that using intrusive selection for multi-select is not the most adequate. // - So we suggest using intrusive selection for multi-select is not really adequate.
struct ExampleSelection struct ExampleSelection
{ {
// Data // Data
@ -2764,6 +2786,7 @@ struct ExampleSelection
// Functions // Functions
ExampleSelection() { Clear(); } ExampleSelection() { Clear(); }
void Clear() { Storage.Clear(); Size = 0; QueueDeletion = false; } void Clear() { Storage.Clear(); Size = 0; QueueDeletion = false; }
void Swap(ExampleSelection& rhs) { Storage.Data.swap(rhs.Storage.Data); }
bool Contains(int n) const { return Storage.GetInt((ImGuiID)n, 0) != 0; } bool Contains(int n) const { return Storage.GetInt((ImGuiID)n, 0) != 0; }
void AddItem(int n) { int* p_int = Storage.GetIntRef((ImGuiID)n, 0); if (*p_int != 0) return; *p_int = 1; Size++; } void AddItem(int n) { int* p_int = Storage.GetIntRef((ImGuiID)n, 0); if (*p_int != 0) return; *p_int = 1; Size++; }
void RemoveItem(int n) { int* p_int = Storage.GetIntRef((ImGuiID)n, 0); if (*p_int == 0) return; *p_int = 0; Size--; } void RemoveItem(int n) { int* p_int = Storage.GetIntRef((ImGuiID)n, 0); if (*p_int == 0) return; *p_int = 0; Size--; }
@ -2776,15 +2799,33 @@ struct ExampleSelection
// - Honoring RequestSetRange requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem. // - Honoring RequestSetRange requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem.
// - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection. // - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection.
// - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform // - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform
// a lookup and have some way to iterate between two values. // a lookup in order to have some way to iterate/interpolate between two items.
// - A full-featured application is likely to allow search/filtering which is likely to lead to using indices and // - A full-featured application is likely to allow search/filtering which is likely to lead to using indices
// constructing a view index <> object id/ptr data structure. (FIXME-MULTISELECT: Would be worth providing a demo of doing this). // and constructing a view index <> object id/ptr data structure anyway.
// - (Our implementation is slightly inefficient because it doesn't take advantage of the fat that ImguiStorage stores sorted key) // WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ExampleSelectionAdapter' INDIRECTION LOGIC.
void ApplyRequests(ImGuiMultiSelectIO* ms_io, int items_count) // Notice that with the simplest adapter (using indices everywhere), all functions return their parameters.
// The most simple implementation (using indices everywhere) would look like:
// if (ms_io->RequestClear) { Clear(); }
// if (ms_io->RequestSelectAll) { Clear(); for (int n = 0; n < items_count; n++) { AddItem(n); } }
// if (ms_io->RequestSetRange) { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { UpdateItem(n, ms_io->RangeSelected); } }
void ApplyRequests(ImGuiMultiSelectIO* ms_io, ExampleSelectionAdapter* adapter, int items_count)
{ {
if (ms_io->RequestClear) { Clear(); } IM_ASSERT(adapter->UserDataToIndex != NULL && adapter->IndexToStorage != NULL);
if (ms_io->RequestSelectAll) { Clear(); for (int n = 0; n < items_count; n++) { AddItem(n); } }
if (ms_io->RequestSetRange) { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { UpdateItem(n, ms_io->RangeSelected); } } if (ms_io->RequestClear || ms_io->RequestSelectAll)
Clear();
if (ms_io->RequestSelectAll)
for (int idx = 0; idx < items_count; idx++)
AddItem(adapter->IndexToStorage(adapter, idx));
if (ms_io->RequestSetRange)
{
int first_item_idx = adapter->UserDataToIndex(adapter, ms_io->RangeFirstItem);
int last_item_idx = adapter->UserDataToIndex(adapter, ms_io->RangeLastItem);
for (int idx = first_item_idx; idx <= last_item_idx; idx++)
UpdateItem(adapter->IndexToStorage(adapter, idx), ms_io->RangeSelected);
}
} }
// Call after BeginMultiSelect(). // Call after BeginMultiSelect().
@ -2904,6 +2945,7 @@ static void ShowDemoWindowMultiSelect()
if (ImGui::TreeNode("Multi-Select")) if (ImGui::TreeNode("Multi-Select"))
{ {
static ExampleSelection selection; static ExampleSelection selection;
ExampleSelectionAdapter selection_adapter; // Use default: Pass index to SetNextItemSelectionUserData(), store index in Selection
ImGui::Text("Tips: Use 'Debug Log->Selection' to see selection requests as they happen."); ImGui::Text("Tips: Use 'Debug Log->Selection' to see selection requests as they happen.");
@ -2920,7 +2962,7 @@ static void ShowDemoWindowMultiSelect()
{ {
ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape; ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape;
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags); ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
selection.ApplyRequests(ms_io, ITEMS_COUNT); selection.ApplyRequests(ms_io, &selection_adapter, ITEMS_COUNT);
for (int n = 0; n < ITEMS_COUNT; n++) for (int n = 0; n < ITEMS_COUNT; n++)
{ {
@ -2932,7 +2974,7 @@ static void ShowDemoWindowMultiSelect()
} }
ms_io = ImGui::EndMultiSelect(); ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io, ITEMS_COUNT); selection.ApplyRequests(ms_io, &selection_adapter, ITEMS_COUNT);
ImGui::EndListBox(); ImGui::EndListBox();
} }
@ -2944,6 +2986,7 @@ static void ShowDemoWindowMultiSelect()
if (ImGui::TreeNode("Multi-Select (with clipper)")) if (ImGui::TreeNode("Multi-Select (with clipper)"))
{ {
static ExampleSelection selection; static ExampleSelection selection;
ExampleSelectionAdapter selection_adapter; // Use default: Pass index to SetNextItemSelectionUserData(), store index in Selection
ImGui::Text("Added features:"); ImGui::Text("Added features:");
ImGui::BulletText("Using ImGuiListClipper."); ImGui::BulletText("Using ImGuiListClipper.");
@ -2954,7 +2997,7 @@ static void ShowDemoWindowMultiSelect()
{ {
ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape; ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape;
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags); ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
selection.ApplyRequests(ms_io, ITEMS_COUNT); selection.ApplyRequests(ms_io, &selection_adapter, ITEMS_COUNT);
ImGuiListClipper clipper; ImGuiListClipper clipper;
clipper.Begin(ITEMS_COUNT); clipper.Begin(ITEMS_COUNT);
@ -2973,7 +3016,7 @@ static void ShowDemoWindowMultiSelect()
} }
ms_io = ImGui::EndMultiSelect(); ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io, ITEMS_COUNT); selection.ApplyRequests(ms_io, &selection_adapter, ITEMS_COUNT);
ImGui::EndListBox(); ImGui::EndListBox();
} }
@ -2995,6 +3038,8 @@ static void ShowDemoWindowMultiSelect()
// But you may decide to store selection data inside your item (aka intrusive storage). // But you may decide to store selection data inside your item (aka intrusive storage).
static ImVector<int> items; static ImVector<int> items;
static ExampleSelection selection; static ExampleSelection selection;
ExampleSelectionAdapter selection_adapter;
selection_adapter.SetupForDirectIndexes(); // Pass index to SetNextItemSelectionUserData(), store index in Selection
ImGui::Text("Adding features:"); ImGui::Text("Adding features:");
ImGui::BulletText("Dynamic list with Delete key support."); ImGui::BulletText("Dynamic list with Delete key support.");
@ -3019,7 +3064,7 @@ static void ShowDemoWindowMultiSelect()
{ {
ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape; ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape;
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags); ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
selection.ApplyRequests(ms_io, items.Size); selection.ApplyRequests(ms_io, &selection_adapter, items.Size);
// FIXME-MULTISELECT: Shortcut(). Hard to demo this? May be helpful to send a helper/optional "delete" signal. // FIXME-MULTISELECT: Shortcut(). Hard to demo this? May be helpful to send a helper/optional "delete" signal.
// FIXME-MULTISELECT: may turn into 'ms_io->RequestDelete' -> need HasSelection passed. // FIXME-MULTISELECT: may turn into 'ms_io->RequestDelete' -> need HasSelection passed.
@ -3044,7 +3089,7 @@ static void ShowDemoWindowMultiSelect()
// Apply multi-select requests // Apply multi-select requests
ms_io = ImGui::EndMultiSelect(); ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io, items.Size); selection.ApplyRequests(ms_io, &selection_adapter, items.Size);
if (want_delete) if (want_delete)
selection.ApplyDeletionPostLoop(ms_io, items); selection.ApplyDeletionPostLoop(ms_io, items);
@ -3060,6 +3105,7 @@ static void ShowDemoWindowMultiSelect()
const int SCOPES_COUNT = 3; const int SCOPES_COUNT = 3;
const int ITEMS_COUNT = 8; // Per scope const int ITEMS_COUNT = 8; // Per scope
static ExampleSelection selections_data[SCOPES_COUNT]; static ExampleSelection selections_data[SCOPES_COUNT];
ExampleSelectionAdapter selection_adapter; // Use default: Pass index to SetNextItemSelectionUserData(), store index in Selection
for (int selection_scope_n = 0; selection_scope_n < SCOPES_COUNT; selection_scope_n++) for (int selection_scope_n = 0; selection_scope_n < SCOPES_COUNT; selection_scope_n++)
{ {
@ -3070,7 +3116,7 @@ static void ShowDemoWindowMultiSelect()
ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape; // | ImGuiMultiSelectFlags_ClearOnClickRectVoid ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape; // | ImGuiMultiSelectFlags_ClearOnClickRectVoid
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags); ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
selection->ApplyRequests(ms_io, ITEMS_COUNT); selection->ApplyRequests(ms_io, &selection_adapter, ITEMS_COUNT);
for (int n = 0; n < ITEMS_COUNT; n++) for (int n = 0; n < ITEMS_COUNT; n++)
{ {
@ -3083,7 +3129,7 @@ static void ShowDemoWindowMultiSelect()
// Apply multi-select requests // Apply multi-select requests
ms_io = ImGui::EndMultiSelect(); ms_io = ImGui::EndMultiSelect();
selection->ApplyRequests(ms_io, ITEMS_COUNT); selection->ApplyRequests(ms_io, &selection_adapter, ITEMS_COUNT);
ImGui::PopID(); ImGui::PopID();
} }
ImGui::TreePop(); ImGui::TreePop();
@ -3127,6 +3173,7 @@ static void ShowDemoWindowMultiSelect()
static int items_next_id = 0; static int items_next_id = 0;
if (items_next_id == 0) { for (int n = 0; n < 1000; n++) { items.push_back(items_next_id++); } } if (items_next_id == 0) { for (int n = 0; n < 1000; n++) { items.push_back(items_next_id++); } }
static ExampleSelection selection; static ExampleSelection selection;
ExampleSelectionAdapter selection_adapter; // Use default: Pass index to SetNextItemSelectionUserData(), store index in Selection
ImGui::Text("Selection size: %d/%d", selection.GetSize(), items.Size); ImGui::Text("Selection size: %d/%d", selection.GetSize(), items.Size);
@ -3139,7 +3186,7 @@ static void ShowDemoWindowMultiSelect()
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f));
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags); ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags);
selection.ApplyRequests(ms_io, items.Size); selection.ApplyRequests(ms_io, &selection_adapter, items.Size);
// FIXME-MULTISELECT: Shortcut(). Hard to demo this? May be helpful to send a helper/optional "delete" signal. // FIXME-MULTISELECT: Shortcut(). Hard to demo this? May be helpful to send a helper/optional "delete" signal.
// FIXME-MULTISELECT: may turn into 'ms_io->RequestDelete' -> need HasSelection passed. // FIXME-MULTISELECT: may turn into 'ms_io->RequestDelete' -> need HasSelection passed.
@ -3265,7 +3312,7 @@ static void ShowDemoWindowMultiSelect()
// Apply multi-select requests // Apply multi-select requests
ms_io = ImGui::EndMultiSelect(); ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io, items.Size); selection.ApplyRequests(ms_io, &selection_adapter, items.Size);
if (want_delete) if (want_delete)
selection.ApplyDeletionPostLoop(ms_io, items); selection.ApplyDeletionPostLoop(ms_io, items);

Loading…
Cancel
Save