From f36a03c317a97da95fba9287afff64c8548f3620 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 6 Mar 2024 14:22:38 +0100 Subject: [PATCH] MultiSelect: (Breaking) merge ImGuiSelectionRequestType_Clear and ImGuiSelectionRequestType_SelectAll into ImGuiSelectionRequestType_SetAll., rename ImGuiSelectionRequest::RangeSelected to Selected. The reasoning is that it makes it easier/faster to write an adhoc ImGuiMultiSelectIO handler (e.g. trying to apply multi-select to checkboxes) --- imgui.h | 13 ++++++------- imgui_internal.h | 3 +-- imgui_widgets.cpp | 38 ++++++++++++++++---------------------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/imgui.h b/imgui.h index 54c4b0264..44990f68b 100644 --- a/imgui.h +++ b/imgui.h @@ -2744,11 +2744,11 @@ struct ImColor // - Store and maintain actual selection data using persistent object identifiers. // - Usage flow: // BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO* result. -// - (2) [If using clipper] Honor request list (Clear/SelectAll/SetRange requests) by updating your selection data. Same code as Step 6. +// - (2) [If using clipper] Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 6. // - (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper.IncludeItemByIndex(). If storing indices in ImGuiSelectionUserData, a simple clipper.IncludeItemByIndex(ms_io->RangeSrcItem) call will work. // LOOP - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls. // END - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO* result. -// - (6) Honor request list (Clear/SelectAll/SetRange requests) by updating your selection data. Same code as Step 2. +// - (6) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 2. // If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is fine to always honor those steps. // About ImGuiSelectionUserData: // - For each item is it submitted by your call to SetNextItemSelectionUserData(). @@ -2771,7 +2771,7 @@ enum ImGuiMultiSelectFlags_ { ImGuiMultiSelectFlags_None = 0, ImGuiMultiSelectFlags_SingleSelect = 1 << 0, // Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho! - ImGuiMultiSelectFlags_NoSelectAll = 1 << 1, // Disable CTRL+A shortcut sending a SelectAll request. + ImGuiMultiSelectFlags_NoSelectAll = 1 << 1, // Disable CTRL+A shortcut to select all. ImGuiMultiSelectFlags_NoRangeSelect = 1 << 2, // Disable Shift+Click/Shift+Keyboard handling (useful for unordered 2D selection). ImGuiMultiSelectFlags_BoxSelect = 1 << 3, // Enable box-selection (only supporting 1D list when using clipper, not 2D grids). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space. ImGuiMultiSelectFlags_BoxSelect2d = 1 << 4, // Enable box-selection with 2D layout/grid support. This alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items. @@ -2803,9 +2803,8 @@ struct ImGuiMultiSelectIO enum ImGuiSelectionRequestType { ImGuiSelectionRequestType_None = 0, - ImGuiSelectionRequestType_Clear, // Request app to clear selection. - ImGuiSelectionRequestType_SelectAll, // Request app to select all. - ImGuiSelectionRequestType_SetRange, // Request app to select/unselect [RangeFirstItem..RangeLastItem] items based on 'bool RangeSelected'. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false. + ImGuiSelectionRequestType_SetAll, // Request app to clear selection (if Selected==false) or select all items (if Selected==true) + ImGuiSelectionRequestType_SetRange, // Request app to select/unselect [RangeFirstItem..RangeLastItem] items (inclusive) based on value of Selected. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false. }; // Selection request item @@ -2813,7 +2812,7 @@ struct ImGuiSelectionRequest { //------------------------------------------// BeginMultiSelect / EndMultiSelect ImGuiSelectionRequestType Type; // ms:w, app:r / ms:w, app:r // Request type. You'll most often receive 1 Clear + 1 SetRange with a single-item range. - bool RangeSelected; // / ms:w, app:r // Parameter for SetRange request (true = select range, false = unselect range) + bool Selected; // / ms:w, app:r // Parameter for SetAll/SetRange requests (true = select, false = unselect) ImGuiSelectionUserData RangeFirstItem; // / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom) ImGuiSelectionUserData RangeLastItem; // / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top) }; diff --git a/imgui_internal.h b/imgui_internal.h index 3659bf8cc..1e97b71f4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1754,8 +1754,7 @@ struct IMGUI_API ImGuiMultiSelectTempData ImVec2 BackupCursorMaxPos; ImGuiID BoxSelectId; ImGuiKeyChord KeyMods; - bool LoopRequestClear; - bool LoopRequestSelectAll; + ImS8 LoopRequestSetAll; // -1: no operation, 0: clear all, 1: select all. bool IsEndIO; // Set when switching IO from BeginMultiSelect() to EndMultiSelect() state. bool IsFocused; // Set if currently focusing the selection scope (any item of the selection). May be used if you have custom shortcut associated to selection. bool IsSetRange; // Set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index fecfbc4fb..e71f32ddd 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7272,9 +7272,8 @@ static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSe ImGuiContext& g = *GImGui; for (const ImGuiSelectionRequest& req : io->Requests) { - if (req.Type == ImGuiSelectionRequestType_Clear) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: Clear\n", function); - if (req.Type == ImGuiSelectionRequestType_SelectAll) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SelectAll\n", function); - if (req.Type == ImGuiSelectionRequestType_SetRange) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %" IM_PRId64 "..%" IM_PRId64 " (0x%" IM_PRIX64 "..0x%" IM_PRIX64 ") = %d\n", function, req.RangeFirstItem, req.RangeLastItem, req.RangeFirstItem, req.RangeLastItem, req.RangeSelected); + if (req.Type == ImGuiSelectionRequestType_SetAll) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", function, req.Selected, req.Selected ? "SelectAll" : "Clear"); + if (req.Type == ImGuiSelectionRequestType_SetRange) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %" IM_PRId64 "..%" IM_PRId64 " (0x%" IM_PRIX64 "..0x%" IM_PRIX64 ") = %d\n", function, req.RangeFirstItem, req.RangeLastItem, req.RangeFirstItem, req.RangeLastItem, req.Selected); } } @@ -7372,11 +7371,10 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags) if (request_clear || request_select_all) { - ImGuiSelectionRequest req = { request_select_all ? ImGuiSelectionRequestType_SelectAll : ImGuiSelectionRequestType_Clear, false, (ImGuiSelectionUserData)-1, (ImGuiSelectionUserData)-1 }; + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, request_select_all, (ImGuiSelectionUserData)-1, (ImGuiSelectionUserData)-1 }; ms->IO.Requests.push_back(req); } - ms->LoopRequestClear = request_clear; - ms->LoopRequestSelectAll = request_select_all; + ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO); @@ -7441,7 +7439,7 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid) if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None) { - ImGuiSelectionRequest req = { ImGuiSelectionRequestType_Clear, false, (ImGuiSelectionUserData)-1, (ImGuiSelectionUserData)-1 }; + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, false, (ImGuiSelectionUserData)-1, (ImGuiSelectionUserData)-1 }; ms->IO.Requests.resize(0); ms->IO.Requests.push_back(req); } @@ -7494,13 +7492,11 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData; IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId && "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope"); - // Apply Clear/SelectAll requests requested by BeginMultiSelect(). + // Apply SetAll (Clear/SelectAll )requests requested by BeginMultiSelect(). // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper. - // If you are using a clipper (aka not submitting every element of the list) you need to process the Clear/SelectAll request after calling BeginMultiSelect() - if (ms->LoopRequestClear) - selected = false; - else if (ms->LoopRequestSelectAll) - selected = true; + // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect() + if (ms->LoopRequestSetAll != -1) + selected = (ms->LoopRequestSetAll == 1); // When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection) // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function) @@ -7595,7 +7591,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) selected = !selected; ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, item_data, item_data }; ImGuiSelectionRequest* prev_req = (ms->IO.Requests.Size > 0) ? &ms->IO.Requests.Data[ms->IO.Requests.Size - 1] : NULL; - if (prev_req && prev_req->Type == ImGuiSelectionRequestType_SetRange && prev_req->RangeLastItem == ms->BoxSelectLastitem && prev_req->RangeSelected == selected) + if (prev_req && prev_req->Type == ImGuiSelectionRequestType_SetRange && prev_req->RangeLastItem == ms->BoxSelectLastitem && prev_req->Selected == selected) prev_req->RangeLastItem = item_data; // Merge span into same request else ms->IO.Requests.push_back(req); @@ -7657,7 +7653,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) request_clear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again. if (request_clear) { - ImGuiSelectionRequest req = { ImGuiSelectionRequestType_Clear, false, (ImGuiSelectionUserData)-1, (ImGuiSelectionUserData)-1 }; + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, false, (ImGuiSelectionUserData)-1, (ImGuiSelectionUserData)-1 }; ms->IO.Requests.resize(0); ms->IO.Requests.push_back(req); } @@ -7734,27 +7730,25 @@ void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage) // The most simple implementation (using indices everywhere) would look like: // for (ImGuiSelectionRequest& req : ms_io->Requests) // { -// if (req.Type == ImGuiSelectionRequestType_Clear) { Clear(); } -// if (req.Type == ImGuiSelectionRequestType_SelectAll) { Clear(); 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->RangeSelected); } } +// 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) { IM_ASSERT(AdapterIndexToStorageId != NULL); for (ImGuiSelectionRequest& req : ms_io->Requests) { - if (req.Type == ImGuiSelectionRequestType_Clear) + if (req.Type == ImGuiSelectionRequestType_SetAll) Clear(); - if (req.Type == ImGuiSelectionRequestType_SelectAll) + if (req.Type == ImGuiSelectionRequestType_SetAll && req.Selected) { - Clear(); Storage.Data.reserve(items_count); for (int idx = 0; idx < items_count; idx++) AddItem(AdapterIndexToStorageId(this, idx)); } if (req.Type == ImGuiSelectionRequestType_SetRange) for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++) - UpdateItem(AdapterIndexToStorageId(this, idx), req.RangeSelected); + UpdateItem(AdapterIndexToStorageId(this, idx), req.Selected); } }