diff --git a/imgui.cpp b/imgui.cpp index ffbebea7c..291863e6f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -14997,8 +14997,8 @@ void ImGui::ShowMetricsWindow(bool* p_open) // Details for MultiSelect if (TreeNode("MultiSelect", "MultiSelect (%d)", g.MultiSelectStorage.GetAliveCount())) { - ImGuiBoxSelectState* ms = &g.BoxSelectState; - Text("BoxSelect ID=0x%08X, Starting = %d, Active %d", ms->ID, ms->IsStarting, ms->IsActive); + ImGuiBoxSelectState* bs = &g.BoxSelectState; + BulletText("BoxSelect ID=0x%08X, Starting = %d, Active %d", bs->ID, bs->IsStarting, bs->IsActive); for (int n = 0; n < g.MultiSelectStorage.GetMapSize(); n++) if (ImGuiMultiSelectState* state = g.MultiSelectStorage.TryGetMapData(n)) DebugNodeMultiSelectState(state); diff --git a/imgui_internal.h b/imgui_internal.h index 7c0c6aa22..3659bf8cc 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1718,6 +1718,7 @@ struct ImGuiBoxSelectState bool IsActive; bool IsStarting; bool IsStartedFromVoid; // Starting click was not from an item. + bool RequestClear; ImGuiKeyChord KeyMods : 16; // Latched key-mods for box-select logic. ImVec2 StartPosRel; // Start position in window-contents relative space (to support scrolling) ImVec2 EndPosRel; // End position in window-contents relative space @@ -1729,7 +1730,6 @@ struct ImGuiBoxSelectState ImRect UnclipRect; // Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets. ImRect BoxSelectRectPrev; // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos) ImRect BoxSelectRectCurr; - ImGuiSelectionUserData LastSubmittedItem; // Copy of last submitted item data, used to merge output ranges. ImGuiBoxSelectState() { memset(this, 0, sizeof(*this)); } }; @@ -1762,6 +1762,7 @@ 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. @@ -3403,6 +3404,10 @@ namespace ImGui IMGUI_API int TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx); IMGUI_API int TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data); + // Box-Select API + IMGUI_API bool BeginBoxSelect(ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags); + IMGUI_API void EndBoxSelect(const ImRect& scope_rect, bool enable_scroll); + // 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); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 14d4cac64..f9dfa143e 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7113,12 +7113,15 @@ void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data) //------------------------------------------------------------------------- // [SECTION] Widgets: Box-Select support +// This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet. //------------------------------------------------------------------------- -// - BoxSelectStart() [Internal] +// - BoxSelectStartDrag() [Internal] // - BoxSelectScrollWithMouseDrag() [Internal] +// - BeginBoxSelect() [Internal] +// - EndBoxSelect() [Internal] //------------------------------------------------------------------------- -static void BoxSelectStart(ImGuiID id, ImGuiSelectionUserData clicked_item) +static void BoxSelectStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item) { ImGuiContext& g = *GImGui; ImGuiBoxSelectState* bs = &g.BoxSelectState; @@ -7159,6 +7162,91 @@ static void BoxSelectScrollWithMouseDrag(ImGuiWindow* window, const ImRect& inne } } +bool ImGui::BeginBoxSelect(ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + KeepAliveID(box_select_id); + if (bs->ID != box_select_id) + return false; + + bs->UnclipMode = false; + bs->RequestClear = false; + + // IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry. + if (bs->IsStarting && IsMouseDragPastThreshold(0)) + { + bs->IsActive = true; + bs->Window = window; + bs->IsStarting = false; + SetActiveID(bs->ID, window); + if (bs->IsStartedFromVoid && (bs->KeyMods & ImGuiMod_Shift) == 0) + bs->RequestClear = true; + } + else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false) + { + bs->IsActive = bs->IsStarting = false; + if (g.ActiveId == bs->ID) + ClearActiveID(); + bs->ID = 0; + } + if (!bs->IsActive) + return false; + + // Current frame absolute prev/current rectangles are used to toggle selection. + // They are derived from positions relative to scrolling space. + const ImRect scope_rect = window->InnerClipRect; + ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel); + ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already + ImVec2 curr_end_pos_abs = g.IO.MousePos; + if (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow + curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max); + bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs); + bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs); + bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs); + bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs); + + // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper) + // Storing an extra rect used by widgets supporting box-select. + if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) + if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x) + { + bs->UnclipRect = bs->BoxSelectRectPrev; + bs->UnclipRect.Add(bs->BoxSelectRectCurr); + bs->UnclipMode = true; + } + + //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); + //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); + //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f); + return true; +} + +void ImGui::EndBoxSelect(const ImRect& scope_rect, bool enable_scroll) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + IM_ASSERT(bs->IsActive); + bs->UnclipMode = false; + + // Render selection rectangle + bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view + ImRect box_select_r = bs->BoxSelectRectCurr; + box_select_r.ClipWith(scope_rect); + window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling + window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavHighlight)); // FIXME-MULTISELECT: Styling + + // Scroll + if (enable_scroll) + { + ImRect scroll_r = scope_rect; + scroll_r.Expand(-g.FontSize); + //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255)); + if (!scroll_r.Contains(g.IO.MousePos)) + BoxSelectScrollWithMouseDrag(window, scroll_r); + } +} //------------------------------------------------------------------------- // [SECTION] Widgets: Multi-Select support @@ -7268,59 +7356,9 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags) if (flags & ImGuiMultiSelectFlags_BoxSelect) { ms->BoxSelectId = GetID("##BoxSelect"); - KeepAliveID(ms->BoxSelectId); - } - if ((flags & ImGuiMultiSelectFlags_BoxSelect) && ms->BoxSelectId == bs->ID) - { - bs->UnclipMode = false; - - // BoxSelectStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry. - if (bs->IsStarting && IsMouseDragPastThreshold(0)) - { - bs->IsActive = true; - bs->Window = ms->Storage->Window; - bs->IsStarting = false; - SetActiveID(ms->BoxSelectId, window); - if (bs->IsStartedFromVoid && (bs->KeyMods & ImGuiMod_Shift) == 0) - request_clear = true; - } - else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false) - { - bs->ID = 0; - bs->IsActive = bs->IsStarting = false; - if (g.ActiveId == ms->BoxSelectId) - ClearActiveID(); - } - if (bs->IsActive) - { - // Current frame absolute prev/current rectangles are used to toggle selection. - // They are derived from positions relative to scrolling space. - const ImRect scope_rect = window->InnerClipRect; - ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel); - ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already - ImVec2 curr_end_pos_abs = g.IO.MousePos; - if (ms->Flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow - curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max); - bs->LastSubmittedItem = ImGuiSelectionUserData_Invalid; - bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs); - bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs); - bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs); - bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs); - - // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper) - // Storing an extra rect used by widgets supporting box-select. - if (flags & ImGuiMultiSelectFlags_BoxSelect2d) - if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x) - { - bs->UnclipRect = bs->BoxSelectRectPrev; - bs->UnclipRect.Add(bs->BoxSelectRectCurr); - bs->UnclipMode = true; - } - - //GetForegroundDrawList()->AddRect(ms->BoxSelectNoClipRect.Min, ms->BoxSelectNoClipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); - //GetForegroundDrawList()->AddRect(ms->BoxSelectRectPrev.Min, ms->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); - //GetForegroundDrawList()->AddRect(ms->BoxSelectRectCurr.Min, ms->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f); - } + ms->BoxSelectLastitem = ImGuiSelectionUserData_Invalid; + if (BeginBoxSelect(window, ms->BoxSelectId, flags)) + request_clear |= bs->RequestClear; } if (request_clear || request_select_all) @@ -7364,24 +7402,10 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() storage->NavIdSelected = -1; } - ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId); - if ((ms->Flags & ImGuiMultiSelectFlags_BoxSelect) && bs != NULL) + if ((ms->Flags & ImGuiMultiSelectFlags_BoxSelect) && GetBoxSelectState(ms->BoxSelectId)) { - // Box-select: render selection rectangle - bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view - ImRect box_select_r = bs->BoxSelectRectCurr; - box_select_r.ClipWith(scope_rect); - window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling - window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavHighlight)); // FIXME-MULTISELECT: Styling - - // Box-select: scroll - ImRect scroll_r = scope_rect; - scroll_r.Expand(-g.FontSize); - //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255)); - if ((ms->Flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms->Flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0 && !scroll_r.Contains(g.IO.MousePos)) - BoxSelectScrollWithMouseDrag(window, scroll_r); - - bs->UnclipMode = false; + bool enable_scroll = (ms->Flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms->Flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0; + EndBoxSelect(scope_rect, enable_scroll); } } @@ -7395,7 +7419,7 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() { if (ms->Flags & ImGuiMultiSelectFlags_BoxSelect) if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1) - BoxSelectStart(ms->BoxSelectId, ImGuiSelectionUserData_Invalid); + BoxSelectStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid); if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid) if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None) @@ -7544,7 +7568,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) selected = pressed = true; } - // Box-select handling + // Box-select toggle handling if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId)) { const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect); @@ -7554,12 +7578,12 @@ 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 == bs->LastSubmittedItem && prev_req->RangeSelected == selected) + if (prev_req && prev_req->Type == ImGuiSelectionRequestType_SetRange && prev_req->RangeLastItem == ms->BoxSelectLastitem && prev_req->RangeSelected == selected) prev_req->RangeLastItem = item_data; // Merge span into same request else ms->IO.Requests.push_back(req); } - bs->LastSubmittedItem = item_data; + ms->BoxSelectLastitem = item_data; } // Right-click handling: this could be moved at the Selectable() level. @@ -7588,7 +7612,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse; if (ms->Flags & ImGuiMultiSelectFlags_BoxSelect) if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1) - BoxSelectStart(ms->BoxSelectId, item_data); + BoxSelectStartDrag(ms->BoxSelectId, item_data); //---------------------------------------------------------------------------------------- // ACTION | Begin | Pressed/Activated | End