From 79b77d91c98e36c1dd487516564cec716ddd94f1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 24 Jul 2024 18:43:54 +0200 Subject: [PATCH] MultiSelect: sequential SetRange merging not generally handled by box-select path, useful for others. --- imgui_internal.h | 3 ++- imgui_widgets.cpp | 35 ++++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index b307adcde..85aaa19bc 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1751,6 +1751,7 @@ struct IMGUI_API ImGuiMultiSelectTempData ImGuiMultiSelectFlags Flags; ImVec2 ScopeRectMin; ImVec2 BackupCursorMaxPos; + ImGuiSelectionUserData LastSubmittedItem; // Copy of last submitted item data, used to merge output ranges. ImGuiID BoxSelectId; ImGuiKeyChord KeyMods; ImS8 LoopRequestSetAll; // -1: no operation, 0: clear all, 1: select all. @@ -1760,7 +1761,6 @@ struct IMGUI_API ImGuiMultiSelectTempData bool NavIdPassedBy; bool RangeSrcPassedBy; // Set by the item that matches RangeSrcItem. bool RangeDstPassedBy; // Set by the item that matches NavJustMovedToId when IsSetRange is set. - ImGuiSelectionUserData BoxSelectLastitem; // Copy of last submitted item data, used to merge output ranges. ImGuiMultiSelectTempData() { Clear(); } void Clear() { size_t io_sz = sizeof(IO); ClearIO(); memset((void*)(&IO + 1), 0, sizeof(*this) - io_sz); } // Zero-clear except IO as we preserve IO.Requests[] buffer allocation. @@ -3408,6 +3408,7 @@ namespace ImGui // Multi-Select API IMGUI_API void MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags); IMGUI_API void MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed); + IMGUI_API void MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item); inline ImGuiBoxSelectState* GetBoxSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.BoxSelectState.ID == id && g.BoxSelectState.IsActive) ? &g.BoxSelectState : NULL; } inline ImGuiMultiSelectState* GetMultiSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return g.MultiSelectStorage.GetByKey(id); } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 068337e9c..183c7fbe2 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7069,7 +7069,7 @@ static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2) // When SingleCharMode is set: // - it is better to NOT display a tooltip of other on-screen display indicator. // - the index of the currently focused item is required. -// if your SetNextItemSelectionData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData. +// if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData. int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx) { if (req == NULL || req->SelectRequest == false) // Support NULL parameter so both calls can be done from same spot. @@ -7397,7 +7397,6 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) { ms->BoxSelectId = GetID("##BoxSelect"); - ms->BoxSelectLastitem = ImGuiSelectionUserData_Invalid; if (BeginBoxSelect(window, ms->BoxSelectId, flags)) request_clear |= bs->RequestClear; } @@ -7432,6 +7431,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel ms->IO.Requests.push_back(req); } ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; + ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid; if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO); @@ -7694,17 +7694,10 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) else { selected = !selected; - ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, +1, item_data, item_data }; - // Merge continuous ranges (unless NoRangeSelect is set) - ImGuiSelectionRequest* prev = ms->IO.Requests.Size > 0 ? &ms->IO.Requests.Data[ms->IO.Requests.Size - 1] : NULL; - if (prev && prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->BoxSelectLastitem && prev->Selected == selected && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0) - prev->RangeLastItem = item_data; // Merge span into same request - else - ms->IO.Requests.push_back(req); + MultiSelectAddSetRange(ms, selected, +1, item_data, item_data); } storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1); } - ms->BoxSelectLastitem = item_data; } // Right-click handling. @@ -7804,9 +7797,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) range_selected = selected; range_direction = +1; } - ImGuiSelectionUserData range_dst_item = item_data; - ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, range_selected, (ImS8)range_direction, (range_direction > 0) ? storage->RangeSrcItem : range_dst_item, (range_direction > 0) ? range_dst_item : storage->RangeSrcItem }; - ms->IO.Requests.push_back(req); + MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data); } // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect) @@ -7821,11 +7812,29 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) } if (storage->NavIdItem == item_data) ms->NavIdPassedBy = true; + ms->LastSubmittedItem = item_data; *p_selected = selected; *p_pressed = pressed; } +void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item) +{ + // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) + if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0) + { + ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1]; + if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected) + { + prev->RangeLastItem = last_item; + return; + } + } + + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item }; + ms->IO.Requests.push_back(req); // Add new request +} + void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage) { #ifndef IMGUI_DISABLE_DEBUG_TOOLS