From d2f208a30c426f48e918897bd5e3b97999aa80f2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 20 May 2023 15:51:39 +0200 Subject: [PATCH] MultiSelect: made SetNextItemSelectionData() optional to allow disjoint selection (e.g. with a CollapsingHeader between items). Amend demo. --- imgui.h | 4 ++-- imgui_demo.cpp | 40 +++++++++++++++++++++++----------------- imgui_widgets.cpp | 7 ++++--- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/imgui.h b/imgui.h index 987d7e7fb..6c65045e3 100644 --- a/imgui.h +++ b/imgui.h @@ -2766,14 +2766,14 @@ enum ImGuiMultiSelectFlags_ // 1) Call BeginMultiSelect() with the last saved value of ->RangeSrc and its selection state. // It is because you need to pass its selection state (and you own selection) that we don't store this value in Dear ImGui. // (For the initial frame or when resetting your selection state: you may use the value for your first item or a "null" value that matches the type stored in your void*). -// 2) Honor Clear/SelectAll requests by updating your selection data. Only required if you are using a clipper in step 4: but you can use same code as step 6 anyway. +// 2) Honor Clear/SelectAll/SetRange requests by updating your selection data. (Only required if you are using a clipper in step 4: but you can use same code as step 6 anyway.) // Loop // 3) Set RangeSrcPassedBy=true if the RangeSrc item is part of the items clipped before the first submitted/visible item. [Only required if you are using a clipper in step 4] // This is because for range-selection we need to know if we are currently "inside" or "outside" the range. // If you are using integer indices everywhere, this is easy to compute: if (clipper.DisplayStart > (int)data->RangeSrc) { data->RangeSrcPassedBy = true; } // 4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls. // Call IsItemToggledSelection() to query if the selection state has been toggled, if you need the info immediately for your display (before EndMultiSelect()). -// When cannot return a "IsItemSelected()" value because we need to consider clipped/unprocessed items, this is why we return a "Toggle" event instead. +// When cannot provide a "IsItemSelected()" value because we need to consider clipped/unprocessed items, this is why we return a "Toggled" event instead. // End // 5) Call EndMultiSelect(). Save the value of ->RangeSrc for the next frame (you may convert the value in a format that is safe for persistance) // 6) Honor Clear/SelectAll/SetRange requests by updating your selection data. Always process them in this order (as you will receive Clear+SetRange request simultaneously) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 3b98bc5c4..52e512e29 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2830,6 +2830,7 @@ static void ShowDemoWindowMultiSelect() ImGui::TreePop(); } + // Demonstrate implementation a most-basic form of multi-selection manually IMGUI_DEMO_MARKER("Widgets/Selection State/Multiple Selection (simplfied, manual)"); if (ImGui::TreeNode("Multiple Selection (simplified, manual)")) { @@ -2841,9 +2842,9 @@ static void ShowDemoWindowMultiSelect() sprintf(buf, "Object %d", n); if (ImGui::Selectable(buf, selection[n])) { - if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held + if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held memset(selection, 0, sizeof(selection)); - selection[n] ^= 1; + selection[n] ^= 1; // Toggle current item } } ImGui::TreePop(); @@ -2883,7 +2884,6 @@ static void ShowDemoWindowMultiSelect() { char label[64]; sprintf(label, "Object %05d: %s", n, random_names[n % IM_ARRAYSIZE(random_names)]); - bool item_is_selected = selection.GetSelected(n); ImGui::SetNextItemSelectionUserData(n); ImGui::Selectable(label, item_is_selected); @@ -2916,28 +2916,36 @@ static void ShowDemoWindowMultiSelect() enum WidgetType { WidgetType_Selectable, WidgetType_TreeNode }; static bool use_table = false; static bool use_drag_drop = true; - static bool multiple_selection_scopes = false; + static bool use_multiple_scopes = false; + static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None; static WidgetType widget_type = WidgetType_TreeNode; 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; } ImGui::Checkbox("Use table", &use_table); ImGui::Checkbox("Use drag & drop", &use_drag_drop); - ImGui::Checkbox("Distinct selection scopes in same window", &multiple_selection_scopes); + ImGui::Checkbox("Multiple selection scopes in same window", &use_multiple_scopes); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoMultiSelect", &flags, ImGuiMultiSelectFlags_NoMultiSelect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoUnselect", &flags, ImGuiMultiSelectFlags_NoUnselect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoSelectAll", &flags, ImGuiMultiSelectFlags_NoSelectAll); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnEscape", &flags, ImGuiMultiSelectFlags_ClearOnEscape); + ImGui::BeginDisabled(use_multiple_scopes); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickWindowVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickWindowVoid); + ImGui::EndDisabled(); - // When 'multiple_selection_scopes' is set we show 3 selection scopes in the host window instead of 1 in a scrolling window. + // When 'use_multiple_scopes' is set we show 3 selection scopes in the host window instead of 1 in a scrolling window. static ExampleSelection selections_data[3]; - const int selection_scope_count = multiple_selection_scopes ? 3 : 1; + const int selection_scope_count = use_multiple_scopes ? 3 : 1; for (int selection_scope_n = 0; selection_scope_n < selection_scope_count; selection_scope_n++) { ExampleSelection* selection = &selections_data[selection_scope_n]; - const int ITEMS_COUNT = multiple_selection_scopes ? 12 : 1000; + const int ITEMS_COUNT = use_multiple_scopes ? 12 : 1000; // Smaller count to make it easier to see multiple scopes in same screen. ImGui::PushID(selection_scope_n); // Open a scrolling region bool draw_selection = true; - if (multiple_selection_scopes) + if (use_multiple_scopes) { ImGui::SeparatorText("Selection scope"); } @@ -2952,15 +2960,13 @@ static void ShowDemoWindowMultiSelect() if (widget_type == WidgetType_TreeNode) ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f)); - ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None; - if (multiple_selection_scopes) - ;// flags |= ImGuiMultiSelectFlags_ClearOnClickRectVoid; - else - flags |= ImGuiMultiSelectFlags_ClearOnClickWindowVoid; - ImGuiMultiSelectData* multi_select_data = ImGui::BeginMultiSelect(flags, (void*)(intptr_t)selection->RangeRef, selection->GetSelected(selection->RangeRef)); + ImGuiMultiSelectFlags local_flags = flags; + if (use_multiple_scopes) + local_flags &= ~ImGuiMultiSelectFlags_ClearOnClickWindowVoid; // local_flags |= ImGuiMultiSelectFlags_ClearOnClickRectVoid; + ImGuiMultiSelectData* multi_select_data = ImGui::BeginMultiSelect(local_flags, (void*)(intptr_t)selection->RangeRef, selection->GetSelected(selection->RangeRef)); selection->ApplyRequests(multi_select_data, ITEMS_COUNT); - if (multiple_selection_scopes) + if (use_multiple_scopes) ImGui::Text("Selection size: %d", selection->GetSelectionSize()); // Draw counter below Separator and after BeginMultiSelect() if (use_table) @@ -3062,7 +3068,7 @@ static void ShowDemoWindowMultiSelect() if (widget_type == WidgetType_TreeNode) ImGui::PopStyleVar(); - if (multiple_selection_scopes == false) + if (use_multiple_scopes == false) ImGui::EndListBox(); } ImGui::PopID(); // ImGui::PushID(selection_scope_n); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index f1da8b168..481853948 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6391,6 +6391,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiID storage_id, ImGuiTreeNodeFlags // Compute open and multi-select states before ItemAdd() as it clear NextItem data. bool is_open = TreeNodeUpdateNextOpen(storage_id, flags); + const bool is_multi_select = (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasSelectionData) != 0; // Before ItemAdd() bool item_add = ItemAdd(interact_bb, id); g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; g.LastItemData.DisplayRect = frame_bb; @@ -6464,7 +6465,6 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiID storage_id, ImGuiTreeNodeFlags const bool was_selected = selected; // Multi-selection support (header) - const bool is_multi_select = (g.MultiSelectState.Window == window); if (is_multi_select) { MultiSelectItemHeader(id, &selected); @@ -6780,7 +6780,9 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl } const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; + const bool is_multi_select = (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasSelectionData) != 0; // Before ItemAdd() const bool item_add = ItemAdd(bb, id, NULL, disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None); + if (span_all_columns) { window->ClipRect.Min.x = backup_clip_rect_min_x; @@ -6816,7 +6818,6 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; } // Multi-selection support (header) - const bool is_multi_select = (g.MultiSelectState.Window == window); const bool was_selected = selected; if (is_multi_select) { @@ -7166,7 +7167,7 @@ ImGuiMultiSelectData* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, void* ms->In.RequestSelectAll = true; if (flags & ImGuiMultiSelectFlags_ClearOnEscape) - if (Shortcut(ImGuiKey_Escape)) + 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; }