diff --git a/imgui.h b/imgui.h index 6c65045e..550e6436 100644 --- a/imgui.h +++ b/imgui.h @@ -2732,7 +2732,7 @@ struct ImColor enum ImGuiMultiSelectFlags_ { ImGuiMultiSelectFlags_None = 0, - ImGuiMultiSelectFlags_NoMultiSelect = 1 << 0, + ImGuiMultiSelectFlags_NoMultiSelect = 1 << 0, // Disable selecting more than one item. This is not very useful at this kind of selection can be implemented without BeginMultiSelect(), but this is available for consistency. ImGuiMultiSelectFlags_NoUnselect = 1 << 1, // Disable unselecting items with CTRL+Click, CTRL+Space etc. ImGuiMultiSelectFlags_NoSelectAll = 1 << 2, // Disable CTRL+A shortcut to set RequestSelectAll ImGuiMultiSelectFlags_ClearOnClickWindowVoid= 1 << 3, // Clear selection when clicking on empty location within host window (use if BeginMultiSelect() covers a whole window) @@ -2787,7 +2787,7 @@ struct ImGuiMultiSelectData bool RangeValue; // End // End: parameter from RequestSetRange request. true = Select Range, false = Unselect Range. void* RangeSrc; // Begin, End // End: parameter from RequestSetRange request + you need to save this value so you can pass it again next frame. / Begin: this is the value you passed to BeginMultiSelect() void* RangeDst; // End // End: parameter from RequestSetRange request. - int RangeDirection; // End // End: parameter from RequestSetRange request. +1 if RangeSrc came before RangeDst, -1 otherwise. Available as an indicator in case you cannot infer order from the void* values. + int RangeDirection; // End // End: parameter from RequestSetRange request. +1 if RangeSrc came before RangeDst, -1 otherwise. Available as an indicator in case you cannot infer order from the void* values. If your void* values are storing indices you will never need this. ImGuiMultiSelectData() { Clear(); } void Clear() diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 52e512e2..3008ca94 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2787,7 +2787,7 @@ struct ExampleSelection void Clear() { Storage.Clear(); SelectionSize = 0; } bool GetSelected(int n) const { return Storage.GetInt((ImGuiID)n, 0) != 0; } void SetSelected(int n, bool v) { int* p_int = Storage.GetIntRef((ImGuiID)n, 0); if (*p_int == (int)v) return; if (v) SelectionSize++; else SelectionSize--; *p_int = (bool)v; } - int GetSelectionSize() const { return SelectionSize; } + int GetSize() const { return SelectionSize; } // When using SelectAll() / SetRange() we assume that our objects ID are indices. // In this demo we always store selection using indices and never in another manner (e.g. object ID or pointers). @@ -2808,7 +2808,6 @@ struct ExampleSelection } }; - static void ShowDemoWindowMultiSelect() { IMGUI_DEMO_MARKER("Widgets/Selection State"); @@ -2873,7 +2872,7 @@ static void ShowDemoWindowMultiSelect() // The BeginListBox() has no actual purpose for selection logic (other that offering a scrolling regions). const int ITEMS_COUNT = 50; - ImGui::Text("Selection size: %d", selection.GetSelectionSize()); + ImGui::Text("Selection size: %d", selection.GetSize()); if (ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20))) { ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape; @@ -2918,7 +2917,8 @@ static void ShowDemoWindowMultiSelect() static bool use_drag_drop = true; static bool use_multiple_scopes = false; static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None; - static WidgetType widget_type = WidgetType_TreeNode; + static WidgetType widget_type = WidgetType_Selectable; + if (ImGui::RadioButton("Selectables", widget_type == WidgetType_Selectable)) { widget_type = WidgetType_Selectable; } ImGui::SameLine(); if (ImGui::RadioButton("Tree nodes", widget_type == WidgetType_TreeNode)) { widget_type = WidgetType_TreeNode; } @@ -2951,7 +2951,7 @@ static void ShowDemoWindowMultiSelect() } else { - ImGui::Text("Selection size: %d", selection->GetSelectionSize()); + ImGui::Text("Selection size: %d", selection->GetSize()); draw_selection = ImGui::BeginListBox("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20)); } if (draw_selection) @@ -2967,7 +2967,7 @@ static void ShowDemoWindowMultiSelect() selection->ApplyRequests(multi_select_data, ITEMS_COUNT); if (use_multiple_scopes) - ImGui::Text("Selection size: %d", selection->GetSelectionSize()); // Draw counter below Separator and after BeginMultiSelect() + ImGui::Text("Selection size: %d", selection->GetSize()); // Draw counter below Separator and after BeginMultiSelect() if (use_table) { @@ -2994,7 +2994,7 @@ static void ShowDemoWindowMultiSelect() ImGui::PushID(n); const char* category = random_names[n % IM_ARRAYSIZE(random_names)]; char label[64]; - sprintf(label, "Object %05d (category: %s)", n, category); + sprintf(label, "Object %05d: category: %s", n, category); bool item_is_selected = selection->GetSelected(n); // Emit a color button, to test that Shift+LeftArrow landing on an item that is not part @@ -3011,7 +3011,7 @@ static void ShowDemoWindowMultiSelect() selection->SetSelected(n, !item_is_selected); if (use_drag_drop && ImGui::BeginDragDropSource()) { - ImGui::Text("(Dragging %d items)", selection->GetSelectionSize()); + ImGui::Text("(Dragging %d items)", selection->GetSize()); ImGui::EndDragDropSource(); } } @@ -3026,7 +3026,7 @@ static void ShowDemoWindowMultiSelect() selection->SetSelected(n, !item_is_selected); if (use_drag_drop && ImGui::BeginDragDropSource()) { - ImGui::Text("(Dragging %d items)", selection->GetSelectionSize()); + ImGui::Text("(Dragging %d items)", selection->GetSize()); ImGui::EndDragDropSource(); } if (open) @@ -3041,6 +3041,7 @@ static void ShowDemoWindowMultiSelect() ImGui::EndPopup(); } + // Demo content within a table if (use_table) { ImGui::TableNextColumn(); diff --git a/imgui_internal.h b/imgui_internal.h index 82f67dc6..c51f2875 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2124,9 +2124,6 @@ struct ImGuiContext ImVec2 NavWindowingAccumDeltaPos; ImVec2 NavWindowingAccumDeltaSize; - // Range-Select/Multi-Select - ImGuiMultiSelectState MultiSelectState; // FIXME-MULTISELECT: We currently don't support recursing/stacking multi-select - // Render float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list) @@ -2169,6 +2166,9 @@ struct ImGuiContext ImVector CurrentTabBarStack; ImVector ShrinkWidthBuffer; + // Multi-Select state + ImGuiMultiSelectState MultiSelectState; // FIXME-MULTISELECT: We currently don't support recursing/stacking multi-select + // Hover Delay system ImGuiID HoverItemDelayId; ImGuiID HoverItemDelayIdPreviousFrame; @@ -3344,7 +3344,7 @@ 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); - // Multi-Select/Range-Select API + // Multi-Select API IMGUI_API void MultiSelectItemHeader(ImGuiID id, bool* p_selected); IMGUI_API void MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 71f0530c..5fbe0821 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7157,22 +7157,20 @@ ImGuiMultiSelectData* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, void* ms->In.RequestClear = true; } - // Shortcuts - if (ms->IsFocused) - { - // Select All helper shortcut (CTRL+A) - // Note: we are comparing FocusScope so we don't need to be testing for IsWindowFocused() - if (!(flags & ImGuiMultiSelectFlags_NoMultiSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll)) - if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A)) - ms->In.RequestSelectAll = true; + // Shortcut: Select all (CTRL+A) + if (ms->IsFocused && !(flags & ImGuiMultiSelectFlags_NoMultiSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll)) + if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A)) + ms->In.RequestSelectAll = true; - if (flags & ImGuiMultiSelectFlags_ClearOnEscape) - if (Shortcut(ImGuiKey_Escape)) // FIXME-MULTISELECT: Only hog shortcut if selection is not null, meaning we need "has selection or "selection size" data here. - ms->In.RequestClear = true; - } + // Shortcut: Clear selection (Escape) + // FIXME-MULTISELECT: Only hog shortcut if selection is not null, meaning we need "has selection or "selection size" data here. + // Otherwise may be done by caller but it means Shortcut() needs to be exposed. + if (ms->IsFocused && (flags & ImGuiMultiSelectFlags_ClearOnEscape)) + if (Shortcut(ImGuiKey_Escape)) + ms->In.RequestClear = true; - //if (ms->In.RequestClear) IMGUI_DEBUG_LOG("BeginMultiSelect: RequestClear\n"); - //if (ms->In.RequestSelectAll) IMGUI_DEBUG_LOG("BeginMultiSelect: RequestSelectAll\n"); + //if (ms->In.RequestClear) IMGUI_DEBUG_LOG_SELECTION("BeginMultiSelect: RequestClear\n"); + //if (ms->In.RequestSelectAll) IMGUI_DEBUG_LOG_SELECTION("BeginMultiSelect: RequestSelectAll\n"); return &ms->In; } @@ -7201,9 +7199,9 @@ ImGuiMultiSelectData* ImGui::EndMultiSelect() ms->Flags = ImGuiMultiSelectFlags_None; PopFocusScope(); - //if (ms->Out.RequestClear) IMGUI_DEBUG_LOG("EndMultiSelect: RequestClear\n"); - //if (ms->Out.RequestSelectAll) IMGUI_DEBUG_LOG("EndMultiSelect: RequestSelectAll\n"); - //if (ms->Out.RequestSetRange) IMGUI_DEBUG_LOG("EndMultiSelect: RequestSetRange %p..%p = %d\n", ms->Out.RangeSrc, ms->Out.RangeDst, ms->Out.RangeValue); + //if (ms->Out.RequestClear) IMGUI_DEBUG_LOG_SELECTION("EndMultiSelect: RequestClear\n"); + //if (ms->Out.RequestSelectAll) IMGUI_DEBUG_LOG_SELECTION("EndMultiSelect: RequestSelectAll\n"); + //if (ms->Out.RequestSetRange) IMGUI_DEBUG_LOG_SELECTION("EndMultiSelect: RequestSetRange %p..%p = %d (dir %+d)\n", ms->Out.RangeSrc, ms->Out.RangeDst, ms->Out.RangeValue, ms->Out.RangeDirection); return &ms->Out; } @@ -7220,7 +7218,7 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d g.NextItemData.SelectionUserData = selection_user_data; g.NextItemData.FocusScopeId = g.CurrentFocusScopeId; - // Auto updating RangeSrcPassedBy for cases were clipped is not used. + // Auto updating RangeSrcPassedBy for cases were clipper is not used. if (g.MultiSelectState.In.RangeSrc == (void*)selection_user_data) g.MultiSelectState.In.RangeSrcPassedBy = true; } @@ -7322,6 +7320,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 (is_shift && is_multiselect) { + // Shift+Arrow always select, Ctrl+Shift+Arrow copy source selection state. ms->Out.RequestSetRange = true; ms->Out.RangeDst = item_data; if (!is_ctrl) @@ -7330,7 +7329,8 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) } else { - selected = (!is_ctrl || (ms->Flags & ImGuiMultiSelectFlags_NoUnselect)) ? true : !selected; + // Ctrl inverts selection, otherwise always select + selected = (is_ctrl && (ms->Flags & ImGuiMultiSelectFlags_NoUnselect) == 0) ? !selected : true; ms->Out.RangeSrc = ms->Out.RangeDst = item_data; ms->Out.RangeValue = selected; } @@ -7338,13 +7338,12 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) if (input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) { // Mouse click without CTRL clears the selection, unless the clicked item is already selected - bool preserve_existing_selection = g.DragDropActive; - if (is_multiselect && !is_ctrl && !preserve_existing_selection) + if (is_multiselect && !is_ctrl) ms->Out.RequestClear = true; - if (is_multiselect && !is_shift && !preserve_existing_selection && ms->Out.RequestClear) + if (is_multiselect && !is_shift && ms->Out.RequestClear) { // For toggle selection unless there is a Clear request, we can handle it completely locally without sending a RangeSet request. - IM_ASSERT(ms->Out.RangeSrc == ms->Out.RangeDst); // Setup by block above + IM_ASSERT(ms->Out.RangeSrc == ms->Out.RangeDst); // Setup by else block above ms->Out.RequestSetRange = true; ms->Out.RangeValue = selected; ms->Out.RangeDirection = +1;