From ab995d3d4f39315ba405beb2a49861b53a8edf58 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Jun 2024 17:04:55 +0200 Subject: [PATCH] MultiSelect: (breaking) Added 'items_count' parameter to BeginMultiSelect(). Will enable extra features, and remove equivalent param from ImGuiSelectionBasicStorage::ApplyRequests(. --- imgui.h | 12 +++++++----- imgui_demo.cpp | 40 ++++++++++++++++++++-------------------- imgui_widgets.cpp | 29 +++++++++++++++++++---------- 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/imgui.h b/imgui.h index 4be6e6467..e358e1aeb 100644 --- a/imgui.h +++ b/imgui.h @@ -179,7 +179,7 @@ struct ImGuiMultiSelectIO; // Structure to interact with a BeginMultiSe struct ImGuiOnceUponAFrame; // Helper for running a block of code not more than once a frame struct ImGuiPayload; // User data payload for drag and drop operations struct ImGuiPlatformImeData; // Platform IME data for io.PlatformSetImeDataFn() function. -struct ImGuiSelectionBasicStorage; // Helper struct to store multi-selection state + apply multi-selection requests. +struct ImGuiSelectionBasicStorage; // Optional helper to store multi-selection state + apply multi-selection requests. struct ImGuiSelectionRequest; // A selection request (stored in ImGuiMultiSelectIO) struct ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use) struct ImGuiStorage; // Helper for key->value storage (container sorted by key) @@ -673,9 +673,10 @@ namespace ImGui // Multi-selection system for Selectable() and TreeNode() functions. // - This enables standard multi-selection/range-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc.) in a way that also allow a clipper to be used. - // - ImGuiSelectionUserData is often used to store your item index. + // - ImGuiSelectionUserData is often used to store your item index within the current view (but may store something else). // - Read comments near ImGuiMultiSelectIO for instructions/details and see 'Demo->Widgets->Selection State & Multi-Select' for demo. - IMGUI_API ImGuiMultiSelectIO* BeginMultiSelect(ImGuiMultiSelectFlags flags, int current_selection_size = -1); + // - 'selection_size' and 'items_count' parameters are optional and used by a few features. If they are costly for you to compute, you may avoid them. + IMGUI_API ImGuiMultiSelectIO* BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size = -1, int items_count = -1); IMGUI_API ImGuiMultiSelectIO* EndMultiSelect(); IMGUI_API void SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data); IMGUI_API bool IsItemToggledSelection(); // Was the last item selection state toggled? Useful if you need the per-item information _before_ reaching EndMultiSelect(). We only returns toggle _event_ in order to handle clipping correctly. @@ -2799,6 +2800,7 @@ struct ImGuiMultiSelectIO ImGuiSelectionUserData NavIdItem; // ms:w, app:r / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items). bool NavIdSelected; // ms:w, app:r / app:r // (If using deletion) Last known selection state for NavId (if part of submitted items). bool RangeSrcReset; // app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection). + int ItemsCount; // ms:w, app:r / app:r // 'int items_count' parameter to BeginMultiSelect() is copied here for convenience, allowing simpler calls to your ApplyRequests handler. Not used internally. }; // Selection request type @@ -2846,8 +2848,8 @@ struct ImGuiSelectionBasicStorage ImGuiID (*AdapterIndexToStorageId)(ImGuiSelectionBasicStorage* self, int idx); // e.g. selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { return ((MyItems**)self->AdapterData)[idx]->ID; }; void* UserData; // User data for use by adapter function // e.g. selection.UserData = (void*)my_items; - // Methods: apply selection requests coming from BeginMultiSelect() and EndMultiSelect() functions - IMGUI_API void ApplyRequests(ImGuiMultiSelectIO* ms_io, int items_count); + // Methods: apply selection requests coming from BeginMultiSelect() and EndMultiSelect() functions. Uses 'items_count' based to BeginMultiSelect() + IMGUI_API void ApplyRequests(ImGuiMultiSelectIO* ms_io); // Methods: selection storage ImGuiSelectionBasicStorage() { Clear(); UserData = NULL; AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; }; } diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 12f873b50..7d4f04cdf 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2874,7 +2874,7 @@ struct ExampleDualListBox // In this example we store item id in selection (instead of item index) Selections[side].UserData = Items[side].Data; Selections[side].AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { ImGuiID* items = (ImGuiID*)self->UserData; return items[idx]; }; - Selections[side].ApplyRequests(ms_io, Items[side].Size); + Selections[side].ApplyRequests(ms_io); } static int IMGUI_CDECL CompareItemsByValue(const void* lhs, const void* rhs) { @@ -2929,7 +2929,7 @@ struct ExampleDualListBox if (child_visible) { ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None; - ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size); + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); ApplySelectionRequests(ms_io, side); for (int item_n = 0; item_n < items.Size; item_n++) @@ -3060,8 +3060,8 @@ static void ShowDemoWindowMultiSelect() if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) { ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; - ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size); - selection.ApplyRequests(ms_io, ITEMS_COUNT); + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); + selection.ApplyRequests(ms_io); for (int n = 0; n < ITEMS_COUNT; n++) { @@ -3073,7 +3073,7 @@ static void ShowDemoWindowMultiSelect() } ms_io = ImGui::EndMultiSelect(); - selection.ApplyRequests(ms_io, ITEMS_COUNT); + selection.ApplyRequests(ms_io); } ImGui::EndChild(); ImGui::TreePop(); @@ -3094,8 +3094,8 @@ static void ShowDemoWindowMultiSelect() if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) { ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; - ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size); - selection.ApplyRequests(ms_io, ITEMS_COUNT); + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); + selection.ApplyRequests(ms_io); ImGuiListClipper clipper; clipper.Begin(ITEMS_COUNT); @@ -3114,7 +3114,7 @@ static void ShowDemoWindowMultiSelect() } ms_io = ImGui::EndMultiSelect(); - selection.ApplyRequests(ms_io, ITEMS_COUNT); + selection.ApplyRequests(ms_io); } ImGui::EndChild(); ImGui::TreePop(); @@ -3158,8 +3158,8 @@ static void ShowDemoWindowMultiSelect() if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) { ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; - ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size); - selection.ApplyRequests(ms_io, items.Size); + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); + selection.ApplyRequests(ms_io); const bool want_delete = ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0); const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1; @@ -3179,7 +3179,7 @@ static void ShowDemoWindowMultiSelect() // Apply multi-select requests ms_io = ImGui::EndMultiSelect(); - selection.ApplyRequests(ms_io, items.Size); + selection.ApplyRequests(ms_io); if (want_delete) selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus); } @@ -3275,8 +3275,8 @@ static void ShowDemoWindowMultiSelect() { ImGui::PushID(selection_scope_n); ImGuiSelectionBasicStorage* selection = &selections_data[selection_scope_n]; - ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection->Size); - selection->ApplyRequests(ms_io, ITEMS_COUNT); + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection->Size, ITEMS_COUNT); + selection->ApplyRequests(ms_io); ImGui::SeparatorText("Selection scope"); ImGui::Text("Selection size: %d/%d", selection->Size, ITEMS_COUNT); @@ -3292,7 +3292,7 @@ static void ShowDemoWindowMultiSelect() // Apply multi-select requests ms_io = ImGui::EndMultiSelect(); - selection->ApplyRequests(ms_io, ITEMS_COUNT); + selection->ApplyRequests(ms_io); ImGui::PopID(); } ImGui::TreePop(); @@ -3375,8 +3375,8 @@ static void ShowDemoWindowMultiSelect() if (widget_type == WidgetType_TreeNode) ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f)); - ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size); - selection.ApplyRequests(ms_io, items.Size); + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); + selection.ApplyRequests(ms_io); const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0)) || request_deletion_from_menu; const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1; @@ -3520,7 +3520,7 @@ static void ShowDemoWindowMultiSelect() // Apply multi-select requests ms_io = ImGui::EndMultiSelect(); - selection.ApplyRequests(ms_io, items.Size); + selection.ApplyRequests(ms_io); if (want_delete) selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus); @@ -9846,12 +9846,12 @@ struct ExampleAssetsBrowser ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease; // To allow dragging an unselected item without altering selection. if (AllowBoxSelect) ms_flags |= ImGuiMultiSelectFlags_BoxSelect; // Enable box-select in 2D mode. - ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, Selection.Size); + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, Selection.Size, Items.Size); // Use custom selection adapter: store ID in selection (recommended) Selection.UserData = this; Selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self_, int idx) { ExampleAssetsBrowser* self = (ExampleAssetsBrowser*)self_->UserData; return self->Items[idx].ID; }; - Selection.ApplyRequests(ms_io, Items.Size); + Selection.ApplyRequests(ms_io); const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (Selection.Size > 0)) || RequestDelete; const int item_curr_idx_to_focus = want_delete ? Selection.ApplyDeletionPreLoop(ms_io, Items.Size) : -1; @@ -9959,7 +9959,7 @@ struct ExampleAssetsBrowser } ms_io = ImGui::EndMultiSelect(); - Selection.ApplyRequests(ms_io, Items.Size); + Selection.ApplyRequests(ms_io); if (want_delete) Selection.ApplyDeletionPostLoop(ms_io, Items, item_curr_idx_to_focus); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index b60e73a49..67ccebeae 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7300,10 +7300,13 @@ static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSe // Return ImGuiMultiSelectIO structure. // Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). -// Passing 'current_selection_size' is currently optional: -// - it is useful for shortcut routing with ImGuiMultiSelectFlags_ClearOnEscape: so we can have Escape be used to clear selection THEN to exit child window. -// - if it is costly for you to compute, but can easily tell if your selection is empty or not, you may pass 1, or use the ImGuiMultiSelectFlags_ClearOnEscape flag dynamically. -ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int current_selection_size) +// Passing 'selection_size' and 'items_count' parameters is currently optional. +// - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags_ClearOnEscape won't claim Escape key when selection_size 0, +// allowing a first press to clear selection THEN the second press to leave child window and return to parent. +// - 'items_count' is stored in ImGuiMultiSelectIO which makes it a convenient way to pass the information to your ApplyRequest() handler (but you may pass it differently). +// - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1. +// - If you can easily tell if your selection is empty or not, you may pass 0/1, or you may enable ImGuiMultiSelectFlags_ClearOnEscape flag dynamically. +ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size, int items_count) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -7341,15 +7344,16 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int cur ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id); storage->ID = id; storage->LastFrameActive = g.FrameCount; - storage->LastSelectionSize = current_selection_size; + storage->LastSelectionSize = selection_size; storage->Window = window; ms->Storage = storage; // Output to user + ms->IO.Requests.resize(0); ms->IO.RangeSrcItem = storage->RangeSrcItem; ms->IO.NavIdItem = storage->NavIdItem; ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false; - ms->IO.Requests.resize(0); + ms->IO.ItemsCount = items_count; // Clear when using Navigation to move within the scope // (we compare FocusScopeId so it possible to use multiple selections inside a same window) @@ -7375,7 +7379,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int cur { // Shortcut: Clear selection (Escape) // Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window. - if ((flags & ImGuiMultiSelectFlags_ClearOnEscape) && (current_selection_size != 0)) + if ((flags & ImGuiMultiSelectFlags_ClearOnEscape) && (selection_size != 0)) if (Shortcut(ImGuiKey_Escape)) request_clear = true; @@ -7826,17 +7830,22 @@ void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage) // if (req.Type == ImGuiSelectionRequestType_SetAll) { Clear(); if (req.Selected) { for (int n = 0; n < items_count; n++) { AddItem(n); } } // if (req.Type == ImGuiSelectionRequestType_SetRange) { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { UpdateItem(n, ms_io->Selected); } } // } -void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io, int items_count) +void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) { + // For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional. + // It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect(). + // Other scheme may handle SetAll differently. + IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!"); IM_ASSERT(AdapterIndexToStorageId != NULL); + for (ImGuiSelectionRequest& req : ms_io->Requests) { if (req.Type == ImGuiSelectionRequestType_SetAll) Clear(); if (req.Type == ImGuiSelectionRequestType_SetAll && req.Selected) { - Storage.Data.reserve(items_count); - for (int idx = 0; idx < items_count; idx++) + Storage.Data.reserve(ms_io->ItemsCount); + for (int idx = 0; idx < ms_io->ItemsCount; idx++) SetItemSelected(AdapterIndexToStorageId(this, idx), true); } if (req.Type == ImGuiSelectionRequestType_SetRange)